menu

페이지 내부의 id를 타겟으로 앵커<a> 태그를 심으면 id를 가지고 있는 요소가 창의 맨 윗부분으로 위치하도록 스크롤이 뿅 하고 이동하게 된다.

바로 거기에서 문제가 시작된다. 이 <a> 태그가 도입된 지 십수 년이 되도록 단 한 명의 브라우저 개발자도 스무드 스크롤링 등의 기능을 적용해주지 않았던 것이다. 분노를 참지 못한 나는 결국 직접 코딩을 시작하는데...

#1 문제 파악

문제점은 크게 세 가지가 있다.

  1. 앵커가 문서 내부에 있는 곳을 가리키는지, 아니면 외부를 가리키는지 구분되지 않음.
  2. 링크 누름 → 해당 요소로 갑자기 뿅! → 누른 사람들 어리둥절.
  3. 링크 누름 → 해당 요소가 꼭대기에 뙇! → 블로그 상단 정보창이 내려와서 요소 가림.

#2 문제 해결

#2-1

일단 문서 내부를 가리키는 앵커일 경우 아이콘으로 표시. css에 몇 줄 추가해주면 해결!

.article a[href^="#"]:before    { font: 1.333em 'icon'; content: "\f293"; }
.article a:not([href*="jinh.tistory.com"]):not([href^="#"]):not([href^="/"]):before    { font: 1em 'icon'; content: "\f35b"; }

그리고 Material Design IconsMaterial Design Iconic Font 등 웹폰트에다 아이콘 박아넣은 개꿀 라이브러리 줍줍 후 치덕치덕 발라줌.

그럼 이렇게 나옴: [문서 내부], [블로그 내부], [블로그 외부] 이야 멋스럽다!

#2-2, 2-3

여기서 문서 내부로 이동하는 링크를 발견한 사람들의 심리 상태를 묘사해본다.

(시작)

[링크 발견]

['아앗 페이지 내부의 유용한 곳으로 이동하는 링크가??']

<클릭>

[문서 내부의 어딘가로 순간 이동]

['띠용! 여기가 어디지 내가 뭘 눌렀더라???']

[혼란, 공황, 두려움]

[잠시 후 냉정 찾음]

['아 내가 찾는 내용이 창의 최상단에 있었구나!']

(끝)

흠... 이 과정을 아래에 묘사한 간결한 의식의 흐름으로 바꾸는 것이 목표.

(시작)

[링크 발견]

['아앗 페이지 내부의 유용한 곳으로 이동하는 링크가??']

<클릭>

['아앗 문서 내부의 어딘가로 이동하... 호옹이! 이렇게 유용한 내용으로 연결되다니!']

(끝)

일단, 부드럽게 스크롤을 이동시키는 코드를 추가.

function scroll_smooth() { 
    $("a[href^='#']").click(function(event){
        event.preventDefault();                                           //기본으로 실행되는 기능을 막고,
        $('body').animate({scrollTop:$(this.hash).offset().top}, 500);    //jquery animate로 스크롤 이동
    }
}
$(function() {    //문서 로딩이 끝나면 실행
    scroll_smooth();
});

위 코드는 성공적으로 동작했다. 그러나 내 블로그는 스크롤을 어느 정도 올리면 상단에 정보창이 튀어나오게 되어있는데, 이동된 요소를 덮어버리기 때문에 어리둥절한 사용자가 호흡곤란 증세를 나타내다가 결국 다이어트 계획마저 잊어버리고 야식으로 치킨을 시켜먹기로 결정해버리는 안타까운 상황까지 발전할 수도 있다.

그래서 코드는 다음과 같이 수정되었다.

function scroll_smooth() { 
    $("a[href^='#']").click(function(event){
        event.preventDefault();
        if($(this.hash).offset().top < $('body').scrollTop() - 600){                                 //타켓 위치가 현 스크롤 위치보다 높으면
            $('body').animate({scrollTop:$(this.hash).offset().top - $('header').height()}, 500);    //헤더만큼 밑으로 내려줌
        } else ​{
            $('body').animate({scrollTop:$(this.hash).offset().top}, 500);
    }
}//이하 생략

막상 만들고 나니깐, 애초에 요소를 창 맨 꼭대기에다 표시할 이유가 전혀 없다는 생각이 들었다. 그냥 마우스 포인터 있는 자리로 이동시키면 되잖아? 그래서 또 수정.

function scroll_smooth() { 
    $("a[href^='#']").click(function(event){
        event.preventDefault();
        if($(this.hash).offset().top < $('body').scrollTop() - 600 && event.pageY - $('body').scrollTop() < $('header').height()){
            $('body').animate({scrollTop:$(this.hash).offset().top - $('header').height()}, 500);    //얘는 예외로 두고
        } else ​{                                                                                     //아랫놈을 마우스 포인터 위치로 이동시키도록 함
            $('body').animate({scrollTop:$(this.hash).offset().top - event.pageY + $('body').scrollTop() + $(this.hash).height() / 2}, 500)
    }
}//이하 생략

완성된 코드 돌려보니 개멋있음... (짝짝짝)

근데... 뭔가 부족함을 느낀 나는 결국... 타겟 요소를 색상으로 강조해줘야겠다는 아이디어를 생각해 내고야 말았다.

        function bg_change(color, time){
                $(this.hash).animate({"background-color":color}, time);
        }

이런 함수를 만들긴 했는데, 여기서 또 문제 발생! 문제를 하나 해결하면 다른 문제가 두 개 발생한다는 코딩의 법칙 몸소 체험 중...

문제 1: jQuery로는 배경색을 애니메이션으로 못 만든다. → jQuery UI 혹은 css의 transition 값으로 해결.

문제 2: <table> 의 자식 요소인 <tr> 태그는 background-color 가 적용되지 않는다. → 태그 이름 읽어서 tr일 때 예외 적용.

어찌어찌 해서 완성된 코드는 다음과 같다.

function scroll_smooth() {
    $("a[href^='#']").click(function(event){
        event.preventDefault();
        
        var target = $(this.hash);
        var target_bg;
        if (target[0].tagName == "TR"){
            target_bg = $(this.hash).children("td").css("background-color");
        } else {
            target_bg = $(this.hash).css("background-color");
        }
        function bg_change(color, time){
            if (target[0].tagName == "TR"){
                target.children("td").css({"background-color":color,"transition":time});
            }else{
                target.css({"background-color":color,"transition":time});
            }
        }
        
        if($(this.hash).offset().top < $('body').scrollTop() - 600 && event.pageY - $('body').scrollTop() < $('header').height()){
            $('body').animate({scrollTop:$(this.hash).offset().top - $('header').height()}, 500, function(){bg_change(target_bg,".75s");});
        }else{
            $('body').animate({scrollTop:$(this.hash).offset().top - event.pageY + $('body').scrollTop() + $(this.hash).height() / 2}, 500, function() {bg_change(target_bg,".75s");});
        }
        
        bg_change("#ffeb3b",".25s");
    });
}

 

#3 결과

테스트 앵커를 눌러보자. 혹은, 위 코드가 적극적으로 적용된 페이지 "여초 용어 정리" 게시물에서 확인하자. 

끝.

붙임 #1: 추가 사항

코드가 파이어폭스에서 안 된다는 댓글을 보고 기분이 다운됐었지만, 해결 후 다시 기분 좋아짐 ㅎㅎ. 게다가 위치 이동 후 더블 클릭하면 다시 그 앵커 자리로 되돌아가는 코드까지 추가 시킴. 완성된 코드는 아래에...

function scroll_smooth() {
    $("a[href^='#']").click(function(event){
        event.preventDefault();
        
        var target = $(this.hash);
        var target_reverse = $(this);
        var target_bg;
        var reversible = true;
        
        function bg_change(t, color, time){
            if (t[0].tagName == "TR"){
                t.children("td").css({"background-color":color,"transition":time});
            }else{
                t.css({"background-color":color,"transition":time});
            }
        }
        
        if (document.height === null) {
            pageYOffset = document.documentElement.scrollTop;
        }
        function scroll(target, event){
            if (target[0].tagName == "TR"){
                target_bg = target.children("td").css("background-color");
            } else {
                target_bg = target.css("background-color");
            }
            if(target.offset().top < pageYOffset - 600 && event.pageY - pageYOffset < $('header').height()){
                target_position = target.offset().top - $('header').height();
            }else{
                target_position = target.offset().top - event.pageY + pageYOffset+ target.height() / 2;
            }
            $('html, body').animate({scrollTop:target_position}, 500, function() {
                bg_change(target, target_bg,".75s");
            });
        }
        
        scroll(target, event);
        bg_change(target, "#ffeb3b",".25s");
        toast("원래 자리로 가려면 더블클릭");
        
        document.ondblclick = function(event){
            if(reversible){
                scroll(target_reverse, event);
                bg_change(target_reverse, "#ffeb3b",".25s");
                reversible = false;
            }
        };
    });
}

퍼가지 말고, 링크로 공유하세요. 자세한 건 공지에.

  1. 블로그 잘 보고있습니다.
    이번글에서 잘 되는지 피드백이 필요할듯해서 댓글남겨봅니다.

    파이어폭스 39.0 버전에서 테스트해봤는데
    맞는 동작이라면 해당항목으로 스크롤을 해야되는게 아닌가 싶어서요
    스크롤은 안하고 배경색은 바뀌네요.
    콘트롤+F5로 캐쉬를 비우고 다시해봐도 스크롤은 안되네요

    ㅁ? 2015.08.04 21:11 신고   link delete reply
    • 정말이네요... 크롬에서는 잘 되는데, 왜 이런 걸까요... 시무룩
      -----------------------
      해결했답니다^^ 뿌듯!

      link delete 2015.08.04 22:31 신고 Favicon of http://blog.jinh.kr JinH
  2. 파이어폭스에서 작동이 안 된 원인은 다음과 같습니다.

    1. 크롬은 현재 스크롤 위치를 <body> 기준으로 잡는다.
    2. 파폭은 현재 스크롤 위치를 <html> 기준으로 잡는다.

    ---------------------------

    따라서 해결법은 다음과 같습니다.

    1.
    "$('body').animate({scrollTop:..." 부분을
    "$('html, body').animate({scrollTop:..." 으로 수정한다.

    2.
    $('body').scrollTop() 값을 $('html, body').scrollTop() 으로 수정하면 역으로 크롬 먹통. (항상 0으로 반환) 그러므로,
    pageYOffset 값을 이용한다. 해당 값이 null인 익스플로러를 지원하기 위해,
    if (document.height === null) {
       pageYOffset = document.documentElement.scrollTop;
    }
    코드를 추가한다.

    끝!

  3. 잘되네요 ^^
    고생하셨습니다 ㅎㅎ

    ㅁ? 2015.08.05 17:12 신고   link delete reply
  4. 대단합니다 ㄷㄷㄷㄷ

    ㅎㄷㄷ 2018.01.30 14:40 신고   link delete reply

퍼가지 마세요...
링크로 공유하세요 ㅠㅠ
부탁할게요 ^_<~*

test 3-1

test 3-2

test 3-3

test 4-1

test 4-2

test 4-3

모든 글 보기
공지
방명록
Share to...

페이스북 공유

트위터 공유

구글+ 공유

카카오스토리 공유

밴드 공유

Follow & Contact

Facebook

Twitter

Mail

RSS 구독

2007-2016 © JinH