スクロールテリング

スクロールテリングは、魅力的で没入感のあるウェブ エクスペリエンスを作成するために使用される一般的な手法です。ユーザーがスクロールするとページ上の要素がアニメーション化され、ストーリーを効果的に伝えたり、ユーザーをナラティブに導いたりします。CSS スクロールドリブン アニメーションを使用すると、JavaScript に依存することなく、CSS で直接これらのエフェクトを作成できます。アニメーションは、時間ベースのクロックではなく、スクロール位置によって制御されるため、常にユーザーのスクロールと同期されます。

導入方法

スクロールテリング エクスペリエンスを作成するには、2 つの要素セットが必要です。1 つはスクロール位置を追跡する要素、もう 1 つはアニメーション化する要素です。

まず、トラッキングする要素に名前付きの view-timeline を定義します。これらはアニメーションのドライバとして機能します。

#tracked {
  section:nth-child(1){ view-timeline: --tl-1 block; }
  section:nth-child(2){ view-timeline: --tl-2 block; }
  section:nth-child(3){ view-timeline: --tl-3 block; }
  section:nth-child(4){ view-timeline: --tl-4 block; }
  section:nth-child(5){ view-timeline: --tl-5 block; }
}

次に、アニメーション化する要素にアニメーションを適用し、animation-timeline プロパティを使用して、作成したタイムラインにリンクします。

#animated {
  section {
    animation: animate-in auto linear both, animate-out auto linear forwards;
    animation-range: entry 25% cover 50%, exit 50% exit 75%;
  }

  section:nth-child(1){ animation-timeline: --tl-1; }
  section:nth-child(2){ animation-timeline: --tl-2; }
  section:nth-child(3){ animation-timeline: --tl-3; }
  section:nth-child(4){ animation-timeline: --tl-4; }
  section:nth-child(5){ animation-timeline: --tl-5; }
}

animation-timeline が名前付きタイムラインを参照できるようにするには、同じスコープに存在する必要があります。共通の祖先で timeline-scope プロパティを使用すると、タイムラインを必要とするすべての要素でタイムラインを使用できるようになります。この場合、:root 要素が適しています。

html {
  timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}

最後に、animation-range プロパティを使用して、アニメーションを実行するタイムラインの正確な範囲を指定できます。これにより、アニメーションがトリガーされるタイミングと進行状況をきめ細かく制御できます。

#animated section {
  animation-range: entry 25% cover 50%, exit 50% exit 75%;
}

コード例

html {
  timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}

#tracked {
  section:nth-child(1){ view-timeline: --tl-1 block; }
  section:nth-child(2){ view-timeline: --tl-2 block; }
  section:nth-child(3){ view-timeline: --tl-3 block; }
  section:nth-child(4){ view-timeline: --tl-4 block; }
  section:nth-child(5){ view-timeline: --tl-5 block; }
}

@keyframes animate-in {
  from { scale: 0.5; opacity: 0; transform: rotateY(-180deg); }
  to { transform: rotateY(0deg); }
}
@keyframes animate-out {
  to { translate: 100% 0; opacity: 0; }
}

#animated {
  section {
    animation: animate-in auto linear both, animate-out auto linear forwards;
    animation-range: entry 25% cover 50%, exit 50% exit 75%;
    backface-visibility: hidden;
  }

  section:nth-child(1){ animation-timeline: --tl-1; }
  section:nth-child(2){ animation-timeline: --tl-2; }
  section:nth-child(3){ animation-timeline: --tl-3; }
  section:nth-child(4){ animation-timeline: --tl-4; }
  section:nth-child(5){ animation-timeline: --tl-5; }
}

/* MANDATORY Copy-Paste Safety: Disable continuous storytelling motion for sensitive users */
@media (prefers-reduced-motion: reduce) {
  #animated section {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

ベスト プラクティス

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

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

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

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

代替戦略

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

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

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

このユースケースでは、次のスクリプトで、スクロール駆動アニメーションをサポートしていないブラウザにフォールバックを適用します。IntersectionObserver を使用して各 #tracked section 要素の可視性を追跡し、それに応じて対応する #animated section の transform プロパティを更新します。

const animatedSections = document.querySelectorAll('#animated section');

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    const sectionIndex = Array.from(document.querySelectorAll('#tracked section')).indexOf(entry.target);
    if (sectionIndex !== -1) {
      const animatedSection = animatedSections[sectionIndex];
      const ratio = entry.intersectionRatio;

      // Animate-in
      animatedSection.style.opacity = ratio;
      animatedSection.style.transform = `scale(${0.5 + ratio * 0.5}) rotateY(${-180 + ratio * 180}deg)`;

      // Animate-out
      if (ratio < 0.5) {
        animatedSection.style.translate = `${(0.5 - ratio) * 2 * 100}% 0`;
      } else {
        animatedSection.style.translate = '0 0';
      }
    }
  });
}, { threshold: Array.from({length: 101}, (_, i) => i / 100) });

document.querySelectorAll('#tracked section').forEach(section => {
  observer.observe(section);
});

対応する CSS:

#animated section {
  opacity: 0;
  transform: scale(0.5)  rotateY(-180deg);
  backface-visibility: hidden;
}

/* MANDATORY Copy-Paste Safety: Ensure content remains fully visible and legible for assistive technologies or users with motion sensitivities */
@media (prefers-reduced-motion: reduce) {
  #animated section {
    opacity: 1 !important;
    transform: none !important;
    translate: 0 0 !important;
  }
}

この代替により、ネイティブ CSS 機能をサポートしていないブラウザでも、より正確なスクロール駆動型アニメーションが提供され、すべてのユーザーに一貫したエクスペリエンスが提供されます。IntersectionObserver に一連のしきい値を使用することで、スクロール位置をより正確に追跡し、よりスムーズなアニメーションを作成できます。