スクロール進行状況インジケーターを作成する

スクロール進行状況インジケーターは、スクロール可能なドキュメントまたはコンテナでのユーザーの進行状況を視覚的に伝える一般的なユーザー インターフェース パターンです。ユーザーがスクロールすると、位置を反映して視覚要素が更新され、閲覧したコンテンツの量と残りのコンテンツの量が明確かつ直感的にわかります。

導入方法

スクロール進行状況インジケーターを作成するには、次の 2 つが必要です。

  1. 進行状況バーとして機能する要素。この要素は通常、position: fixed または position: absolute で、ユーザーがスクロールしても表示されたままになります。
  2. スクロール位置にリンクされたアニメーション。

以下に、この問題の解決方法をご紹介いたします。

  • まず、進行状況バーとして機能する HTML 要素を作成します。この要素は、好みに合わせてスタイルを設定できます。
  • 次に、CSS で、進行状況バーをスケーリングする @keyframes アニメーションを定義します。一般的な方法は、要素を scaleX(0) から scaleX(1) にスケーリングすることです。
  • 最後に、このアニメーションを進行状況バー要素に適用し、animation-timeline を scroll-timeline に設定します。これにより、最も近い祖先スクローラーのスクロール位置に基づいてアニメーションの進行を制御するようブラウザに指示します。

コード例

このコードは、scroll() 関数によって作成された匿名の scroll-timeline を使用して、スクロール時に #progress 要素を拡大します。

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: scroll())) {
    @keyframes grow-progress {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }

    #progress {
      position: fixed;
      left: 0; top: 0;
      width: 100%; height: 1em;
      background: red;

      transform-origin: 0 50%;
      animation: grow-progress auto linear;
      animation-timeline: scroll();
    }
  }
}

DOM 内の場所により、scroll() 関数は block 方向の最も近い祖先スクローラーを追跡します。ここではルート スクロールです。

<body>
  <!-- MANDATORY: Purely decorative visual scroll progress bars MUST set aria-hidden="true" to remove the empty element from the assistive technology reading tree -->
  <div id="progress" aria-hidden="true"></div>
</body>

このコードは、scroll-timeline プロパティによって作成された名前付きの scroll-timeline を使用して、スクロール時に #progress 要素を拡大します。

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: scroll())) {
    @keyframes grow-progress {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }

    :root {
      scroll-timeline: --tl block;
    }

    #progress {
      position: fixed;
      left: 0; top: 0;
      width: 100%; height: 1em;
      background: red;

      transform-origin: 0 50%;
      animation: grow-progress auto linear;
      animation-timeline: --tl;
    }
  }
}

ベスト プラクティス

スクロール駆動型アニメーションを使用する場合は、スムーズでアクセスしやすいエクスペリエンスを実現するために、いくつかのベスト プラクティスに従うことが重要です。

  • 推奨: 機能検出を含める。すべてのブラウザがスクロールドリブン アニメーションをサポートしているわけではありません。@supports (animation-timeline: scroll()) を使用してサポートを確認し、サポートしていないブラウザに代替を提供します。
    • フォールバック戦略に scroll-timeline-polyfill パッケージを使用しないでください。このパッケージは機能が完全ではなく、既知の問題が多数あります。
    • アニメーションが装飾的なものにすぎない場合は、プログレッシブ エンハンスメントを選択し、代替手段を提供しないでください。
  • 推奨: 支援技術の読み上げフローから、純粋に装飾的な要素を削除します。純粋に視覚的なスクロール インジケーターに aria-hidden="true" を適用して、スクリーン リーダーが空の、名前のないノードに遭遇しないようにします。
  • 推奨事項: ユーザーの設定を尊重します。ウェブ上でのモーションを好まないユーザーもいます。prefers-reduced-motion メディアクエリを使用して、これらのユーザーに対してアニメーションを無効にするか、アニメーションを減らします。
  • 推奨: パフォーマンスの高い CSS プロパティのみをアニメーション化する。アニメーションを最もスムーズにするには、ブラウザのコンポジタ スレッドで処理できるプロパティ(transform や opacity など)のみをアニメーション化します。width や height などの他のプロパティをアニメーション化すると、パフォーマンスの問題が発生する可能性があります。
  • 推奨: 正しい宣言順序を使用します。animation 略式プロパティを使用する場合は、略式プロパティがタイムラインをリセットしないように、animation-timeline を後に宣言します。

scroll() 関数を使用してスクロール駆動型アニメーションを作成する場合:

  • 省略可 スクロールを明示的に指定する: 最も近い祖先スクロールをターゲットにしない場合は、scroll(root) または scroll(self) で使用するスクロールを明示的に指定します。
    • root、nearest、self では不十分な場合は、名前付きの scroll-timeline を使用します。
  • 任意: トラッキングする軸を明示的に指定します。デフォルトの block 軸をターゲットにしない場合(水平スクローラーなど)、scroll(block) または scroll(inline) でトラッキングする軸を明示的に指定します。

scroll-timeline プロパティを使用してスクロール駆動型アニメーションを作成する場合:

  • 推奨: 名前に CSS <dashed-ident> を使用します。
  • 任意: トラッキングする軸を明示的に指定します。デフォルトの block 軸をターゲットにしない場合(水平スクローラーなど)、scroll-timeline-axis でトラッキングする軸を明示的に指定します。
  • 推奨: ルックアップのスコープが機能することを確認します。scroll-timeline を宣言している要素が、アニメーション化された要素のフラットツリーの祖先でない場合は、共有の祖先で timeline-scope を使用して、scroll-timeline の名前の可視性を引き上げます。

代替戦略

スクロール駆動型アニメーションをサポートしていないブラウザでは、フォールバックを使用して視覚効果を再現できます。フォールバックは通常、スクロール リスナー(ScrollTimeline 効果の場合)または IntersectionObserver API(ViewTimeline 効果の場合)を使用して構築されます。

スクロールドリブン アニメーションのサポートが組み込まれているブラウザでは、常にネイティブ CSS 実装を使用してください。その方がパフォーマンスが向上します。

フォールバック アプローチを使用してすべてのエフェクトを再現できるわけではありません。

このユースケースでは、次のスクリプトで、スクロール駆動アニメーションをサポートしていないブラウザにフォールバックを適用します。スクロール リスナーを使用してルート要素のスクロール位置を追跡し、それに応じて進行状況バーの transform プロパティを更新します。

<script>
  if (!CSS.supports('animation-timeline', 'scroll()')) {
    const progress = document.querySelector('#progress');

    window.addEventListener('scroll', () => {
      const scrollable = document.documentElement.scrollHeight - window.innerHeight;
      const scrolled = window.scrollY;
      const progressPercentage = (scrolled / scrollable);

      progress.style.transform = `scaleX(${progressPercentage})`;
    });
  }
</script>