본문으로 바로가기

앞서 유사한 기능의 플러그인인 Headroom.js 에 대하여 살펴보았는데, 구형 IE에 대한 크로스브라우징에 어려움을 겪을 수 있습니다. 이에 원리는 유사하나 대상 요소가 명확하게 한정된 요소일때 간단히 구현할 수 있는 스니펫을 소개합니다.

고정된 헤더(네비게이션) 사용 불편함 인지

많은 웹사이트에서 네비게이션 탐색에 도움을 주기위해 상단의 헤더(네비게이션을 포함한)를 CSS의 포지셔닝을 사용해 position: fixed 와 같이 고정해 두곤 한다. 헤더 부분은 대부분 상호(브랜드)를 포함하고 네비게이션을 포함하기에 고정된 헤더에 높이가 긴 콘텐츠를 포함하고 있는 경우 오히려 사이트의 사용자경험을 떨어뜨리는 요인이 되기도 한다. 이는 모바일과 같은 뷰포트가 작은 기기들에서는 더욱 그러하다.

기존의 단순히 고정된 형태의 헤더 디자인을 제공하는 것 보다는 사용자의 스크롤 방향에 따라 헤더부분을 보여주거나 감춤으로써, 사용자의 의도에 맞는 디자인을 제공하는 것이 목적이다. 즉, 사용자의 스크롤이 아래로 향한다면 콘텐츠를 탐색하길 원하는 것이라 간주하고 헤더부분을 감추고, 스크롤이 위로 향한다면 처음으로 돌아가거나 네비게이션을 찾아 이동하는 것으로 간주하여 헤더부분을 재등장 시키는 원리이다. 이것을 원저자는 혁명이라 부르고 있다.

원리의 이해

CSS3의 트랜지션과 약간의 자바스크립트를 사용하여 이 효과를 얻는게 주된 원리이다.

  1. 헤더 요소에 position: fixed 를 설정한다.
  2. 스크롤 방향이 아래로 향한다면 헤더 요소에 클래스를 추가하고 이 클래스에 의해 헤더가 위로 사라진다.
  3. 스크롤 방향이 위로 향한다면 추가한 클래스를 제거하여 헤더 요소가 재등장한다.

HTML 구조

기본 구조는 아래와 같으나 사실 헤더부분은 fixed 값을 가지게 되므로 다른 곳의 영향을 벗어나기에 어떠한 구조라도 문제는 없다.

<header></header>
<main></main>
<footer></footer>

CSS 예제

기본적인 CSS 디자인은 아래와 같으며, 자신의 디자인에 맞게 수정하면 된다.

body {
    padding-top: 40px; // 헤더 높이만큼 여백부여
}

header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 40px;
    background: #f5b335;
    transition: top 0.2s ease-in-out;
}

// 자바스크립트로 추가될 클래스
.nav-up {
    top: -40px; // 헤더 높이와 같게 
}
원문의 내용은 위와 같으나 부드러운 효과를 부여하기 위해서는 각종 등장 애니메이션을 적용할 수도 있다. 또한 단순히 트랜지션으로 위치 이동을 제어하는 것보다 트랜스폼(transform)과 트랜지션의 조합이 다양한 기기에서 버벅임없는 자연스러운 효과를 얻을 수 있다.

자바스크립트 구현

jQuery를 문서에 삽입한다.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

코드를 구현하기에 앞서, 스크롤 이벤트를 감지하고 동작을 부여하는 것은 성능에 많은 영향을 준다는 것을 언급하고 싶다. 이 문제를 해결하기 위해서 스크롤된 모든 픽셀에 대해 동작을 구현하는 것 대신에 사용자가 스크롤한 간격을 확인하고 있다.

var didScroll;
// 스크롤시에 사용자가 스크롤했다는 것을 알림 
$(window).scroll(function(event){
    didScroll = true;
});

// hasScrolled()를 실행하고 didScroll 상태를 재설정
setInterval(function() {
    if (didScroll) {
        hasScrolled();
        didScroll = false;
    }
}, 250);

function hasScrolled() {
  // 동작을 구현
}

$(window).scroll 에 의해 스크롤 이벤트를 감지하여 didScroll 의 변수 값을 true로 설정한다. 매 250ms 마다 didScroll의 변수 값을 체크하여 동작을 구현하고 다시 didScroll의 변수 값을 false로 설정한다. 이는 스크롤될때 마다 전체의 동작을 구현하는 것보다 변수를 설정하는 것이 브라우저의 부하를 덜 수 있는 방법이 된다.

헤더 숨기기

아래의 요구사항에 맞춰질때 헤더를 숨긴다.

  1. delta 값 보다 더 스크롤이 되었을 경우
  2. 헤더의 높이보다 더 스크롤이 되었을 경우
  3. 위 또는 아래로 스크롤이 되었을 경우
  4. 변수에 현재의 스크롤 위치를 저장

변수값 설정

스크립트의 상단에 변수값을 설정하도록 하자.

var lastScrollTop = 0;
var delta = 5;  // 동작의 구현이 시작되는 위치 
var navbarHeight = $(‘header’).outerHeight();  // 영향을 받을 요소를 선택

hasScrolled() 구현

접근하기 쉽게 현재 스크롤의 위치를 저장한다.

var st = $(this).scrollTop();

설정한 delta 값보다 더 스크롤되었는지를 확인한다.

if (Math.abs(lastScrollTop — st) <= delta)
    return;

헤더의 높이보다 더 스크롤되었는지 확인하고 스크롤의 방향이 위인지 아래인지를 확인한다.

// If current position > last position AND scrolled past navbar...
if (st > lastScrollTop && st > navbarHeight){
    // Scroll Down
    $(‘header’).removeClass(‘nav-down’).addClass(‘nav-up’);
} else {
    // Scroll Up
    // If did not scroll past the document (possible on mac)...
    if(st + $(window).height() < $(document).height()) { 
      $(‘header’).removeClass(‘nav-up’).addClass(‘nav-down’);
    }
}

lastScrollTop 에 현재 스크롤위치를 지정한다.

lastScrollTop = st;

완성된 자바스크립트 코드

// Hide Header on on scroll down
var didScroll;
var lastScrollTop = 0;
var delta = 5;
var navbarHeight = $('header').outerHeight();

$(window).scroll(function(event){
    didScroll = true;
});

setInterval(function() {
    if (didScroll) {
        hasScrolled();
        didScroll = false;
    }
}, 250);

function hasScrolled() {
    var st = $(this).scrollTop();
    
    // Make sure they scroll more than delta
    if(Math.abs(lastScrollTop - st) <= delta)
        return;
    
    // If they scrolled down and are past the navbar, add class .nav-up.
    // This is necessary so you never see what is "behind" the navbar.
    if (st > lastScrollTop && st > navbarHeight){
        // Scroll Down
        $('header').removeClass('nav-down').addClass('nav-up');
    } else {
        // Scroll Up
        if(st + $(window).height() < $(document).height()) {
            $('header').removeClass('nav-up').addClass('nav-down');
        }
    }
    
    lastScrollTop = st;
}
위에서 소개한 동작과 유사한 구현방식으로 헤더 요소를 완전히 숨기지 않고 높이를 줄인다던가 다른 변화를 준다던가 하는 다양한 결과를 만들수 있다. 관련하여 많은 스니펫들이 떠돌고 있으며 검색으로 접근할 수 있을 것이다. 핵심은 스크롤 방향에 맞춘 클래스의 추가와 제거이다.


신고

댓글을 달아 주세요

  1. 김종호 2015.11.16 13:18 신고

    유용한 정보 감사합니다^^,

  2. 예나 2016.03.02 15:33 신고

    정보 감사합니다~^^

  3. 하광현 2016.05.16 06:39 신고

    아주 초보라 Headroom.js처럼 예제 파일은 없나요? ㅡㅡ,

  4. 랑포 2016.05.20 13:54 신고

    완성본은 없는건가요??

  5. 홍주 2016.05.23 09:11 신고

    클리어인터벌이 없는데 부화같은것은 없을지요?

  6. 슈퍼장 2016.08.01 10:29 신고

    setInterval은 이벤와 무관하게 계속 호출될 텐데 이벤트 발생시에 구현하는 방법은 없을까요? 마우스 터치이벤트면 changedTouches속성을 써서 이벤트발생시에 체크가 가능한데 스크롤시가 애매하네요 ㅜ

    • 강그루 2017.07.20 20:12 신고

      아무래도 루프를 돌리고 있으면 모바일 환경에서 리소스 낭비 문제가 생길 것입니다.

      .scroll으로 이벤트 핸들러를 추가하고,
      toggle만 검사해준 다음 .slideToggle함수를 이용하세요.

      예시입니다:(금칙어라 안올라가네요)
      코드펜.io/kidkkr/pen/GEVjjx

  7. 상우 2016.09.01 14:57 신고

    모바일 ios에서도 문제없이 잘 작동하는 건가요??

  8. 황승준 2016.09.22 01:47 신고

    좋은 지식 감사히 배워갑니다.
    작동 잘합니다. css에서 left에 세미콜론이 빠졌습니다.^^

  9. 2016.11.15 17:53

    비밀댓글입니다

  10. 칠천피트 2017.05.03 11:56 신고

    오홋...정보 감사합니다.

  11. 2017.05.13 17:17 신고

    안녕하세요~

    if(Math.abs(lastScrollTop - st) <= delta) return;

    이부분은 꼭 들어가야 하는건가요?

    • BlogIcon 흉내쟁이 2017.05.13 17:21 신고

      delta 값이 반응할 위치의 마지노선입니다. 이것을 사용하지 않아도 작동하는데는 문제없습니다.

      delta 값을 넉넉히 주시면 애니메이션이 좀 여유있게 반응하는 정도입니다.

    • 2017.05.13 17:25 신고

      빠른 답변 감사합니다!!

      한가지만 더 여쭤볼게요

      Scroll Up에

      if(st + $(window).height() < $(document).height())

      이부분은 뭐를 판단 하는건가요??

    • BlogIcon 흉내쟁이 2017.05.13 17:32 신고

      st 는 현재 스크롤의 위치값
      $(window).height() 는 브라우저 창높이
      $(document).height() 는 전체 문서의 높이

      즉, 문서의 마지막에 다다르지 않았다면 scroll up 에 표현된 식이 적용된다는 겁니다.

    • 2017.05.13 17:34 신고

      아~ 그렇군요!!

      많이 배워갑니다!! 감사합니다

티스토리 툴바