今回は「ページ内リンクのスムーススクロール」をjQueryに頼らず実装してみました。

スクロールアニメーションはjQueryなしでは難しいとされている処理の一つですが、特に問題なく動作するようなコードが完成したので、メモに残しておきます。

目次

完成コードと動作デモ

まずは私が使用しているコードの紹介から。

スムーススクロール処理の本体部分を関数化しています。使い方やカスタマイズは後ほど。

スムースリンク用関数

function smoothLink(headH = 0) {
    const interval = 10;               //スクロール処理を繰り返す間隔
    const divisor = 8;                  //近づく割合(数値が大きいほどゆっくり近く)
    const range = (divisor / 2) + 1;    //どこまで近づけば処理を終了するか(無限ループにならないように divisor から算出)
    const links = document.querySelectorAll('a[href^="#"]');

    for (let i = 0; i < links.length; i++) {
        links[i].addEventListener('click', function (e) {
            e.preventDefault();
            let toY;
            let nowY = window.pageYOffset;                       //現在のスクロール値
            const href = e.target.getAttribute('href');          //href取得
            const target = document.querySelector(href);         //リンク先の要素(ターゲット)取得
            const targetRect = target.getBoundingClientRect();   //ターゲットの座標取得
            const targetY = targetRect.top + nowY - headH;        //現在のスクロール値 & ヘッダーの高さを踏まえた座標
            //スクロール終了まで繰り返す処理
            (function doScroll() {
                toY = nowY + Math.round((targetY - nowY) / divisor);  //次に移動する場所(近く割合は除数による。)
                window.scrollTo(0, toY);                              //スクロールさせる
                nowY = toY;                                           //nowY更新

                if (document.body.clientHeight - window.innerHeight < toY) {
                    //最下部にスクロールしても対象まで届かない場合は下限までスクロールして強制終了
                    window.scrollTo(0, document.body.clientHeight);
                    return;
                }
                if (toY >= targetY + range || toY <= targetY - range) {
                    //+-rangeの範囲内へ近くまで繰り返す
                    window.setTimeout(doScroll, interval);
                } else {
                    //+-range の範囲内にくれば正確な値へ移動して終了。
                    window.scrollTo(0, targetY);
                }
            })();
        });
    }
};

ES2015(ES6)で記述していますので、ES5でしかコードを書いていない人は注意してください。

実際の挙動の様子は以下のCodePenで確認してください。

See the Pen jQueryなしでページ内リンクをスムーススクロール by LOOS WEB STUDIO (@loos) on CodePen.

使い方と解説

まずは簡単な使い方と処理の流れを説明しておきます。詳しくはコードを眺めてください。

使い方

呼び出すだけです。

標準の使い方

//DOM読み込み後に呼び出す
smoothLink();

ヘッダーを上部に固定表示している場合は引数にヘッダーの高さを渡します

ヘッダーを上部に固定している場合

let headH = document.getElementById('header').offsetHeight;  //ヘッダーの高さを取得
document.body.style.marginTop = headH + "px";  //body上部にヘッダーの高さ分のマージン

smoothLink(headH);  //ヘッダーの高さを引数に渡して呼び出し

2行目の処理は人によって異なると思いますので各自のやり方でどうぞ。

重要なのはヘッダーの高さheadHを引数に渡すという点です。

先ほどのCodePenでもヘッダーを固定するパターンで使用しているので参考にしてみて下さい。

処理内容

ざっくりと処理の流れを示しておきます。

  1. href属性が#から始まるアンカータグにクリックイベントを登録
  2. クリックされると現在のスクロール量nowYを取得
  3. 現在の位置からのターゲット(リンク先)の絶対座標を取得
  4. 現在のスクロール量やヘッダーの高さをふまえ、最終的なスクロール位置targetYを計算
  5. 次のスクロール位置toY(現在の位置からターゲットまでの距離の 1/8(=divisor)近づいた点 の座標)を計算
  6. スクロールを実行
  7. この時、次のスクロール位置が一番下までスクロールしても届かない場合は、最下部までスクロールさせて処理を終了
  8. ターゲットの近く(targetY +- rangeの範囲) にくるまで、5~7の処理を 10=(interval)ミリ秒 ごとに繰り返し
  9. 十分近づいたら、最終的なスクロール位置(targetY)までスクロールさせて終了

といった感じです。

具体的なコードについては、繰り返し部分で変数宣言が必要ないようにできるだけ工夫はしたつもりですが、もっといいコードで実装できるかもしれません。

ちなみに、querySelectorAll('a[href^="#"]')の中身がよくわからない人は以下を参考にしてみて下さい。

[href^=#]とはなんぞや。ページ内リンクをスムーススクロールさせる時に記述しているあれ[href^=#]とはなんぞや。ページ内リンクをスムーススクロールさせる時に記述しているあれ

スクロール速度について

divisorintervalがスクロールの速度に関係します。

好きな値に調節してください。

rangeについて

どこまで近づけば処理を終了するかの範囲を決めるのがrangeです。

これがなぜdivisorを用いた式で計算されているかというと、次のスクロール位置toYの計算時に小数点以下四捨五入する部分があり、これが0.5未満になるとそれ以上値が変化せず無限に処理が続くためです。

そのような値を取らないようにrangeを定める必要があり、それはdivisorによって変化するので、式で計算しています。

カスタマイズ例

紹介したコードはあくまでもひとつの例にすぎません。

例えば、smoothLink()を呼び出すのが手間であれば即時実行関数にしてもいいでしょう。

let headH = /* 略 */
(function smoothLink(headH = 0) {
    /* 処理内容省略 */
})(headH);

というかそもそも、これらの一連の処理を関数にまとめる必要すら特にありません。

個人的にwebpackで外部モジュール化するために関数化しているだけです。


また、smoothLink内部で繰り返し実行しているdoScrollは、名前を付けなくてもarguments.calleeを使って自分自身を呼び出すこともできます。

ただの繰り返し処理に名前をつけたくない人はこちらに書き換えて下さい。

/* 略 */
    //スクロール終了まで繰り返す処理
    (function () {
        let me = arguments.callee;  //自分自身を変数に代入
        /* 略 */
        if (toY >= targetY + range || toY <= targetY - range) {
            //+-rangeの範囲内へ近くまで繰り返す
            window.setTimeout(me, interval);
        } else {
            /* 略 */
        }
    })();
/* 略 */
- Thank you for reading. -

あなたの1クリックが励みになります\( ̄ー ̄)/

コメント

コメントする

CAPTCHA


TOPへ Top