jQuery / Plugin / jQuery Smooth Scroll

jQuery Smooth Scrollはhref="#beta"のような同じページ内のリンク移動をアニメーションにするプラグインです。リンクを選んで.smoothScroll()を呼び、speedで時間、offsetで到着位置を調整します。到着先のフォーカスと動きの軽減も扱うと、表示以外の操作も続けやすくなります。

対象と必要なファイル

使うのはkswedbergのjquery-smooth-scrollで、同名に近いホイール制御ライブラリーとは別です。href="#beta"に対応する一意のid="beta"を同じ文書内に用意します。

CDNのプラグイン2.2.0と完全版jQuery4.0.0を固定します。jQuery、プラグイン、初期化の順で、必要な効果がないslimは使いません。同じプラグインをWindows Edgeで4.0.0・3.7.1それぞれと実行しました。テーマや別プラグインを含む全環境の互換性ではありません。

対象はメニュー内の.js-scroll二リンクだけです。Native jumpと本文のリンクは通常動作を残し比較できます。サイトの全aへ無差別に適用せず、ページ内案内という意図の明確な範囲に限定します。

余白とフォーカスを含む完全な例

UTF-8のsmooth-scroll-demo.htmlとして保存し開きます。サーバーやビルドは不要で、ネット接続から二つのスクリプトを読みます。各節と末尾に十分な空間を取り、到着位置を観察できます。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Same-page smooth scrolling</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-smooth-scroll/2.2.0/jquery.smooth-scroll.min.js"></script>
  <style>
    :root { --anchor-gap: 100px; }
    html { scroll-behavior: auto; }
    body { font: 16px/1.6 sans-serif; margin: 20px; padding-top: var(--anchor-gap); }
    #menu { position: fixed; top: 0; left: 0; right: 0; padding: 12px; background: #e6f4f1; z-index: 10; }
    #menu a, #menu button { display: inline-block; margin: 4px 8px; font: inherit; }
    section { min-height: 700px; max-width: 700px; }
    h2 { scroll-margin-top: var(--anchor-gap); }
    h2:focus { outline: 3px solid teal; }
    footer { min-height: 100vh; }
  </style>
</head>
<body>
  <nav id="menu" aria-label="Sections">
    <a class="js-scroll" href="#alpha">Alpha</a>
    <a class="js-scroll" href="#beta">Beta</a>
    <a href="#native">Native jump</a>
    <button type="button" id="reset">Reset</button>
    <span id="status" role="status">Native links available.</span>
  </nav>
  <h1>Explore the sections</h1>
  <section><h2 id="alpha" tabindex="-1">Alpha</h2><p>First destination. Text remains available without JavaScript.</p></section>
  <section><h2 id="beta" tabindex="-1">Beta</h2><p>Second destination. Tab can continue to <a href="#native">the next destination</a>.</p></section>
  <section><h2 id="native" tabindex="-1">Native destination</h2><p>This menu link is not enhanced by the plugin.</p></section>
  <footer>Enough space to align the last heading below the menu.</footer>
  <script>
    jQuery(function ($) {
      if (typeof $.fn.smoothScroll !== "function") return;
      const $menu = $("#menu"), $status = $("#status"), duration = 600;
      function gap() {
        const value = $menu.outerHeight() + 12;
        document.documentElement.style.setProperty("--anchor-gap", value + "px");
        return value;
      }
      gap(); $(window).on("resize.demo", gap);
      $("#menu a.js-scroll").smoothScroll({
        speed: duration, easing: "swing", autoFocus: true,
        beforeScroll: function (options) {
          options.offset = -gap();
          options.speed = window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : duration;
          $status.text("Moving to " + options.scrollTarget);
        },
        afterScroll: function (options) {
          history.replaceState(null, "", options.scrollTarget);
          $status.text("Arrived at " + options.scrollTarget);
        }
      });
      $("#reset").on("click", function () {
        $("html, body").stop(true, false); window.scrollTo(0, 0);
        history.replaceState(null, "", location.pathname + location.search);
        $status.text("Ready; enhanced links: 2.");
      });
      $status.text("Ready; enhanced links: 2.");
    });
  </script>
</body>
</html>

準備後は次の表示です。JavaScriptを無効にするとNative links available.が残り、通常のハッシュリンクとして動きます。

Ready; enhanced links: 2.
  1. Betaを選ぶと約600msで移動しArrived at #betaになります。見出しはメニューの12px下に止まります。
  2. Resetで先頭とハッシュなしURLへ戻します。AlphaかBetaへキーボードでフォーカスしEnterを押します。
  3. 到着見出しには輪郭が出ます。Beta後にTabで本文のリンクへ続けます。
  4. Native jumpはプラグインを通らず、到着状態の表示も更新しません。
  5. 動きの軽減をreduceにすると時間0で同じ目的地と完了処理になります。
  6. 幅を変えてメニューが折り返しても見出しが隠れないことを確認し、Resetで再比較します。

tabindex="-1"は通常のTab順へ追加せずプログラムから見出しをフォーカス可能にします。autoFocus=trueで到着後のキーボード位置も移し、メニューへ置き去りにしません。

時間・位置・URLの方針

speedはこの例ではミリ秒の所要時間で、小さいほど同距離を速く移動します。同じ目的地でduration定数を600から1200へ変えて比較し600へ戻します。easingは時間を固定してswingとlinearを比較します。他の名前は対応する拡張が必要です。

offsetは目標座標に加える値です。上に空間を作るためメニュー実高さと12pxの合計を負数にします。beforeScrollで測り直すので折返しを反映します。正数ではさらに下へ進み、固定メニューにタイトルが隠れることがあります。

通常リンクにはscroll-margin-top、プラグインには数値offsetを使います。CSS指定がプラグインの座標計算を代行するとは考えず、両方確認します。

プラグインは既定でリンクの通常動作を止めます。この例は完了後にhistory.replaceState()でハッシュを変更します。現在の履歴を置換するため目的地ごとに戻る履歴は増えず、hashchangeでの案内も実装していません。戻る・進むが必要なアプリは別の履歴設計が必要です。

Resetは進行中と待機中の効果を止め、位置・ハッシュ・状態を復元します。連続した目的地変更も確認しましたが、ホイールを含む全入力での中断を実装したものではありません。

CSSの代替と診断

単純なアンカーならscroll-behaviorでも実現できます。次は併用ではなく代替です。外部二スクリプトと初期化、Resetボタンと状態表示を削除し、既存のhtmlのauto規則を置換します。見出しのscroll-margin-topは維持し、固定メニュー高さに合う余白を指定します。

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

ブラウザーの標準リンク・履歴を使い、プラグインのミリ秒指定やコールバックはありません。メディアクエリーで動きを減らします。逆にプラグイン例はCSSをautoにして二方式を競合させません。

smoothScroll is not a functionならNetworkの応答と順序を確認します。プラグイン後に別のjQueryを読むと拡張が失われることがあります。WordPressではテーマの登録依存関係を先に確認します。

移動しなければidの欠落・重複、実際のhref、選択範囲を確認します。ずれはメニュー高、遅れて読み込む画像・フォント、末尾の余地を点検します。内側のスクロール領域ではscrollElementと座標基準を別に設定・検証します。