【JavaScript】スムーススクロールを実装する方法|scrollIntoView・scrollTo・CSS scroll-behavior・固定ヘッダー対応まで解説

【JavaScript】スムーススクロールを実装する方法|scrollIntoView・scrollTo・CSS scroll-behavior・固定ヘッダー対応まで解説 JavaScript

ページ内リンクをクリックした際に目的の場所まで滑らかにスクロールする「スムーススクロール」は、ユーザー体験を向上させる基本テクニックです。CSS だけで実現する方法から JavaScript で細かく制御する方法まで、複数のアプローチがあります。

この記事でわかること
・CSS scroll-behavior: smooth による最もシンプルな実装
scrollIntoView で特定要素まで滑らかにスクロールする方法
window.scrollTo で座標を指定してスクロールする方法
・固定ヘッダーがある場合のオフセット調整
・ページ内リンクすべてにスムーススクロールを適用する方法
・requestAnimationFrame によるカスタムイージング
・prefers-reduced-motion(モーション軽減)への対応
スポンサーリンク

CSS scroll-behavior で実現する(最もシンプル)

JavaScript を一切書かずにスムーススクロールを実現できる最もシンプルな方法です。

CSS
html {
  scroll-behavior: smooth;
}

この 1 行だけで、ページ内リンク(<a href="#section">)をクリックした際のスクロールが滑らかになります。JavaScript の scrollToscrollIntoViewbehavior: "smooth" を指定した場合にも適用されます。

メリット デメリット
CSS 1 行で実装完了 スクロール速度やイージングのカスタマイズ不可
JavaScript 不要 固定ヘッダー分のオフセット調整ができない
すべてのページ内リンクに自動適用 Safari は 15.4 以降で対応(古い iOS は非対応)
固定ヘッダーのオフセットが不要で、シンプルなページ内リンクのスムーススクロールであれば、CSS scroll-behavior だけで十分です。

scrollIntoView で特定要素までスクロールする

scrollIntoView は、指定した要素が画面内に表示されるように自動でスクロールするメソッドです。behavior: "smooth" を指定すると滑らかにスクロールします。

JavaScript
const target = document.getElementById("section2");

target.scrollIntoView({
  behavior: "smooth",  // 滑らかにスクロール
  block: "start"       // 要素の上端をビューポートの上端に合わせる
});

block オプションの種類

block は要素の垂直方向の表示位置を指定します。

動作
"start"(デフォルト) 要素の上端をビューポートの上端に合わせる
"center" 要素をビューポートの中央に表示
"end" 要素の下端をビューポートの下端に合わせる
"nearest" 最も近い位置にスクロール(すでに表示されていればスクロールしない)
ボタンクリックで特定セクションにスクロール
document.getElementById("goToFaq").addEventListener("click", () => {
  document.getElementById("faq-section").scrollIntoView({
    behavior: "smooth",
    block: "start"
  });
});
scrollIntoView は固定ヘッダーの高さを考慮しません。ヘッダーの下に要素が隠れてしまう場合は、後述のオフセット調整が必要です。

window.scrollTo で座標を指定してスクロールする

window.scrollTo はスクロール先の座標(ピクセル)を直接指定するメソッドです。behavior: "smooth" を付けると滑らかにスクロールします。

JavaScript
// ページ上部にスムーススクロール
window.scrollTo({
  top: 0,
  behavior: "smooth"
});

// 500px の位置にスムーススクロール
window.scrollTo({
  top: 500,
  behavior: "smooth"
});

特定要素の位置を計算してスクロールする

scrollIntoView と違い、scrollTo は座標を自分で計算するため、固定ヘッダーの高さを引いたオフセット調整が簡単に行えます。

JavaScript
function scrollToElement(selector, offset = 0) {
  const element = document.querySelector(selector);
  if (!element) return;

  const top = element.getBoundingClientRect().top + window.scrollY - offset;
  window.scrollTo({
    top: top,
    behavior: "smooth"
  });
}

// 固定ヘッダーの高さ(60px)を考慮してスクロール
scrollToElement("#target-section", 60);
getBoundingClientRect().top はビューポートからの相対位置を返します。window.scrollY(現在のスクロール量)を足すことで、ページ全体における絶対位置を計算できます。

固定ヘッダーがある場合のオフセット調整

固定ヘッダー(position: fixed / sticky)があるサイトでは、スクロール先の要素がヘッダーの下に隠れてしまう問題が起きます。対処法は 2 つあります。

CSS scroll-padding-top を使う方法

CSS だけでオフセットを調整できる最もシンプルな方法です。scroll-behavior: smooth と組み合わせると、JavaScript なしで固定ヘッダー対応のスムーススクロールが完成します。

CSS
html {
  scroll-behavior: smooth;
  scroll-padding-top: 70px; /* 固定ヘッダーの高さ + 余白 */
}

scroll-padding-topscrollIntoView にも適用されるため、JavaScript でスクロールする場合にも有効です。

JavaScript で動的にオフセットを計算する方法

ヘッダーの高さがレスポンシブで変わる場合は、JavaScript で動的に取得します。

JavaScript
function scrollToWithHeader(selector) {
  const element = document.querySelector(selector);
  if (!element) return;

  // ヘッダーの高さを動的に取得
  const header = document.querySelector("header");
  const headerHeight = header ? header.offsetHeight : 0;
  const margin = 16; // 追加の余白

  const top = element.getBoundingClientRect().top + window.scrollY - headerHeight - margin;
  window.scrollTo({ top, behavior: "smooth" });
}

すべてのページ内リンクにスムーススクロールを適用する

サイト内のページ内リンク(href="#xxx")すべてにスムーススクロールを適用するコードです。固定ヘッダーのオフセット調整も含めた実用的な実装です。

JavaScript
document.addEventListener("DOMContentLoaded", () => {
  // href="#" で始まるすべてのアンカーリンクを取得
  const anchors = document.querySelectorAll('a[href^="#"]');

  anchors.forEach(anchor => {
    anchor.addEventListener("click", (e) => {
      const href = anchor.getAttribute("href");
      if (href === "#" || href === "#top") {
        // ページ上部にスクロール
        e.preventDefault();
        window.scrollTo({ top: 0, behavior: "smooth" });
        return;
      }

      const target = document.querySelector(href);
      if (!target) return;

      e.preventDefault();

      // 固定ヘッダーの高さを考慮
      const header = document.querySelector("header");
      const offset = header ? header.offsetHeight + 16 : 0;
      const top = target.getBoundingClientRect().top + window.scrollY - offset;

      window.scrollTo({ top, behavior: "smooth" });
    });
  });
});
CSS の scroll-behavior: smooth + scroll-padding-top で済む場合は、JavaScript は不要です。上記の JavaScript パターンは、スクロール完了後のコールバック処理やアニメーションの細かい制御が必要な場合に使いましょう。

requestAnimationFrame でカスタムイージングを実装する

ネイティブの behavior: "smooth" はスクロール速度やイージング(加減速のカーブ)をカスタマイズできません。独自のアニメーションが必要な場合は requestAnimationFrame で自前実装します。

JavaScript
function smoothScrollTo(targetY, duration = 600) {
  const startY = window.scrollY;
  const distance = targetY - startY;
  let startTime = null;

  // イーズインアウト(加速→減速)
  function easeInOutCubic(t) {
    return t < 0.5
      ? 4 * t * t * t
      : 1 - Math.pow(-2 * t + 2, 3) / 2;
  }

  function step(currentTime) {
    if (!startTime) startTime = currentTime;
    const elapsed = currentTime - startTime;
    const progress = Math.min(elapsed / duration, 1);

    window.scrollTo(0, startY + distance * easeInOutCubic(progress));

    if (progress < 1) {
      requestAnimationFrame(step);
    }
  }

  requestAnimationFrame(step);
}

// 使用例: #target まで 800ms でスクロール
const target = document.getElementById("target");
const targetY = target.getBoundingClientRect().top + window.scrollY;
smoothScrollTo(targetY, 800);

よく使われるイージング関数

イージング 動き 数式
easeInQuad ゆっくり始まる t * t
easeOutQuad ゆっくり止まる t * (2 - t)
easeInOutCubic 滑らかに加速→減速 上記コード参照
linear 等速(一定速度) t
イージングの違いは微細ですが、easeInOutCubic が最も自然で多くのサイトで使われています。

prefers-reduced-motion への対応(アクセシビリティ)

一部のユーザーは OS 設定で「視差効果を減らす」を有効にしています。この設定を尊重し、スムーススクロールを無効化(即座にジャンプ)するのがアクセシビリティのベストプラクティスです。

CSS
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}
JavaScript
function getScrollBehavior() {
  const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  return prefersReduced ? "auto" : "smooth";
}

// 使用例
window.scrollTo({
  top: 0,
  behavior: getScrollBehavior()
});
モーション軽減設定を無視すると、前庭障害のあるユーザーにめまいや吐き気を引き起こす可能性があります。アニメーションを実装する際は必ず prefers-reduced-motion への対応を検討してください。

スムーススクロール実装方法の比較

方法 固定ヘッダー対応 速度カスタム JS 不要 IE 対応
CSS scroll-behavior scroll-padding-top で対応 × ×
scrollIntoView scroll-padding-top で対応 × × △(smooth 非対応)
window.scrollTo ○(座標計算で対応) × × △(smooth 非対応)
requestAnimationFrame ×

関連記事

よくある質問

QCSS scroll-behavior: smooth と JavaScript の scrollIntoView はどちらがよいですか?
Aシンプルなページ内リンクだけなら CSS で十分です。ボタンクリックで特定要素にスクロールしたい場合や、スクロール完了後に処理を実行したい場合は JavaScript を使いましょう。CSS の scroll-padding-top を併用すれば、固定ヘッダーへの対応も CSS だけで可能です。
Q固定ヘッダーの下に要素が隠れてしまいます。どう対処しますか?
ACSS で html { scroll-padding-top: 70px; }(ヘッダー高さ分)を設定するのが最もシンプルです。JavaScript で対応する場合は getBoundingClientRect().top + window.scrollY - headerHeight で座標を計算し、window.scrollTo に渡します。
Qスムーススクロールの速度を変更できますか?
Aネイティブの behavior: "smooth" では速度を指定できません。速度やイージングをカスタマイズしたい場合は requestAnimationFrame を使って自前で実装します。duration(ミリ秒)とイージング関数を変えることで自由に調整できます。
QSafari で scroll-behavior: smooth が効きません。
ASafari は バージョン 15.4(2022 年 3 月)以降で対応しています。iOS 15.3 以前のユーザーには効かないため、古い iOS への対応が必要な場合は JavaScript(scrollIntoView または requestAnimationFrame)を併用してください。
Qprefers-reduced-motion への対応は必須ですか?
A法的には必須ではありませんが、WCAG 2.1(Web コンテンツアクセシビリティガイドライン)の達成基準 2.3.3 で推奨されています。実装は CSS の @media (prefers-reduced-motion: reduce)scroll-behavior: auto に切り替えるだけなので、対応コストはごくわずかです。

まとめ

スムーススクロールの実装は、要件に応じて適切な手法を選ぶことが重要です。

  • シンプルなページ内リンク → CSS scroll-behavior: smooth(1 行で完了)
  • ボタンクリックで特定要素へscrollIntoView({ behavior: "smooth" })
  • 固定ヘッダー対応 → CSS scroll-padding-top または window.scrollTo でオフセット計算
  • 速度やイージングのカスタムrequestAnimationFrame で自前実装

どの方法でも、prefers-reduced-motion への対応を忘れないようにしましょう。アクセシビリティを考慮したスムーススクロールは、すべてのユーザーにとって快適なブラウジング体験を提供します。