【PR】を含みます。

フロントエンド

【JavaScript】ライブラリ未使用で慣性スクロールを実装する方法

JavaScript ライブラリ未使用で慣性スクロールを実装する方法

Webサイトのスクロール体験をより滑らかにしたいと考えたことはありませんか?

本記事では、外部ライブラリを使わずに純粋なJavaScriptだけで「慣性スクロール」を実装する方法を、サンプルコードとともに詳しく解説します。

transformfixed要素の注意点、ページ内リンクや動的な高さ変化への対応方法まで、実践的なノウハウをまとめました。

【実装コード】requestAnimationFrame()メソッドで慣性スクロールを実装

HTML
Copy
<div class="inertia-scroll">
  <p>ここにコンテンツが入ります。</p>
</div>
JavaScript
Copy
(function() {
  // 慣性スクロールを適用したい要素を指定
  const scrollElements = document.querySelector('.inertia-scroll');
  // 要素が存在しない場合は処理を中止
  if (!scrollElements) {
    return;
  }
  let currentScroll = window.scrollY;  // 現在のスクロール位置
  let targetScroll = 0;                // 目標スクロール位置
  const ease = 0.1;                    // 慣性(0に近いほどスムーズになる)
  const threshold = 0.1;               // 差がこの値以下ならリセット
  // 要素にスタイルを適用
  scrollElements.style.cssText =
    'position: fixed;' +
    'width: 100%;' +
    'top: 0;' +
    'left: 0;';
  // bodyに高さを設定
  document.body.style.height = scrollElements.clientHeight + 'px';
  // ページ更新対策
  window.addEventListener('beforeunload', function() {
    // ページ更新前にwindow.scrollYの値をsessionStorageに格納
    sessionStorage.setItem('scrollY', window.scrollY);
  });
  let isReloaded = false;
  const entries = performance.getEntriesByType('navigation');
  if (entries[0] && entries[0].type === 'reload') {
    isReloaded = true;
  }
  // ページ更新が更新された場合、sessionStorageからwindow.scrollYの値を取得
  if (isReloaded && sessionStorage.getItem('scrollY')) {
    currentScroll = Number(sessionStorage.getItem('scrollY'));
    targetScroll = currentScroll;
    window.scrollTo({
      top: currentScroll,
      left: 0,
      behavior: 'auto'
    });
  }
  // アニメーションを開始
  updateScroll();
  // スクロール位置を更新する関数
  function updateScroll() {
    if (Math.abs(targetScroll - currentScroll) < threshold) {
      // 差が閾値以下ならリセット
      currentScroll = targetScroll;
    } else {
      // 現在のスクロール位置を目標位置に近づける
      currentScroll += (targetScroll - currentScroll) * ease;
    }
    // 位置を更新
    scrollElements.style.transform = 'translate3d(0, -' + currentScroll + 'px, 0)';
    // アニメーションフレームを設定
    requestAnimationFrame(updateScroll);
  }
  // スクロールイベントで目標スクロール位置を更新
  window.addEventListener('scroll', function() {
    targetScroll = window.scrollY;
  });
})();

注意事項

  1. 固定ヘッダー等の追従要素(position: fixedを指定している要素)は、<div class="inertia-scroll"></div>の外に記述する必要があります。

    慣性スクロール実現するため、<div class="inertia-scroll"></div>transformプロパティを設定してます。

    ※CSSの仕様上、transformプロパティが指定された要素にposition: fixedを持つ要素を指定した場合、ビューポート(画面全体)を基準とするのではなく、transformを持つ親要素を基準に固定されます。

  2. <div class="inertia-scroll"></div>にページ内リンクがある場合、機能しない場合があります。

    ページ内リンクが機能しないときの対策方法は以下にまとめてます。

  3. <div class="inertia-scroll"></div>にアコーディオンメニュー等で動的に高さが変化する要素がある場合、bodyの高さを調整する必要があります。

    また、動的に要素を追加したり、非同期で要素を追加する場合もbodyの高さを調整する必要があります。

    動的に高さが変わるコンテンツがあるときの対応方法は以下にまとめてます。

  4. ほとんどのモダンブラウザに対応してますが、古いブラウザで動作しない場合があります。

<div class="inertia-scroll"></div>position: fixedを指定しているため、通常の方法ではページ内リンクが機能しない状態になっています。

別途JavaScriptで要素の位置を取得して、スクロールさせるという処理が必要になります。

実装コード

JavaScript
Copy
(function() {
  // 慣性スクロールを適用したい要素を指定
  const scrollElements = document.querySelector('.inertia-scroll');
  // 要素が存在しない場合は処理を中止
  if (!scrollElements) {
    return;
  }
  let currentScroll = window.scrollY;  // 現在のスクロール位置
  let targetScroll = 0;                // 目標スクロール位置
  const ease = 0.1;                    // 慣性(0に近いほどスムーズになる)
  const threshold = 0.1;               // 差がこの値以下ならリセット
  // 要素にスタイルを適用
  scrollElements.style.cssText =
    'position: fixed;' +
    'width: 100%;' +
    'top: 0;' +
    'left: 0;';
  // bodyに高さを設定
  document.body.style.height = scrollElements.clientHeight + 'px';
  // ページ更新対策
  window.addEventListener('beforeunload', function() {
    // ページ更新前にwindow.scrollYの値をsessionStorageに格納
    sessionStorage.setItem('scrollY', window.scrollY);
  });
  let isReloaded = false;
  const entries = performance.getEntriesByType('navigation');
  if (entries[0] && entries[0].type === 'reload') {
    isReloaded = true;
  }
  // ページ更新が更新された場合、sessionStorageからwindow.scrollYの値を取得
  if (isReloaded && sessionStorage.getItem('scrollY')) {
    currentScroll = Number(sessionStorage.getItem('scrollY'));
    targetScroll = currentScroll;
    window.scrollTo({
      top: currentScroll,
      left: 0,
      behavior: 'auto'
    });
  }
  // アニメーションを開始
  updateScroll();
  // スクロール位置を更新する関数
  function updateScroll() {
    if (Math.abs(targetScroll - currentScroll) < threshold) {
      // 差が閾値以下ならリセット
      currentScroll = targetScroll;
    } else {
      // 現在のスクロール位置を目標位置に近づける
      currentScroll += (targetScroll - currentScroll) * ease;
    }
    // 位置を更新
    scrollElements.style.transform = 'translate3d(0, -' + currentScroll + 'px, 0)';
    // アニメーションフレームを設定
    requestAnimationFrame(updateScroll);
  }
  // スクロールイベントで目標スクロール位置を更新
  window.addEventListener('scroll', function() {
    targetScroll = window.scrollY;
  });
  // すべてのリンクにイベントを追加
  document.querySelectorAll('a[href^="#"]').forEach(function (link) {
    link.addEventListener('click', function (event) {
      event.preventDefault(); // デフォルトの動作を無効化
      const adjust = 0; // スクロール位置の調整
      const speed = 400; // スクロール速度(ms)
      const href = this.getAttribute('href'); // href属性を取得
      const target = href === '#' || href === '' ? document.documentElement : document.querySelector(href);
      if (target) {
        const position = target.offsetTop - adjust;
        // スムーズスクロール対応か確認
        if ('scrollBehavior' in document.documentElement.style) {
          // スムーズスクロールを使用
          window.scrollTo({ top: position, behavior: 'smooth' });
        } else {
          // フォールバック:アニメーションなしで移動
          window.scrollTo(0, position);
        }
      }
    });
  });
})();

動的に高さが変わるコンテンツがあるときの対応方法

<div class="inertia-scroll"></div>に高さが動的に変化する要素がある場合、高さが変化するたびにbodyの高さを更新する必要があります。

高さの更新方法は様々な方法がありますが、今回はResizeObserver<div class="inertia-scroll"></div>の高さの変化を監視して、変化があればbodyの高さを更新する方法を採用しました。

実装コード

JavaScript
Copy
(function() {
  // 慣性スクロールを適用したい要素を指定
  const scrollElements = document.querySelector('.inertia-scroll');
  // 要素が存在しない場合は処理を中止
  if (!scrollElements) {
    return;
  }
  let currentScroll = window.scrollY;  // 現在のスクロール位置
  let targetScroll = 0;                // 目標スクロール位置
  const ease = 0.1;                    // 慣性(0に近いほどスムーズになる)
  const threshold = 0.1;               // 差がこの値以下ならリセット
  // 要素にスタイルを適用
  scrollElements.style.cssText =
    'position: fixed;' +
    'width: 100%;' +
    'top: 0;' +
    'left: 0;';
  // bodyに高さを設定
  document.body.style.height = scrollElements.clientHeight + 'px';
  // ページ更新対策
  window.addEventListener('beforeunload', function() {
    // ページ更新前にwindow.scrollYの値をsessionStorageに格納
    sessionStorage.setItem('scrollY', window.scrollY);
  });
  let isReloaded = false;
  const entries = performance.getEntriesByType('navigation');
  if (entries[0] && entries[0].type === 'reload') {
    isReloaded = true;
  }
  // ページ更新が更新された場合、sessionStorageからwindow.scrollYの値を取得
  if (isReloaded && sessionStorage.getItem('scrollY')) {
    currentScroll = Number(sessionStorage.getItem('scrollY'));
    targetScroll = currentScroll;
    window.scrollTo({
      top: currentScroll,
      left: 0,
      behavior: 'auto'
    });
  }
  // アニメーションを開始
  updateScroll();
  // スクロール位置を更新する関数
  function updateScroll() {
    if (Math.abs(targetScroll - currentScroll) < threshold) {
      // 差が閾値以下ならリセット
      currentScroll = targetScroll;
    } else {
      // 現在のスクロール位置を目標位置に近づける
      currentScroll += (targetScroll - currentScroll) * ease;
    }
    // 位置を更新
    scrollElements.style.transform = 'translate3d(0, -' + currentScroll + 'px, 0)';
    // アニメーションフレームを設定
    requestAnimationFrame(updateScroll);
  }
  // スクロールイベントで目標スクロール位置を更新
  window.addEventListener('scroll', function() {
    targetScroll = window.scrollY;
  });
  // 監視対象の要素を取得
  const targetElement = document.querySelector('.inertia-scroll');
  // ResizeObserverのインスタンスを作成
  const resizeObserver = new ResizeObserver((entries) => {
    entries.forEach((entry) => {
      document.body.style.height = entry.contentRect.height + 'px';
    });
  });
  // 要素の監視を開始
  resizeObserver.observe(targetElement);
})();

まとめ

本記事では、JavaScriptのみで慣性スクロールを実装する方法について、基本から応用まで詳しく解説しました。

transformfixed要素の注意点、ページ内リンクや動的な高さ変化への対応など、実際の運用でつまずきやすいポイントもカバーしています。

慣性スクロールを導入し、Webサイト雰囲気に合わせることでユーザー体験をより向上させることができます。

-フロントエンド
-