スムーススクロールをJavaScriptでつくるよくある方法[window.scrollTo, getBoundingClientRect ほか]

スムーススクロールをJavaScriptでつくるよくある方法[window.scrollTo, getBoundingClientRect ほか]
前回の記事では、ページ内で、スムーススクロールを実装した時に
1行のcssのみで位置を調整できるプロパティ、
scroll-margin, scroll-padding について書きました。

その記事の中でも出てきたとおり、今回は
スムーススクロールをJavaScriptでつくるよくある方法
について書いていきます。

結論この方法ができれば、前回の scroll-margin, scroll-padding の上位互換になりうる方法です。

それでは早速、スムーススクロールをJavaScriptでつくるよくある方法 について、次のセクションより書いていきたいと思います。


まずは結論から。さっそく、よくある感じのスムーススクロールについてご紹介

さっそく、よくある感じのスムーススクロールについてご紹介します。
scrollTo, getBoundingClientRect(), scrollY, を使用したスムーススクロールになります。

<script>
const triggerContainer = document.getElementById('headerMenu');
const offsetNumber = 100;
triggerContainer.addEventListener('click', function(e) {
  if (e.target.classList.contains('header-menu-list-link')) {
    e.preventDefault();
        
    const triggerLinkID = e.target.getAttribute('href').slice(1);
    const targetLinkClientRect = document.getElementById(triggerLinkID).getBoundingClientRect().top;
    const nowOffsetTop = Math.round(window.scrollY);
    const scrollPoint = targetLinkClientRect + nowOffsetTop - offsetNumber;

    window.scrollTo({
      top: scrollPoint,
      behavior: 'smooth'
    });
  }
});
</script>

上記は、ヘッダ内にあるグローバルメニューを押すと、各メニューに対応した項目に遷移するという挙動をするスムーススクロールの例になります。
具体的には、まず
2行目で、ヘッダ内のメニュー項目を包括する親要素を取得。変数 triggerContainer に格納。
4行目で、取得した要素に対して、clickイベントの監視を行っています。
5行目にあるように、もしクリックされた対象に header-menu-list-link というclassが付与されていれば、ということを条件に処理を実行しています。
6行目の preventDefault(); を記述することで、メニューのaタグの本来の挙動(クリックすると遷移する)を解除。
8行目にあるように、クリックされた箇所の href 属性を取得。#firstSection, #secondSection の様な形でクリックした箇所のhrefの値が帰ってくるので、先頭の # を削除すべく、slice() を実行しています。
9行目は、クリックされたメニューに対応する、遷移先のセクションをid名で取得。
getBoundingClientRect().top として、遷移先のセクションのブラウザ(Viewport)の頭からのy座標をpx値で取得しています。
10行目では、現在のページスクロール量を Math.round(window.scrollY) として、整数値で取得。
11行目で、それら取得した各項目を計算して、クリックされたメニューに対応する、遷移先のy座標を算出しています。
計算項目としては、
targetLinkClientRect + nowOffsetTop – offsetNumber となっていますが、左から
(遷移先のセクションのy座標) + (現在のページスクロール量) – (追従ヘッダがある場合を考慮してずらすpx値)
となります。
「追従ヘッダがある場合を考慮してずらすpx値」は、
3行目で定義されている変数、offsetNumber の値になります。この例では100pxを指定しています。

これらの設定をもとに、
13行目の window.scrollTo に値を渡してスムーススクロールを実行させている、というわけです。
window.scrollTo の設定項目は、
top には 11行目 で計算した遷移先の座標の値。
behavior は、遷移の際の挙動を指定しています。(この場合はスムーススクロールさせたいので、’smooth’ と設定)

以上が、よくある感じのスムーススクロールとなります。

htmlとcssの基本構造

上のセクションではまず、よくある感じのスムーススクロールのJavaScript側の記述を書きましたが、
そこで出てきた「ヘッダ内にあるグローバルメニュー」、「ヘッダ内のメニュー項目」、「追従ヘッダ」などのhtml構造は、例えば次の様なものになります。
1〜12行目までがヘッダ。14行目〜末尾までが、ヘッダ内のメニューから移動する遷移先です。

<header class="header-container">
  <h1>なんかすごいロゴ</h1>
  <nav>
    <ul id="headerMenu" class="header-menu-list">
      <li class="header-menu-list-item"><a class="header-menu-list-link" href="#firstSection">セクション1</a></li>
      <li class="header-menu-list-item"><a class="header-menu-list-link" href="#secondSection">セクション2</a></li>
      <li class="header-menu-list-item"><a class="header-menu-list-link" href="#thirdSection">セクション3</a></li>
      <li class="header-menu-list-item"><a class="header-menu-list-link" href="#fourthSection">セクション4</a></li>
      <li class="header-menu-list-item"><a class="header-menu-list-link" href="#fifthSection">セクション5</a></li>
    </ul>
  </nav>
</header>

<div class="main-container">
  <div id="firstSection" class="target-section">要素1</div>
  <div id="secondSection" class="target-section">要素2</div>
  <div id="thirdSection" class="target-section">要素3</div>
  <div id="fourthSection" class="target-section">要素4</div>
  <div id="fifthSection" class="target-section">要素5</div>   
</div>

上記の基本的なcssは次の様な感じです。サンプルなので、特段これといった装飾はしておらず、必要最低限のcssです。

body {
  height: 10000px;
}
.header-container {
  align-items: center;
  background: #fff;
  box-sizing: border-box;
  display: flex;
  height: 80px;
  justify-content: space-between;
  left: 0;
  padding: .5rem 1rem;
  position: static;
  top: -80px;
  transition: top .5s;
  width: 100%;
  z-index: 100;
}
.header-container.is-active {
  box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
  left: 0;
  position: fixed;
  top: 0;
}
.header-menu-list {
  display: flex;
  gap: 0 1rem;
  list-style-type: none;
}
.main-container {
  height: 100%;
}
.main-container.is-active {
  height: 100%;
  padding: 80px 0 0;
}
.target-section {
  margin: 10rem auto 0;
  width: 1200px;
}
.target-section:first-of-type {
  margin: 20rem auto 0;
}

MDN内の解説など

この記事で登場した、用語などに関するMDN内の詳しい解説は次のとおりとなります。
[getBoundingClientRect()]
Element: getBoundingClientRect() メソッド – Web API | MDN
遷移先のセクションのブラウザ(Viewport)の頭からのy座標をpx値で取得する際に使用。

[window.scrollY]
window.scrollY – Web API | MDN
現在のページスクロール量を Math.round(window.scrollY) として、整数値で取得する際に使用。

[window.scrollTo]
window.scrollTo – Web API | MDN
設定をもとに値を渡して、スムーススクロールを実行させるのに使用しています。

こちらの「MDN」についてですが、何かWeb技術で知りたい事がある時、ひとまず読んでおけば基本的な知見が得られるとても良質なリファレンスサイトです(Mozilla ≒ Webブラウザの挙動そのものの仕様を決めてる人達、が運営してるので当然と言えば当然ですが)。ぜひ一読することをおすすめします。

この記事のまとめ

今回は、Webサイトでよく見かけるタイプの、
JavaScriptによるスムーススクロールについて記事を書きました。

記事の冒頭でも書きましたが、前回の記事でご紹介した
scroll-margin, scroll-padding によるアプローチの上位互換とも言える方法になります。
(今回のこの方法が使えるサイトであれば、無理に scroll-margin, scroll-padding によってスムーススクロールの遷移先の位置を調整する必要がない、という意味です)

また、よく見かけると書いている通り、実際この書き方によるスムーススクロールはよく見かけます。
覚えておくと、スムーススクロールを実装したいなという時に役に立つのではないかと思います。

この記事が皆さんのより良いWeb制作体験につながれば、嬉しく思います。

※この記事は内容の品質向上を目的に、随時更新を行う場合があります。

この記事をシェアする: