スクロールポートに出入りする要素に、開始効果と終了効果を追加する

開始効果と終了効果は、要素がビューポートに入ったとき、またはビューポートから出たときにトリガーされるアニメーションです。これを使用して、魅力的で動的なユーザー エクスペリエンスを作成できます。たとえば、エントリ効果を使用して、要素がスクロールして表示されるときにフェードインしたり、終了効果を使用して、要素がスクロールして表示されなくなるときに縮小したりできます。

導入方法

要素に開始効果と終了効果を追加するには、いくつかの CSS プロパティを組み合わせる必要があります。詳しい手順を次に説明しますのでご覧ください。

  1. 開始アニメーションと終了アニメーション用に別々の @keyframes を作成します。開始アニメーションは要素がビューポートに入るときに適用され、終了アニメーションは要素がビューポートから出るときに適用されます。

    @keyframes slide-in {
      from { transform: translateX(-100%); }
    }
    @keyframes slide-out {
      to { transform: translateX(100%); }
    }
    
  2. エントリ キーフレームと終了キーフレームを要素に適用します。これを行うには、animation プロパティで複数のアニメーションを定義します。

    • 開始アニメーションの animation-fill-mode を backwards に設定して、アニメーションが開始される前に初期状態が適用されるようにします。
    • 終了アニメーションの animation-fill-mode を forwards に設定して、アニメーションの完了後に最終状態が維持されるようにします。
    .animated-element {
      animation:
        slide-in 1s linear backwards,
        slide-out 1s linear forwards;
    }
    
  3. ビュー タイムラインを作成してアニメーションにリンクします。ビュー タイムラインは、ビューポート内の要素の可視性にリンクされたタイムラインの一種です。view() 関数を使用して作成し、animation-timeline プロパティを使用してアニメーションに適用できます。

    .animated-element {
      animation-timeline: view();
    }
    

    デフォルトでは、view() は block 軸上の要素をトラッキングします。inline 軸でトラッキングする必要がある場合は、view(inline) を使用できます。

  4. アニメーションを entry と exit の範囲に制限します。animation-range プロパティを使用すると、アニメーションを実行するタイムラインの部分を指定できます。

    • entry 範囲は、要素が最初にビューポートに入ってから完全に表示されるまでの時間をカバーします。
    • exit の範囲は、要素がビューポートから離れ始めてから完全に非表示になるまでの時間をカバーします。
    .animated-element {
      animation-range: entry, exit;
    }
    

コード例

このコードは、匿名の view-timeline を使用して、スクロール時にスクローラーの直接の子をアニメーション化します。

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: view(inline);
      animation-range: entry, exit;
    }
  }
}

要素がスクロールポートに入ると grow アニメーションが再生され、スクロールポートから出ると shrink アニメーションが再生されます。

次のコードは、視覚的な結果は同じですが、名前付きビュー タイムラインを使用して、スクロール時にスクローラーの直接の子をアニメーション化します。

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      view-timeline: --tl inline;
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: --tl;
      animation-range: entry, exit;
    }
  }
}

ベスト プラクティス

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

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

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

  • 任意: トラッキングする軸を明示的に指定します。デフォルトの block 軸をターゲットにしない場合(水平スクローラーなど)、view(block) または view(inline) でトラッキングする軸を明示的に指定します。
  • アニメーションがトラッキング対象の要素自体に適用されない場合は、名前付きビュー タイムラインを使用します。

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

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

追跡対象の複数の要素または子をアニメーション化する必要がある場合は、名前付きの view-timeline を優先します。

ブラウザのサポートとフォールバック戦略

そのため、通常はフォールバック戦略が必要です。

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

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

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

このユースケースでは、次のスクリプトで、スクロール駆動アニメーションをサポートしていないブラウザにフォールバックを適用します。IntersectionObserver を使用して .wrapper 要素の可視性を追跡し、スクロール位置に基づいてレイヤの transform プロパティを更新します。

<script>
  if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
    const observer = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          // This matches the effect as defined in the CSS example above.
          // Customize this further if needed.
          entry.target.style.scale = 0.5 + entry.intersectionRatio * 0.5;
        }
      },
      {
        threshold: Array.from({ length: 101 }, (_, i) => i / 100),
      }
    );

    document.querySelectorAll('.scroller > *').forEach((el) => {
      observer.observe(el);
    });
  }
</script>