【jQuery無し】素のJSでページ内リンクをスムーススクロールさせる方法

ランディングページのナビゲーションやブログページの目次など、ページ内の特定の場所にリンクを設定することは多いですよね。

ページ内リンクは、デフォルトだとリンク先の場所まで一瞬でジャンプしてしまいますが、滑らかにスクロールさせたい場面が多いかと思います。

今回の記事ではJavaScriptを使用して、ページ内リンクをスムーズにスクロールさせる方法を紹介していきます。

  • ページ内リンクをクリックすると、対象位置までスムーズにスクロールする
  • jQueryやライブラリは使用せず、素のJavaScriptで実装
  • IE11やSafariにも対応させる

jQueryやプラグインは一切使用しないので、軽量かつカスタマイズも容易です。

ページ内リンクのスムーススクロールを実装したい方は、ぜひ参考にしてみてください。

目次

ページ内リンクのスムーススクロールの実装手順

ここからは実際の実装手順を説明していきます。

  • HTML上でページ内リンクを設定
  • JavaScriptでスムーススクロールの動作を実装
  • IEやSafari等にも対応させるためにpolyfillを読み込む

上記の順番で解説していきますね。

手順1. HTMLでページ内リンクを設定

まずは通常のページ内リンクを設定していきます。

ページ内リンクは、

  1. ターゲットの要素にidを付ける
  2. aタグのhref属性にターゲットの要素のidを指定

という手順で実装可能ですね。

<a href="#target">ターゲットの要素へスクロール</a>

<div id="target">ターゲットの要素</div>

これでaタグをクリックすると、targetというidがついたdivまでジャンプすることができます。

これだけでもページ内リンクは動きますが、画面が一瞬で切り替わってしまいますね…。

これをスルスルと滑らかにスクロールさせるために、次のJavaScriptコードを記入しましょう。

手順2. JavaScriptでスムーススクロールの動作を実装

window.addEventListener('DOMContentLoaded', () => {
  const anchorLinks = document.querySelectorAll('a[href^="#"]');
  const anchorLinksArr = Array.prototype.slice.call(anchorLinks);

  anchorLinksArr.forEach(link => {
    link.addEventListener('click', e => {
      e.preventDefault();
      const targetId = link.hash;
      const targetElement = document.querySelector(targetId);
      const targetOffsetTop = window.pageYOffset + targetElement.getBoundingClientRect().top;
      window.scrollTo({
        top: targetOffsetTop,
        behavior: "smooth"
      });
    });
  });
});

JavaScriptのファイルを作成し、こちらのコードを貼り付けます。

ES6以降のコードを含むので、必要に応じてBabelなどのツールでトランスパイルしてください。

これで多くのブラウザではスムーススクロールが正常に動作しますが、IEやSafariではうまく動きません。

次で対処法を説明していきます。

手順3. クロスブラウザ対応のためにpolyfillを読み込む

SafariとIEでは、コードの中で使用しているscrollToが対応されていません。

参照:https://developer.mozilla.org/ja/docs/Web/API/ScrollToOptions

これに対処するために、polyfillを読み込む必要があります。

  • polyfillファイルを読み込む
  • npmでパッケージをインストールする

これらの2通りの方法があるので、それぞれ説明していきますね。

polyfill導入方法1. polyfillファイルを読み込む

Smooth Scroll behavior polyfillは、Github上で公開されています。

上のリンクからsmoothscroll.jsを読み込むか、こちらからソースをコピーして使用してください。

JSファイルの読み込み順は、必ずpolyfillが上、実際のJSコードを書いたファイルを下にしてくださいね。

  <script src="smoothscroll.js"></script><!-- polyfillファイル -->
  <script src="main.js"></script><!-- スムーススクロール実装ファイル -->
</body>
</html>

polyfill導入方法2. npmでpolyfillパッケージをインストールする

npmを使用している方は、以下のようにしてパッケージをインストールしてください。

npm install smoothscroll-polyfill --save

インストールしたら、以下のコードを追加するだけでOKです。

import smoothscroll from 'smoothscroll-polyfill';
smoothscroll.polyfill();

これで、SafariやIEでも正常に動作するようになりました。

ページ内リンクのスムーススクロールの実装手順まとめ

  1. HTMLで通常通りアンカーリンクを設定する
  2. JavaScriptのコードを貼り付ける
  3. polyfillを読み込む

以上の手順で、ページ内リンクのスムーススクロールを実装できたかと思います。

ここからはコードの中身を理解したい・カスタマイズしたいという方に向けて、詳しい解説をしていきますね。

ページ内リンクのスムーススクロールさせるJavaScriptコードの解説

window.addEventListener('DOMContentLoaded', () => {
  const anchorLinks = document.querySelectorAll('a[href^="#"]')
  const anchorLinksArr = Array.prototype.slice.call(anchorLinks);

  anchorLinksArr.forEach(link => {
    link.addEventListener('click', e => {
      e.preventDefault();
      const targetId = link.hash;
      const targetElement = document.querySelector(targetId);
      const targetOffsetTop = window.pageYOffset + targetElement.getBoundingClientRect().top;
      
      window.scrollTo({
        top: targetOffsetTop,
        behavior: "smooth"
      });
    });
  });
});

こちらがJSコードの全体像ですが、少し長いので分解しつつ解説していきます。

ページ内リンクが設定されているaタグをすべて取得する

const anchorLinks = document.querySelectorAll('a[href^="#"]')
const anchorLinksArr = Array.prototype.slice.call(anchorLinks);

まず最初の行で、ページ内リンクを指定している全てのaタグquerySelectorAllで取得します。

セレクターの部分にはa[href^="#"]が入っていますが、これはhref属性の値が’#’から始まるaタグを指定しています。

<!-- a[href^="#"]の条件に当てはまるもの -->
<a href="#target1">ページ内リンク</a>
<a href="#target2">ページ内リンク</a>

<!-- a[href^="#"]の条件に当てはまらないもの -->
<a href="https://google.com">外部リンク</a>

ページ内リンクのhref属性の値は必ず#がつくので、この指定をすることでページ内リンクが設定されたaタグのみを選択することができますね。

次の行ではIE対策として、取得したNodeListをArrayに変換する処理をしています。

こうすることで、querySelectorAllで取得したNodeListに対してforEachメソッドを使えるようになりますね。

ページ内リンクひとつひとつにクリックイベントを付与する

anchorLinksArr.forEach(link => {
  link.addEventListener('click', e => {
  // クリックすると行われる処理
  });
});

anchorLinksArrには、最初に取得したaタグの配列が格納されています。

このaタグ一つ一つを取り出してクリックイベントを付与するために、forEachメソッドを使用します。

linkにはそれぞれのaタグが入っているので、addEventListenerでクリックイベントを登録します。

このaタグをクリックするとスムーススクロールさせたいので、中身を作っていきましょう。

クリックすると特定の要素までスムーススクロールさせる

// ①ブラウザのデフォルトの挙動を止める
e.preventDefault();
// ②ターゲットの要素を取得する
const targetId = link.hash;
const targetElement = document.querySelector(targetId);
// ③ターゲットの要素のページ上端からの位置座標を取得
const targetOffsetTop = window.pageYOffset + targetElement.getBoundingClientRect().top;
// ④ターゲットの要素の位置までスムーススクロールさせる
window.scrollTo({
  top: targetOffsetTop,
  behavior: "smooth"
});

クリックすると、これらの処理が行われます。

一つずつ見ていきましょう。

①ブラウザのデフォルトの挙動を止める

e.preventDefault();

ページ内リンクをクリックすると、本来はスルスルとスクロールするのではなく、画面が一瞬で切り替わりますよね。

これはブラウザのデフォルトの挙動なのですが、今回の目的はスムーススクロールの実装なので、この機能は邪魔ですね。

なのでpreventDefault()を使用して、デフォルトの動作を無効にしてしまいましょう。

この記述をすることで、aタグをクリックしてもリンク先に飛ばなくなりました。

これで下準備はできたので、次でスムーススクロールの動きを自作していきます。

②各aタグのターゲットの要素を取得する

const targetId = link.hash;
const targetElement = document.querySelector(targetId);

ここでは、各aタグのターゲット要素を取得します。

以下のHTMLを例として見てみましょう。

<a href="#target">リンク</a>

<div id="target">ターゲットの要素</div>

リンクが設定されたaタグと、ターゲットのdiv要素がありますね。

このaタグをクリックすると、上のJavaScriptの変数の中身は以下のようになります。

const targetId = '#target';
const targetElement = document.querySelector('#target');

hashプロパティは要素のリンクのハッシュ部分を取得するので、targetId には#targetが入ります。

これをquerySelectorのセレクタ部分に指定することで、ターゲット要素を取得することができますね。

③ターゲットの要素のページ上端からの位置座標を取得

const targetOffsetTop = window.pageYOffset + targetElement.getBoundingClientRect().top;

ターゲット要素を取得できたので、次はページの上端からターゲット要素の上端までの距離を取得します。

この計算をするには、以下の2つの数値が必要になります。

  1. ページ上端から現在位置までの距離
  2. 現在位置からターゲットの要素までの距離

window.pageYOffsetは、垂直方向のスクロール量を返すプロパティですね。

垂直方向にスクロールした量 = ページ上の垂直方向の位置になるので、この数値をそのまま使います。

getBoundingClientRect().topは、ウィンドウの上端からターゲット要素の上端までの距離を返すプロパティです。

これらを足すことで、ターゲット要素の垂直方向の位置を取得できますね。

④ターゲットの要素の位置までスムーススクロールさせる

window.scrollTo({
  top: targetOffsetTop,
  behavior: "smooth"
});

scrollToは、指定した座標までスクロールさせることができるメソッドです。

このメソッドには、以下の2つのオプションを指定します。

  • top: ページ垂直線上のどの地点にスクロールするかをpxで指定
  • behavior: スクロールの挙動を指定

topには、③で取得したターゲット要素の位置座標を渡します。

behaviorにはsmoothと指定することで、一瞬で切り替わるのではなくスルスルと滑らかにスクロールさせることができます。

これでページ内リンクのスムーススクロールが完成しました!

【カスタマイズ編】ページ内リンクのスムーススクロールのカスタマイズ2選

ここでは実案件でよくあるケースに対応するために、2つほどカスタマイズ方法を紹介しておきます。

  • 画面上部に追従するヘッダーがある場合
  • 特定のページ内リンクにのみスムーススクロールを実装したい場合

これらに対応する方法を見ていきましょう。

画面上部に追従するヘッダーがある場合

ヘッダーを常にウィンドウ上部に固定しているページだと、スクロール後にヘッダーと要素が被ってしまうという問題が起こります。

これを避けるには、ヘッダーの高さ分をスクロール量から引いてあげる必要があります。

window.addEventListener('DOMContentLoaded', () => {
  const anchorLinks = document.querySelectorAll('a[href^="#"]')
  const anchorLinksArr = Array.prototype.slice.call(anchorLinks);
  const header = document.querySelector('#header');

  anchorLinksArr.forEach(link => {
    link.addEventListener('click', e => {
      e.preventDefault();
      const targetId = link.hash;
      const targetElement = document.querySelector(targetId);
      const targetOffsetTop = window.pageYOffset + targetElement.getBoundingClientRect().top;
      const headerHeight = header.offsetHeight;
      const totalScrollAmount = targetOffsetTop - headerHeight;
      window.scrollTo({
        top: totalScrollAmount,
        behavior: "smooth"
      });
    });
  });
});

変更点はハイライト表示をしている箇所になります。

これらを順に説明していきますね。

4行目:ヘッダーの要素を取得する

const header = document.querySelector('#header');

まずはヘッダーの要素を取得する必要があるので、querySelectorで取得します。

セレクタの部分は、案件に合わせて変えてください。

ここでは#headerとしているので、headerというidがついた要素を指定しています。

今回のようにheaderidをつけている場合は、getElementByIdで取得してもいいですね。

12行目:ヘッダーの高さを取得する

const headerHeight = header.offsetHeight;

要素の高さを取得するには、offsetHeightプロパティを使用します。

offsetHeightは、height padding borderを含む高さを返すので、ヘッダーの要素の高さを取得してくれます。

13行目:ヘッダーの高さを引いたトータルのスクロール量を計算する

const totalScrollAmount = targetOffsetTop - headerHeight;

新しくtotalScrollAmountという変数を用意し、ここにトータルのスクロール量からヘッダーの高さを引いた数値を代入します。

15行目:上で取得したスクロール量をscrollToメソッドに渡す

window.scrollTo({
  top: totalScrollAmount,
  behavior: "smooth"
});

windowToのtopの部分にtotalScrollAmountを渡すことで完了です!

これでヘッダーが画面上部に固定されている場合でも、要素が被らずに表示されるようになりました。

リンクをクリックするたびにヘッダーの高さを測るので、レスポンシブページにも対応済みです。

特定のページ内リンクにのみスムーススクロールを実装したい場合

window.addEventListener('DOMContentLoaded', () => {
  const smoothScrollLinks = document.querySelectorAll('js-smooth-scroll')
  const smoothScrollLinksArr = Array.prototype.slice.call(smoothScrollLinks);

  smoothScrollLinksArr.forEach(link => {
    link.addEventListener('click', e => {
      e.preventDefault();
      const targetId = link.hash;
      const targetElement = document.querySelector(targetId);
      const targetOffsetTop = window.pageYOffset + targetElement.getBoundingClientRect().top;
      
      window.scrollTo({
        top: targetOffsetTop,
        behavior: "smooth"
      });
    });
  });
});

元のコードでは、ページ内リンクが設定されているaタグ全てにスムーススクロールを付けました。

しかし、スムーススクロールさせるaタグを手動で選びたいという場面もあるかもしれません。

そういうときは、以下の方法で対処できます。

// 元のコード: すべてのページ内リンクを取得
const anchorLinks = document.querySelectorAll('a[href^="#"]');

// カスタマイズコード: 特定のページ内リンクのみ取得
const smoothScrollLinks = document.querySelectorAll('.js-smooth-scroll');

querySelectorAllのCSSセレクター部分をa[href^="#"]から.js-smooth-scrollに変更しました。

こうすることで、スムーススクロールさせるaタグを手動で選ぶことができます。

<a href="#target1" class="js-smooth-scroll">スムーススクロールさせる</a>
<a href="#target2" class="js-smooth-scroll">スムーススクロールさせる</a>
<a href="#target3" class="js-smooth-scroll">スムーススクロールさせる</a>

<a href="#target4">スムーススクロールさせない</a>
<a href="#target5">スムーススクロールさせない</a>
<a href="#target6">スムーススクロールさせない</a>

HTML上で、上の例のようにスムーススクロールさせたいaタグにクラスを付けてください。

これで、js-smooth-scrollのクラスを持つページ内リンクのみスムーススクロールされるようになりました。

【まとめ】ページ内リンクのスムーススクロールは素のJavaScriptで実装可能!

ページ内リンクのスムーススクロールを素のJavaScriptで実装する方法を紹介しました。

jQueryやプラグインを使わずとも簡単に実装できましたね。

IEやSafariを含めたブラウザでも対応しているので、かなり使いやすいのではないでしょうか。

カスタマイズ方法も紹介しましたので、幅広い案件に使用できるはずです。

ページTOPに戻るボタンのスムーススクロール実装方法も紹介してますので、興味があればチェックしてみてください。

記事が役に立ったらサポートしてください

Web制作に関する
記事案を募集中!

Web制作について知りたいこと、質問等ありましたら、以下のフォームから気軽に投稿してください。
要望が多かったものは解説記事を作成します。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次