jQueryを使わないスムーススクロールのコードを紹介していますので、もしよければ参考にしてみてください。
サイト制作時に、必ずと言っていいほど使用しているスクリプトがあります。
ページ内リンク(アンカーリンク)のスムーススクロールです。
これはもう本当に毎回使用するので、手元のデフォルトのJSファイルには当たり前のように用意してあります。
しかし、先日書いた記事「【JavaScript】Chromeの最新版(61)から画面スクロール量を取得する要素がdocument.bodyじゃなくてdocument.documentElementに変わっていた事を知った。」にあるように、
Chromeの最新版(61)から、画面のスクロール位置を取得したりする要素が変わり、
今まで使用していたスムーススクロールが、Chromeでは動かなくなってしまいました。
これは簡単に修正する事ができましたが、少しひっかかる点があり、
今現在ベストな実装方法はどのようなものだろうと思い、考えてみました。
あくまでも、個人的なベストですので、ご了承下さい。
動かない例
今まで、どこかで拾ってきたコードを何も考えずそのまま使用していました。
このコードのように、スクロールする要素をユーザーエージェントで振り分けていると、Chrome61では動かなくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$('a[href^="#"]').not('.noscroll').on('click', function() { var speed = 300; var easing = 'swing'; var href= $(this).attr("href"); var target = $(href == "#" || href == "" ? 'html' : href); var position = target.offset().top; var body = 'body'; if (!navigator.userAgent.match(/WebKit/)) { body = 'html'; } $(body).animate({scrollTop:position}, speed, easing); return false; }); |
問題は7〜10行目で、ユーザーエージェントにWebKitが含まれていた場合スクロールする要素を’body’にしている所です。
WebKitを採用しているSafariやOperaでは今のところ問題ありませんが、同じWebKitでもChrome61以降では画面をスクロールする要素が’html’になったため、このコードでは動かなくなります。
(ChromeのレンダリングエンジンはBlinkですが、WebKit系列なのでユーザーエージェントには’WebKit’が含まれています。)
とりあえず、Chrome61でもhtml要素に対してanimateメソッドを実行するようにすれば、解決はできそうです。
簡単な対処
とりあえず、ユーザーエージェントで分岐せず、下記のように’html’と’body’両方を指定してしまえば、問題は解決できます。
1 2 3 4 5 6 7 8 9 |
$('a[href^="#"]').not('.noscroll').on('click', function() { var speed = 300; var easing = 'swing'; var href= $(this).attr("href"); var target = $(href == "#" || href == "" ? 'html' : href); var position = target.offset().top; $('html, body').animate({scrollTop:position}, speed, easing); return false; }); |
これで、新しいChromeでも古いChromeでも動くようになりました。
実は「スムーススクロール」で検索すると、ほとんどの記事がこの指定方法で紹介されているので、
今回のChromeの更新で問題が起きたサイトは少ないのかなと思います。
実際あまり話題になっていないようなので、気づいている人も少なそうです。
コールバックが2回呼ばれてしまう
ほとんどの場合上記のコードで問題無いと思いますが、個人的には、どうしても2つの要素に無駄にanimateを実行している所にモヤモヤしてしまいます。
本来なら、どちらか一方でいいはずです。
実際の問題点として、animateのコールバックが2回呼ばれてしまうという点があります。
1 2 3 4 5 6 7 8 9 |
$('a[href^="#"]').not('.noscroll').on('click', function() { /* 省略 */ $('html, body').animate({scrollTop:position}, speed, easing, function() { console.log('コールバック!'); }); return false; }); |
結果:
スムーススクロールが終わった後に何かをしようとした場合、同じ処理が2回行われてしまいます。
この辺りを踏まえて、ベストな方法を考えたいと思います。
ベストを考える
まず、スクロール要素の取得はユーザーエージェントじゃなく、基本的にdocument.scrollingElementで取得した方が良さそうです。
document.scrollingElementは、画面スクロールの要素を取得できるプロパティで、
例えば、Firefoxなら<html>、
Safariなら<body>、
Operaなら<body>、
Chrome60以下なら<body>、
Chrome61以上なら<html>、
を、参照できます。
残念ながらIEは対応していないので、
document.scrollingElementが使えればdocument.scrollingElementを、
それ以外(IE)はdocument.documentElementを、
念のため、古いWebKitブラウザの場合はdocument.bodyを参照するように、条件分岐をすれば良さそうです。
なので、先日書いた記事、「【JavaScript】Chromeの最新版(61)から画面スクロール量を取得する要素がdocument.bodyじゃなくてdocument.documentElementに変わっていた事を知った。」で紹介したコードをそのまま使用します。
1 2 3 4 5 6 7 8 9 |
var scrollElm = (function() { if('scrollingElement' in document) { return document.scrollingElement; } if(navigator.userAgent.indexOf('WebKit') != -1) { return document.body; } return document.documentElement; })(); |
これで、そのブラウザの画面スクロールの要素を取得する事ができました。
これを今までのjQueryのコードにどのように入れ込もうかと一瞬悩みかけたんですが、
よく考えなくとも、上記のコードの返り値の中身は<html>か<body>なので、
変数scrollElmをそのまま突っ込んでjQueryのオブジェクトにしてしまえば良さそうです。
1 2 3 4 5 |
$('a[href^="#"]').not('.noscroll').on('click', function() { /* 省略 */ $(scrollElm).animate({scrollTop:position}, speed, easing); return false; }); |
この方法で、完成形を作ります。
元々、どこからかコピーしてきたコードをそのまま使用していたのですが、
よく見てみると変数が多いのと、無駄な論理和があったりするのが気になったので、これを少し整形して、下記のようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var scrollElm = (function() { if('scrollingElement' in document) { return document.scrollingElement; } if(navigator.userAgent.indexOf('WebKit') != -1) { return document.body; } return document.documentElement; })(); $('a[href^="#"]').not('.noscroll').on('click', function() { var speed = 300; var easing = 'swing'; var href= $(this).attr("href"); $(scrollElm).animate({ scrollTop: $(href == "#" ? 'html' : href).offset().top }, speed, easing); return false; }); |
これでスッキリしました。
しばらくはこの形で問題無いのではないでしょうか。
番外編として、これをクロージャで1つにまとめてみるというのもやってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var scrollObject = (function() { var elm = (function() { if('scrollingElement' in document) { return document.scrollingElement; } if(navigator.userAgent.indexOf('WebKit') != -1) { return document.body; } return document.documentElement; })(); var speed = 300; var easing = 'swing'; return { getElm: function() { return elm; }, smoothScroll: function() { var href= $(this).attr("href"); $(elm).animate({ scrollTop: $(href == "#" ? 'html' : href).offset().top }, speed, easing); return false; } } })(); $('a[href^="#"]').not('.noscroll').on('click', scrollObject.smoothScroll); |
メリットは特にありませんが、クロージャを使ってる感で気持ちよくなれます。
scrollObject.getElm()という画面スクロールの要素を取得できるメソッドを用意している所が気持ちいいポイントですね。
クロージャにしたのはおまけですが、とりあえずはこの形で個人的にはベストとして落ち着きました。
ですが、明日には違う方法を発見してたり、2〜3日後には$(‘html, body’)でいいじゃないかという考えに至っているかもしれません。
JavaScriptは奥が深いです。
jQueryを使わないスムーススクロールのコードを紹介していますので、もしよければ参考にしてみてください。
One thought to “【JS・jQuery】ページ内リンクのスムーススクロールのベスト(2017年9月現在)な実装を考える”