LAB

スムーススクロール

Written by EVOWORX

INDEX

説明

jQueryやライブラリを使用せずにイージングを伴ったページ内スクロールを実装します。

使用するライブラリ

なし

手順

手順1

まず初めにスクロール先要素のid属性とトリガーになるaタグのhref属性がそれぞれ対応するようにhtmlを記述します。下記の例では.menu内の各aタグのhref属性が各.sectionid属性と対応しており、仮にhref="#b"が指定されているaタグをクリックした場合はid="b"が指定されている.sectionまでイージングを伴ってスクロールします。

<nav class="nav">
    <ul class="menu">
        <li><a href="#a">A</a></li>
        <li><a href="#b">B</a></li>
        <li><a href="#c">C</a></li>
        <li><a href="#d">D</a></li>
    </ul>
</nav>
<main class="main">
    <section id="a" class="section">
        <h2>Section A</h2>
    </section>
    <section id="b" class="section">
        <h2>Section B</h2>
    </section>
    <section id="c" class="section">
        <h2>Section C</h2>
    </section>
    <section id="d" class="section">
        <h2>Section D</h2>
    </section>
</main>

手順2

次にJavaScript側で下記4点の定数を定義します。triggers以外の定数に代入する値は好みやサイトの仕様に合わせて適宜調整してください。

// ▼ イージング関数を指定(今回は easeInOut の関数を指定)
const easing = t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
// ▼ 持続時間を指定(600ミリ秒 = 0.6秒)
const duration = 600;
// ▼ 固定ヘッダーがある場合は高さを指定
const headerHeight = 0;
// ▼ href属性の値が # から始まる全てのaタグを取得
const triggers = document.querySelectorAll('a[href^="#"]');

手順3

手順2で定義した定数を元にして、クリックした際にリンク先までスクロールするイベントを各aタグに付与します。それぞれの部分で何を記述しているかはコード内のコメントで説明しているので、よければ参考にしてください。

triggers.forEach( item => {
    // ▼ アンカーボタンがクリックされた時の処理
    item.addEventListener('click', e => {
        // ▼ aタグのデフォルトの挙動&イベントの伝播をキャンセル
        e.preventDefault();
        e.stopPropagation();
        // ▼ aタグのhref属性取得
        const href = item.getAttribute('href');
        // ▼ 現在のスクロール位置を取得
        const currentPosition = document.documentElement.scrollTop || document.body.scrollTop;
        // ▼ aタグのhref属性に指定されているスクロール先の要素を取得
        const targetElement = document.getElementById(href.replace('#', ''));
        // ▼ スクロール先の要素が存在する場合のみ以下のブロック内処理を実行
        if (targetElement) {
            // ▼ スクロール先の要素の現在の位置を取得
            const targetPosition = window.pageYOffset + targetElement.getBoundingClientRect().top - headerHeight;
            // ▼ ページが表示されてからaタグがクリックされるまでの時間をミリ秒で取得
            const startTime = window.performance.now();
            // ▼ スクロールが完了するまでリフレッシュレートに合わせて定期的に実行される関数
            // ▼ 引数の nowTime にはページが表示されてから各 loop() が呼び出されるまでの時間がミリ秒で入る
            const loop = nowTime => {
                // ▼ loop() が呼び出されるまでの時間 - aタグがクリックされるまでの時間
                const time = nowTime - startTime;
                // ▼ time の数値を手順2で定義した duration で割る
                const normalizedTime = time / duration;

                if (normalizedTime < 1) {
                    // ▼ normalizedTime が1より少ない場合は下記のスクロール処理を実行&loop() を呼び出し
                    window.scrollTo(0, currentPosition + ((targetPosition - currentPosition) * easing(normalizedTime)));
                    window.requestAnimationFrame(loop);
                } else {
                    // ▼ normalizedTime が1以上になった場合はスクロール処理を終了させる
                    window.scrollTo(0, targetPosition);
                }
            };

            // ▼ loop() の初回呼び出し
            window.requestAnimationFrame(loop);
        }
    });
});

サンプル

参考文献