Advertisement
  1. Web Design
  2. Patterns
Webdesign

스크롤로 움직이는 페이지에서 헤더를 고정하는 방법

by
Difficulty:BeginnerLength:MediumLanguages:

Korean (한국어) translation by Jin Ah Chon (you can also view the original English article)

이번 튜토리얼에서는 요즘에 웹사이트에서 많이 보이는 패턴을 제작해 보도록 하겠습니다. 스크롤 해서 페이지를 이동할 때 매끄럽게 애니메이션 되는 고정 헤더입니다. 기본 구조부터 잡고 나서 CSS와 순수 자바스크립트를 이용해 동작하도록 하겠습니다.  마무리하기 전에는 코드의 최적화 방법을 짧게 얘기하겠습니다. 또한, 이런 유형의 패턴을 터치 방식의 기기에 적용할 때 현시점에서 해결해야 할 문제점에 대해서도 논의하겠습니다.

우리가 무엇을 제작할지 감 잡도록 아래에 견본을 보여드립니다. (원하면 전체 화면에서 보세요.)

HTML 마크업

<nav>가 안에 있는 header와 소수의 중첩된 다른 요소가 있는 아래의 마크업을 이용해 연습을 시작하겠습니다. 

nav 요소는 header의 일부분이며 3개의 요소를 담고 있습니다. 즉, 로고와 메인 메뉴, 그리고 (1061xp 이하일 때) 반응형 메뉴로 변형되는 햄버거(placeholder) 버튼입니다.

메모: 햄버거 버튼을 누른다 해도 아무런 변화가 없을 것입니다. 반응형 메뉴를 제작하는 과정은 이 튜토리얼의 학습 범위를 벗어납니다.

초기 CSS 스타일

자, 계속해서 CSS 스타일의 일부를 살펴봅시다.

여기서 가장 중요한 규칙을 다음과 같이 간단히 설명하겠습니다.

  • header 는 위치가 고정된 요소입니다.
  • nav 요소의 레이아웃에 플랙스박스(flexbox)를 사용합니다.
  • 로고에 margin-top: 50pxmargin-left: 40px을 적용했습니다. padding: 20px 30px도 추가했습니다.
  • 메인 메뉴는 로고와 흡사하게 margin-top: 50pxmargin-right: 50px을 사용했습니다.
  • 반응형 링크 버튼은 보이지 않습니다. 버튼은 뷰포트의 너비가 1061px 이하가 되면 나타납니다. 그밖에 위와 오른쪽에 margin을 10px로 주었습니다.
  • 요소에 transition 속성을 추가했는데, 향후 이 속성값은 변경될 것입니다. 이런 식으로 처음 상태와 최종 상태 사이에 부드러운 transition 효과를 적용합니다.

이 규칙이 적용된 헤더는 다음과 같이 보입니다.

How the header initially looks like

헤더 애니메이션

지금까지는 헤더의 기본 구조를 만들었습니다. 이제 다음 과정을 얘기할 차례입니다.

  • main 요소는 header 바로 아래에 있어야 합니다. header가 positioned: fixed로 되어 있어서 main 요소 위로 올라간다는 점을 기억하세요.
  • 스크롤 해서 페이지가 아래로 내려갈 때 header에서 애니메이션이 동작해야 합니다.

첫 번째 작업을 완수하기 위해 main 요소에 padding-top 속성을 추가했습니다. 속성값은 header 높이와 같아야 합니다. 이 경우, header에 고정된 높이를 명시하지 않았으므로 자바스크립트를 사용해 그 높이 값을 계산할 것입니다. 그러고 나서 main 요소에 부합하는 padding을 추가하겠습니다.

두 번째 작업을 완수하기 위해 다음과 같이 작업할 것입니다.

  1. document가 수직으로 스크롤 된 만큼의 픽셀 수를 가져옵니다.
  2. 그 수가 150px 이상이라면 scroll class를 header에 할당합니다.

JavaScript

필요한 자바스크립트 코드가 아래에 있습니다. 변수를 정의하고 header 높이를 계산하며 padding-top 값을 main 요소에 넣는 것부터 시작합니다.

이 견본에서 header의 높이값을 가져오는 데 offsetHeight 속성을 이용했습니다. getBoundingClientRect() 메서드를 동일하게 사용해도 된다는 것을 알아 두세요. 이 메서드가 소수 값을 반환한다는 것은 언급할 만 하죠.

자, 스크롤 이벤트로 갑니다.

document가 수직으로 스크롤 된 만큼의 픽셀값을 계산하는 데 윈도우의 pageYOffset 속성을 이용했습니다. 이 속성이 IE(<9) 하위 버전에서 작동하지 않는다는 점을 기억하세요. 만약 하위 버전도 지원하고 싶다면 여기에 적용 가능한 해결 방법이 있습니다.

그다음에는 header에서 scroll class를 더하고 빼는데 classList 속성을 사용합니다. 이 속성을 모든 브라우저에서 지원하지 않습니다. 그러나 모든 브라우저에서 잘 동작하게 하고 싶다면, 여러분에게 classList.jsclassie.js 폴리필이 필요할 것 같습니다. 예를 들자면, 하나의 class를 수정하는데 className 속성을 사용해도 됩니다. 그러나 현실의 시나리오에서 (다수의 class가 있는 경우에) 이 방법이 이상적인 해결 방안이 아닐 수도 있습니다. 

다시 정리하면, 우리는 두 가지 다른 경우에 함수를 호출합니다.

  • 페이지가 로딩될 때
  • 그리고 브라우저 창의 크기를 바꿀 때

CSS

스크롤이 150px의 한계를 벗어나 있는 동안에 몇 가지 부가적인 CSS 규칙이 발생합니다.

구체적으로 다음과 같은 변화가 생깁니다.

  • header에 옅은 회색의 box shadow가 들어갑니다.
  • 로고의 padding값과 폰트 크기가 줄어듭니다.
  • 교차축(cross-axis)를 가로지르는 flex 아이템의 정렬이 바뀝니다.
  • 로고와 메뉴와 반응형 링크 버튼의 margin 값을 없앱니다.

위에서 말한 규칙으로 다음과 같이 새 header 레이아웃이 나옵니다.

How the header looks like when our scrolling exceeds the limit of 150px

반응형으로

위의 내용에서 설명했듯이 뷰포트의 너비가 1061px 이하일 때는 메뉴를 안 보이게 숨기고 (실제로 작동하지 않는) 반응형 링크 버튼을 보여줍니다. 더불어 대상 요소에 몇 가지 다른 변화를 주겠습니다.

반응형 header의 초기 화면 상태를 아래에서 볼 수 있습니다.

How the responsive header initially looks like

다음은 그와 관련한 CSS 규칙입니다.

그리고 아래는 애니메이션이 일어난 후의 header 상태입니다.

How the responsive header looks like when our scrolling exceeds the limit of 150px

성능에 관해 고려할 사항

우리가 원했던 동작을 header에 적용했으니 이제는 한 단계 더 나아가 성능에 대해 몇 가지 얘기해 봅시다.

예제에서는 scroll 이벤트가 발생할 때 header에서 애니메이션을 처리하는 코드(예: callbackFunc)가 실행됩니다. 이는 이벤트가 수백 번 이상  발생할 수 있다는 의미입니다. 특히 스크롤을 위아래로 해서 페이지를 이동하는 동안  callbackFunc 안에 실행할 코드가 많이 있다면 성능과 연관되어 문제가 발생할 수 있습니다. 이 예제의 경우에 간단한 애니메이션을 이용해 처리했지만, 실무에서 위치를 변경하는 요소 등과 같은 더 복잡한 시나리오를 원한다고 상상해 보세요.

그러면 이 문제에 관해 무엇을 할 수 있을까요? 적용 가능한 해결책많이 있지만, 그중에 하나를 간단히 얘기해 봅시다. 구체적으로 얘기하자면, 200 밀리세컨드(임의의 값입니다)마다 한 번씩 함수를 실행하고 싶습니다. 이 함수를 실행하기 위해 모던 자바스크립트 유틸리티 라이브러리인 Lodash를 이용하겠습니다. 이 라이브러리에서 우리가 필요로 하는 throttle 함수를 제공합니다.

우선, 프로젝트에 라이브러리를 포함합니다. (기쁘게도 원하는 함수만 포함하는 옵션도 있습니다.) 그다음 아래 코드를 

window.addEventListener("scroll", callbackFunc);

이것으로 대체해 줍니다.

window.addEventListener("scroll", _.throttle(callbackFunc, 200));

차이점을 이해하기 위해 간단한 테스트를 해보죠. callbackFunc 함수를 호출할 때마다 숫자가 증가하는 카운터를 초기화하겠습니다.

이제는 스크롤을 해서 페이지를 위아래로 이동해 보세요. 스크롤 하면서 여러분은 카운터 수치가 대략 200ms마다 변한다는 것을 인지할 것입니다. 다음에는 이 과정을 Lodash를 이용하지 않고 다시 해보세요. 카운터 수치가 더 빠르게 변한다는 것을 알게 될 것입니다.

이런 최적화가 여기의 간단한 예제에 굳이 필요하지 않을 듯하지만, 비용이 많이 들고 반복되는 과제를 다룰 때는 고려해봐야 합니다.

위와 같은 방식으로 resize 이벤트 리스너를 최적화할 수 있습니다.

브라우저 지원

작업 결과는 가장 최신 브라우저와 디바이스에서 작동합니다. 그렇다 하더라도 모바일 기기(예: iOS 기기)에서 얻는 경험이 모두 이상적인 것은 아닙니다. 모바일 기기와 비교해 볼 때 데스크톱 브라우저에서 scroll 이벤트가 다르게 동작하기 때문에 그렇습니다. 예를 들자면, 데스크톱 브라우저에서 scroll 이벤트는 지속적으로 발생합니다. 반면 iPad에서는 사용자가 화면에서 손가락으로 스와이프하고 나서 떼는 순간 발생합니다.

여러분은 위의 문제점을 알고 있으므로 패턴이 웹 페이지에서 사용하기에 적절한지를 판단해야 합니다.

마무리

튜토리얼에서는 스크롤을 해서 페이지를 이동할 때, 헤더가 애니메이션이 되면서 고정되도록 제작했습니다. 여러분이 데모를 맘에 들어 했으면 하는 바람입니다. 그리고 향후 프로젝트에서 영감을 떠올리는 데 이용하셨으면 합니다!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.