jquery ページ内リンクへアニメーションで移動

2013年12月8日

ページ内リンクへプラグインを使わずにアニメーションで移動するスムーズスクロールに関するメモ。

animate メソッドと scrollTop プロパティ

参考:「jQueryで画面をスクロールさせる時の注意点

画面をスクロールさせる場合 animate メソッドと scrollTop プロパティを利用するが、どのセレクタに対して実行すべきかはブラウザ(WebKit かそれ以外)により異なる。

WebKit : body 要素
それ以外 : html 要素

$('body').animate({ scrollTop: 0 }); // WebKit
$('html').animate({ scrollTop: 0 }); // WebKit以外

以下のように両方を指定して全てのブラウザに対応することができるが、単純なスクロールだけなら問題はないが、両方指定するとコールバックを指定している場合、コールバックが2回呼ばれてしまう。

$('html, body').animate({ scrollTop: 0 });

そのためブラウザを判定する必要があるが、jQuery 1.9 になって $.browser が使えなくなっているので、「使用したい機能が機能するか」で判定する。

参考:「jQuery 1.9 で $.browser が使えなくなってしまった対策

スクロールさせることができる要素が html か body かを判定するには、実際にどちらの要素でスクロールするかを調べる。

var isHtmlScrollable = (function(){
    var html = $('html'), top = html.scrollTop();
    var elm = $('<div/>').height(10000).prependTo('body');
    html.scrollTop(10000);
    var rs = !!html.scrollTop();
    html.scrollTop(top);
    elm.remove();
    return rs;
})();
 
//スクロールのアニメーション
$(isHtmlScrollable ? 'html' : 'body').animate({scrollTop:100});

以下は全て上記の関数(isHtmlScrollable)の記述を前提。

ページトップへの移動

href 属性の値が「#」の場合、ページトップへアニメーションで移動する。

jQuery(document).ready(function($) { 
    $('a[href=#]').click(function () {
        $(isHtmlScrollable ? 'html' : 'body').animate({
            scrollTop: 0
        }, 500);
        return false;
    });  
});

注意

但し、href 属性の値が「#」でも、上記のアニメーションを適用したくないものもあるので、それらに関しては not() を使って除外する。例えばこのサイトの場合、ナビゲーションの「その他」は階層メニューになっているので、上記を使用すると階層メニューが機能しなくなるので、以下のようにする。それらがあまりに多い場合は、アニメーションするものには、クラスを付けるなど考える必要があるかもしれない。

jQuery(document).ready(function($) { 
    $('a[href=#]').not('#header-nav a.dropdown-toggle').click(function () {
        $(isHtmlScrollable ? 'html' : 'body').animate({
            scrollTop: 0
        }, 500);
        return false;
    });  
});

ページ内リンクとページトップへの移動

ある要素の id 属性へのリンクへの移動。<a href=”#someElement” > のような場合にも対応するには以下のようにしてみる。

  • $(“[属性名^=’値’]”):特定の属性が指定した値で始まっている要素
  • .not() 除外する要素を指定(除外するものがなければ不要)
  • 要素の href 属性の値を「hrefval」に格納
  • 「hrefval」はページトップの場合「#」、その他の場合は要素の id「#id名」
  • 対象となる要素「targetelement」を生成
  • 要素のポジションを取得
  • その位置までアニメーション
jQuery(document).ready(function($) {
    $('a[href^=#]').not('アニメーションさせない a 要素').click(function(){
        var hrefval= $(this).attr('href');
        var targetelement = hrefval == "#" ? $('html') : $(hrefval);
        var positiontop = targetelement.offset().top;
        $(isHtmlScrollable ? 'html' : 'body').animate({
            scrollTop: positiontop
        }, 500);
        return false;
    });
});

スクロールすると「先頭へ」というリンクを表示させる

ある程度以上スクロールすると「先頭へ」というリンクを表示させ、クリックするとページ先頭にアニメーションで移動する。

  • フッターなどの要素の後に「先頭へ」というリンク(クラス名:”tothetop”)を追加する
  • リンクを非表示にする topBtn.hide();
  • スクロールの値(以下の例では500px)に応じてリンクをアニメーションで表示・非表示にする
  • その後に前述のアニメーションでの移動に関する記述をする
jQuery(document).ready(function($) {
    $('#footer').after('<div class="tothetop"><a href="#">先頭へ</a></div>');
    var topBtn = $('.tothetop');  
    topBtn.hide();
    $(window).scroll(function () {
        if ($(this).scrollTop() > 500) {
            topBtn.fadeIn();
        } else {
            topBtn.fadeOut();
        }
    });
    
    //ページ内リンクとページトップへの移動
    $('a[href^=#]').not('アニメーションさせない a 要素').click(function(){
        var hrefval= $(this).attr('href');
        var targetelement = hrefval == "#" ? $('html') : $(hrefval);
        var positiontop = targetelement.offset().top;
        $(isHtmlScrollable ? 'html' : 'body').animate({
            scrollTop: positiontop
        }, 500);
        return false;
    });
});

「ページ先頭へ」というリンクのCSSの例

div.tothetop {
  position: fixed;
  right: 5%;
  bottom: 5%;
  z-index: 1500;
}  

div.tothetop a {
  display: block;
  font-weight: bold;
  color: #FFF;
  padding: 10px;
  margin: 0;
  background-color: #AAA;
  font-size: 0.8em;
  outline: none;
  text-decoration: none;  
  /* Firefox v1.0+ */
  -moz-border-radius:6px ;
  /* Safari v3.0+ and by Chrome v0.2+ */
  -webkit-border-radius:6px ;
  /* Firefox v4.0+ , Safari v5.0+ , Chrome v4.0+ , Opera v10.5+  and by IE v9.0+ */
  border-radius:6px ;  
}

div.tothetop a:hover {
  color: #9CF;
  background-color: #666;
}

スピードやポジションの調整

以下は移動する距離に応じてスピードを調整(速くなり過ぎないなど)して、ページ内リンクへの移動の場合は、ナビゲーションが常に表示されているため、位置を調整する例。

jQuery(document).ready(function($) {
    $('a[href^=#]').not('アニメーションさせない a 要素').click(function(){
        var hrefval= $(this).attr('href');
        var positiontop;
        var speed;
        if(hrefval == "#") {
            positiontop = 0;
            speed = 200 + $(this).offset().top /30;
        }else{
            var targetelement = $(hrefval);
            positiontop = targetelement.offset().top -120;
            speed = 200 + (positiontop + 120) / 30;
        }
        $(isHtmlScrollable ? 'html' : 'body').animate({
            scrollTop: positiontop
        },  speed);
        return false;
    });
});

外部ページでもスムーズスクロール

ページ内リンクではなく、外部ページでもスムーズスクロールする方法。

  • ページ内リンクと同様にスクロールさせることができる要素が html か body かを判定
  • # 記号に続くURL の部分(ハッシュ)を「window.location.hash」で取得
  • ハッシュが空文字でなければ、それを元にそのポジションを取得(下記の場合は 100px 上に調整)
  • animate() でアニメーション
var isHtmlScrollable = (function(){
  var html = $('html'), top = html.scrollTop();
  var elm = $('<div/>').height(10000).prependTo('body');
  html.scrollTop(10000);
  var rs = !!html.scrollTop();
  html.scrollTop(top);
  elm.remove();
  return rs;
})();

var hash = window.location.hash;
if (hash != "") {
  var target    = $(hash);
  var pos    = target.offset().top - 100;    
  $(isHtmlScrollable ? 'html' : 'body').animate({      
    scrollTop: pos
  }, 500, "swing");
}

注意する点としては、ページ内リンクの場合はページが全て読み込まれている(jQuery の処理なども終了している)が、外部ページの場合は記述する位置によっては、他の jQuery の処理などに影響されて期待した位置にスクロールされない可能性がある。その場合は、記述する位置を最後の方に移して見るのも1つの方法。

window.location / MDN