• 作成日:

【JavaScript】「ページ内スクロール」と「トップに戻る」を同時に実装する方法

【JavaScript】「ページ内スクロール」と「トップに戻る」を同時に実装する方法

今回は「ページ内スクロール」と「トップへ戻る」を同時に実装する方法を説明します。

前提条件は以下

  • jQueryは使わない
  • リンクが#のみだと画面トップまで戻る
  • リンクが設定されていても、スクロール対象が見つからないときはアラート
  • 固定ヘッダーがある場合、その高さ分、スクロール位置を調整できる(PCとSPで切り替え可能)

説明環境は以下

  • macOS Monterey 12.5
  • Visual Studio Code v1.70.1
この記事の目次

ページ内スクロールとトップに戻るを同時に実装する

最近ではスムーススクロールはCSSだけで実装可能です。以下のようにhtml要素にscroll-behaviorを指定すれば実装できます。固定ヘッダーがある場合もscroll-margin-topで高さ分、スクロール位置が調整できます。

html{
  scroll-behavior: smooth;
  scroll-margin-top: 60px; //固定ヘッダーの高さ
}

ただし、別ページの特定の場所に飛ぶときも、まずは別ページの画面トップに飛んでからスムーススクロールで特定の場所に移動する動きになるので、個人的にはJavaScriptで実装するようにしています。

HTMLで書いてること

以下はデモページのHTMLです。

<div class="p-scroll">
  <nav class="p-scroll__nav">
    <ul class="p-scroll__list">
      <li class="p-scroll__item"><a class="p-scroll__link" href="#section01">section1</a></li>
      <li class="p-scroll__item"><a class="p-scroll__link" href="#section02">section2</a></li>
      <li class="p-scroll__item"><a class="p-scroll__link" href="#section03">section3</a></li>
      <li class="p-scroll__item"><a class="p-scroll__link" href="#section04">section4</a></li>
      <li class="p-scroll__item"><a class="p-scroll__link" href="#section05">section5</a></li>
    </ul>
  </nav>
  <section class="p-scroll__section p-scroll__section--01" id="section01"><span class="p-scroll__section-text">SECTION1</span></section>
  <section class="p-scroll__section p-scroll__section--02" id="section02"><span class="p-scroll__section-text">SECTION2</span></section>
  <section class="p-scroll__section p-scroll__section--03" id="section03"><span class="p-scroll__section-text">SECTION3</span></section>
  <section class="p-scroll__section p-scroll__section--04" id="section04"><span class="p-scroll__section-text">SECTION4</span></section>
</div>
<div class="p-scroll__top"><a class="p-scroll__top-link" href="#">↑</a></div>
<div class="p-scroll__footer"><span class="p-scroll__footer-text">FOOTER</span></div>
</div>

ナビゲーションをクリックすれば、各sectionにスムーススクロールします。ナビゲーションにあるsection5については、section5自体が存在しないので「移動先が見当たりません」とalertされます。

デモページ右下にある上向き矢印をクリックすれば、ページ上までスムーススクロールします。このように「トップに戻る」を機能させる場合は、リンクには#(ハッシュタグ)のみ設定してください。

JavaScriptで書くこと

以下はデモページのJavaScriptです。

const smoothScroll = (gapPC = 0, gapSP = 0) => {
  const mediaSp = window.matchMedia('(max-width: 560px)');
  let gap;
  if (mediaSp.matches) {
    gap = gapSP;
  } else {
    gap = gapPC;
  }

  const positionSet = (elem, _gap) => {
    const targetElement = document.getElementById(elem.replace('#', ''));
    if (!targetElement) {
      return undefined;
    }
    const rect = targetElement.getBoundingClientRect().top;
    const offset = window.pageYOffset;
    const target = rect + offset - _gap;
    return target;
  };

  // URLのハッシュ部分(#以降)を取得
  const urlHash = window.location.hash;
  // URLにハッシュ部分があれば…
  if (urlHash) {
    setTimeout(() => {
      // スクロール先の要素が見つからない場合は…
      if (positionSet(urlHash, gap) === undefined) {
        // アラートを出す
        alert('移動先が見当たりません');
        // スクロール先の要素があれば…
      } else {
        window.scrollTo({
          top: positionSet(urlHash, gap), // ハッシュ値と固定headerの余白分から移動距離を取得
          // 別ページのときはスムーススクロールせずに移動
        });
      }
    }, 0);
    // URLにハッシュ部分が無ければ…
  } else {
    // a要素でリンク先に#が設定されている要素を全て取得
    const smoothScrollTrigger = document.querySelectorAll('a[href^="#"]');
    // もし要素がなければ処理終了
    if (!smoothScrollTrigger.length) return;
    // 取得した要素の数だけループ
    for (let i = 0; i < smoothScrollTrigger.length; i = i + 1 || 0) {
      // 要素1つ1つにクリックイベントを設定
      smoothScrollTrigger[i].addEventListener('click', (e) => {
        // クリックイベントの初期動作を停止
        e.preventDefault();
        // 要素からhrefに設定されている値を取得
        const href = smoothScrollTrigger[i].getAttribute('href');
        let targetPosition;
        // hrefの値が#のみなら…
        if (href === '#') {
          // トップに戻すため0を設定
          targetPosition = 0;
          // スクロール先が見つからない場合は…
        } else if (positionSet(href, gap) === undefined) {
          // アラートを出す
          alert('移動先が見当たりません');
          /// スクロール先の要素があれば…
        } else {
          targetPosition = positionSet(href, gap); // ハッシュ値と固定headerの余白分から移動距離を取得
        }

        window.scrollTo({
          top: targetPosition, // 移動距離を設定
          behavior: 'smooth', // ページ内ではスムーススクロールを設定
        });
      });
    }
  }
};

smoothScroll(); //smoothScrollを実行

固定ヘッダーの高さがPC表示では60pxだった場合、以下のように書くと60px分の位置を調整して移動します。

smoothScroll(60); //60px分、位置を調整して移動する

固定ヘッダーの高さがスマホ表示では30px、PC表示では60pxの場合は以下です。

smoothScroll(60,30); //PCは60px、SPは30px分、調整して移動する

スマホ表示の判定はmatchMediaを使っています。今回は560px以下をスマホだと判定しているので、お好みでここの数値は変更してください、

  const mediaSp = window.matchMedia('(max-width: 560px)'); //560px以下の場合
  let gap;
  if (mediaSp.matches) {
    gap = gapSP;
  } else {
    gap = gapPC;
  }

さいごに

今回はJavaScriptで「ページ内スクロール」と「トップに戻る」を同時に実装する方法を説明しました。CSSだけでスムーススクロールを実装した場合と比べて、非常に手間がかかるのがわかります。

それでもCSSのscroll-behaviorのように、別ページの特定の場所までスムーススクロールで移動するのは、読み手のストレスになりうるので、JavaScriptで実装することをオススメします。