Scroll-driven AnimationsでCSSでの実装が大きく変わる! スクロールをトリガーにしたアニメーションを実装する方法

まもなくリリースされるChrome 115で実装されるScroll-driven Animationsにより、スクロールをトリガーにしたアニメーションの実装方法が大きく変わります。

スクロールすると要素がアニメーションで表示されたり、スクロール量で変化するインジケーター、背景が変化するパララックスなど、実装がそれなりに手間がかかりましたが、数行のコードで実装できるようになります。スクロールをトリガーにしたアニメーションを実装するこれからの方法を紹介します。

スクロールをトリガーにしたアニメーションを実装する方法

Animate elements on scroll with Scroll-driven animations
by Bramus

下記は各ポイントを意訳したものです。
※元サイト様のライセンスに基づいて翻訳しています。基づいてというのは、貢献部分が同ライセンスも含みます。

はじめに

スクロール駆動のアニメーションは、Webでよく使用されるUXパターンです。スクロール駆動のアニメーションとは、スクロールコンテナのスクロール位置と連動してアニメーションするものです。上下にスクロールすると、連動したアニメーションが直接反応して前後にスクラブします。

たとえば、パララックス背景画像や下記のようなスクロールに応じて変化するインジケーターなどのエフェクトがあります。

デモのキャプチャ

デモページ

また、スクロール駆動のアニメーションには、コンテナ内の要素の位置と連動したアニメーションもあります。たとえば、ビューポート内に要素が表示されたときにフェードインさせるエフェクトです。

デモのキャプチャ

デモページ

このようなエフェクトを実装するための古典的な方法は、メインスレッドでスクロールイベントに応答することですが、これは次の2つの問題が発生します。

  • モダンブラウザはスクロールを別プロセスで実行するため、スクロールイベントは非同期で配信されます。
  • メインスレッドのアニメーションはジャンクの影響を受けます。

そのため、スクロールと同期したパフォーマンス性の高いスクロール駆動のアニメーションを実装することは不可能か、非常に困難です。

Chromeに導入されるのは既存のWeb Animations API(WAAPI)CSS Animations APIと連携して動作し、宣言型のスクロール駆動アニメーションを可能にする新しいAPIとコンセプトのセットです。

スクロール駆動のアニメーションを既存の2つのAPIと統合することは、これらのAPIの利点を活用できることを意味します。その中にはメインスレッドからアニメーションを実行させる機能も含まれています。数行のコードを記述するだけで、メインスレッドからスクロール駆動のシルクのように滑らかなアニメーションを実行できるようになりました。
これほど素晴らしいことはないですね!

この記事を読み切れない場合は、Scroll-driven Animationsにアクセスしてください。スクロール駆動のアニメーションのデモやツールが満載です。

Webのアニメーション、ちょっとだけおさらい

CSSで実装するWebのアニメーション

CSSでアニメーションを実装するには、@keyframesを使用してキーフレームを定義します。それをanimation-nameプロパティで要素にリンクし、animation-durationを設定して、アニメーションの時間を設定します。animation-easing-functionanimation-fill-modeなど、ほかにもanimation-*の省略形プロパティがありますが、これらはすべてanimationのショートハンドで使用できます。

たとえば、ある要素をX軸方向に拡大し、背景色を変化させるアニメーションがあります。

JavaScriptで実装するWebのアニメーション

JavaScriptではWeb Animation APIを使用して、まったく同じことを実現できます。AnimationKeyFrameEffectのインスタンスを新規に作成するか、より短いElement animate()メソッドを使用します。

このJavaScriptの視覚的な結果は、前述のCSSバージョンと同じです。

アニメーションのタイムライン

デフォルトでは、ある要素につけられたアニメーションはドキュメントのタイムライン上で実行されます。ページが読み込まれたときに0から始まり、時間の経過に伴った変化します。これがデフォルトのアニメーションのタイムラインで、これまではアクセスできる唯一のアニメーションのタイムラインでした。

スクロール駆動のアニメーションの仕様では、新しいタイプのタイムラインが2つ定義されています。

  • Scroll Progress Timeline
    スクロール進行状況タイムライン、特定の軸に沿ったスクロールコンテナのスクロール位置に連動するタイムラインです。
  • View Progress Timeline
    ビュー進行状況タイムライン、スクロールコンテナ内の特定の要素の相対位置に連動するタイムラインです。

スクロール進行状況タイムライン

スクロール進行状況タイムライン(Scroll Progress Timeline)は、スクロールコンテナ(スクロールポートやスクローラーと呼ばれるもの)のスクロール位置の進行状況に連動するアニメーションタイムラインで、スクロール範囲内の位置を進行状況のパーセントに変換します。

スクロールの開始位置は0%の進捗状況を表し、終了位置は100%を表します。下記のデモは、スクローラーを上から下にスクロールすると、進捗が0%から100%にカウントされます。

デモのキャプチャ

デモページ

スクロール進行状況タイムラインの視覚化。コンテナ内をスクロールしてみてください。

ビュー進行状況タイムライン

ビュー進行状況タイムライン(View Progress Timeline)は、スクロールコンテナ内の特定の要素の相対的な進捗に連動します。スクロール進行状況タイムラインと同様に、スクローラーのスクロールオフセットが追跡されます。異なるのは、進行状況を決定するのがスクローラー内の主題の相対位置です。

これはIntersectionObserverの仕組みと似ており、ある要素がスクローラー内でどれだけ表示されているかを追跡することができます。もしその要素がスクローラーに表示されなければ、要素は交差していません。どんなに小さくな部分だけであってもスクローラー内に表示されている場合、それは交差しています。

ビュー進行状況タイムラインは、ある要素がスクローラーと交差し始めた瞬間から始まり、ある要素がスクローラーと交差しなくなった時点で終わります。下記のデモは、ある要素がスクロールコンテナに入った時点で0%からカウントを始め、離れた時点で100%に達していることが確認できます。

サイトのキャプチャ

デモページ

ビュー進行状況タイムラインの視覚化。コンテナ内をスクロールしてみてください。

スクロール進行状況タイムラインの使い方

CSSで匿名のスクロール進行状況タイムラインを作成する

CSSでスクロールタイムラインを作成するもっとも簡単な方法は、scroll()関数を使用することです。これにより、匿名のスクロールタイムラインが作成され、新しいanimation-timelineプロパティの値として設定できます。

scroll()関数は、<scroller>および<axis>引数を受け入れます。

<scroller>の値は次のとおりです。

  • nearest: もっとも近い祖先のスクロールコンテナを使用します(デフォルト)。
  • root: ドキュメントビューポートをスクロールコンテナとして使用します。
  • self: 要素自体をスクロールコンテナとして使用します。

<axis>の値は次のとおりです。

  • block: スクロールコンテナのブロック軸に沿った進行状況の指標を使用します(デフォルト)。
  • inline: スクロールコンテナのインライン軸に沿った進行状況の指標を使用します。
  • y: スクロールコンテナのy軸に沿った進行状況の指標を使用します。
  • x: スクロールコンテナのx軸に沿った進行状況の指標を使用します。

たとえば、ブロック軸のルートスクローラーにアニメーションをバインドする場合、scroll()に渡す値はrootblockです。まとめると、値はscroll(root block)になります。

重要
スクロール進行状況タイムラインを使用する場合、animation-durationを秒単位で設定しても意味がないため、animation-durationautoに設定する必要があります。また、上記のコードのようにanimation-durationを省略するとデフォルト値であるautoが設定されます。

下記のデモでは、ビューポートの上部に読み取り進行状況インジケーターが表示されています。ページをスクロールすると、プログレスバーが表示され、ドキュメントの最後に到達するとビューポートの幅いっぱいに表示されます。アニメーションを駆動するためには、匿名のスクロール進行状況タイムラインが使用されています。

デモのキャプチャ

デモページ

この読み取り進行状況インジケーターは、position: fixed;で上部に固定されています。アニメーションを活性するためには、widthをアニメーションするのではなく、transformでX軸に対して拡大縮小します。

CSSは、下記の通りです。

#progress要素のアニメーションgrow-progressのタイムラインは、scroll()で作成された匿名タイムラインに設定されます。scroll()には引数を与えないので、デフォルト値が使用されます。

追跡するデフォルトのスクローラーはnearestで、デフォルトの軸はblockです。これは、#progress要素のブロック方向を追跡しながら、最も近いスクローラーであるルートスクローラーを効果的にターゲットにします。

CSSで名前付きスクロール進行状況タイムラインを作成する

スクロール進行状況タイムラインを設定する別の方法は、名前付きタイムラインを使用することです。冗長になりますが、親スクローラーやルートスクローラーをターゲットにしていない場合、ページが複数のタイムラインを使用しているときや自動ルックアップが機能しないときに便利な方法です。こうすることで、指定した名前によってスクロール進行状況タイムラインを特定できます。

要素に名前付きスクロール進行状況タイムラインを作成するには、スクロールコンテナのscroll-timeline-nameプロパティに任意の識別しを設定します。値は--で始まる必要があります。

どの軸を追跡するかを設定するには、scroll-timeline-axisプロパティを使用します。指定できる値は、scroll()<axis>引数と同じです。

最後にアニメーションをスクロール進行状況タイムラインに連動させるためには、アニメーションが必要な要素のanimation-timelineプロパティをscroll-timeline-nameに設定した識別子と同じ値に設定します。

必要に応じて、scroll-timelineのショートハンドでscroll-timeline-namescroll-timeline-axisの両方を設定することもできます。

重要
名前付きの場合でも要素からスクローラーへのルックアップは先祖間でのみ行われることに注意してください。兄弟要素など、先祖以外の要素をターゲットにする方法はこの記事の下の方で解説しています。

下記のデモでは、カルーセル内の画像の上に表示されるステップインジケーターを採用しています。カルーセル内に画像が3つある場合、インジケーターは33%の幅で始まり、3つのうち1つを表示していることを示しています。最後の3つ目の画像を表示すると、インジケーターは全幅を占めます。アニメーションの駆動には名前付きスクロール進行状況タイムラインが使用されています。

デモのキャプチャ

デモページ

基本となるHTMLは、下記の通りです。

CSSは、下記の通りです。
.gallery__progress要素は、ラッパーの.gallery要素内に絶対位置で配置されています。初期静は--num--imagesのカスタムプロパティによって設定されています。

.gallery__scrollcontainerは、含まれている.gallery__entry要素を水平に配置し、スクロールさせる要素です。スクロール位置を追跡することで、.gallery__progressはアニメーション化されます。これは名前付きスクロール進行状況タイムライン--gallery__scrollcontainerを参照することでおこなわれています。

注意
このデモでは、匿名のスクロール進行状況タイムラインでは機能しません。gallery__progressanimation-timeline: scroll(nearest inline);を設定すると、たとえばその要素が直接の親であっても.gallery__scrollcontainerからスクロールを見つけることはできません。

その理由は、nearestのルックアップは、その位置とサイズに影響を与えることができる要素のみを考慮するからです。.gallery__progressは絶対位置のため、そのサイズと位置を決定する最初の親要素は.gallery要素でpotision: relative;が設定されており、それによってgallery__scrollcontainer要素を飛び越えています。

専門的な言葉でいうと、ルックアップはもっとも近いスクロールコンテナを見つけるために、含まれているブロックチェーンをたどります。

JavaScriptでスクロール進行状況タイムラインを作成する

avaScriptでスクロールのタイムラインを作成するには、ScrollTimelineクラスの新しいインスタンスを作成します。追跡したいsourceと軸axisをプロパティバッグに渡します。

  • source: 追跡したいスクローラーを持つ要素への参照。ルートスクローラーを対象にする場合はdocument.documentElementを使用します。
  • axis: 追跡したい軸を設定します。値はblock, inline, x, yです。

これをWebのアニメーションにするには、timelineプロパティとして渡します。durationがある場合は省略します。

JavaScriptを使用したバージョンは、デモページをご覧ください。

HTMLは同じですが、JavaScriptでインジケーターを実装するには、次のJavaScriptを使用します。

見た目はCSSバージョンとまったく同じですが、JavaScriptバージョンは作成されたタイムラインはルートスクローラーを追跡し、ページをスクロールするにつれてx軸の#pregressを0%から100%に拡大表示しています。

ビュー進行状況タイムラインの使い方

CSSで匿名のビュー進行状況タイムラインを作成する

CSSでビュー進行状況タイムラインを作成する方法は、view()関数を使用します。引数は<axia><view-timeline-inset>です。

  • <axia>: スクロール進行状況タイムラインと同じで、追跡したい軸を設定します。デフォルトはblockです。
  • <view-timeline-inset>: 要素がビューポート内にあるかどうかを考慮するときに、オフセット(正または負)を設定できます。値はパーセントまたはautoで、デフォルトはautoです。

たとえば、ブロック軸上のスクローラーと交差する要素にアニメーションをバインドするにはview(block)を使用します。scroll()と同様に、animation-timelineプロパティの値として設定し、animation-durationautoに設定することを忘れないでください。

次のコードを使用すると、スクロール中にビューポートを横切ったときにすべてのimgがフェードインします。

重要
ビュータイムラインの<scroller>は常にもっとも近い親スクローラーの要素を追跡するため、決定することはできません。

息ぬき: タイムラインの範囲を表示

デフォルトでは、ビュータイムラインにリンクされたアニメーションは、タイムラインの全範囲に添付されます。これは、要素がスクロールポートに入ろうとする瞬間から始まり、要素がスクロールポートから完全に離れたときに終了します。

また、ビュータイムラインの特定の部分にリンクさせることも可能で、その場合は範囲を指定します。たとえば、要素がスクローラーに入った時だけ、といった感じです。下記のデモでは、要素がスクロールコンテナに入ったときに0%からカウントを開始しますが、完全に交差した瞬間からすでに100%に達しています。

デモのキャプチャ

デモページ

ターゲットにできるビュータイムラインの範囲は次のとおりです。

  • cover: ビュー進行状況タイムラインの全範囲を表します。
  • entry: 主要ボックスがビュー進行状況の可視範囲に入る範囲を表します。
  • exit: 主要ボックスがビュー進行状況の可視範囲を出るまでの範囲を表します。
  • entry-crossing: 主要ボックスが終了ボーダーエッジを横切る範囲を表します。
  • exit-crossing: 主要ボックスが開始ボーダーエッジを横切る範囲を表します。
  • contain: 主要ボックスがスクロールポート内のビュー進行状況の可視範囲に完全に含まれるか、完全にカバーされる範囲を表します。これは、被写体がスクローラーよりも高いか低いかに依存します。

範囲を定義するには、範囲開始と範囲終了を設定する必要があります。それぞれは範囲名(上記のリスト)とその範囲名内の位置を決定する範囲オフセットで構成されています。範囲のオフセットは通常、0%から100%までのパーセント表記ですが、20emのような固定長を設定することも可能です。

たとえば、要素が入った瞬間からアニメーションを実行したい場合は、範囲開始としてentry 0%を設定します。要素が入るまでに終了させたい場合は、範囲終了としてentry 100%を設定します。CSSでは、animation-rangeでこれを設定します。

下記のデモでは、それぞれの範囲名が何を表しているのか、パーセントが開始位置と終了位置にどのように影響するのかを確認できます。範囲開始をentry 0%にし、範囲終了をcover 50%に設定し、スクロールバーをドラッグしてアニメーションの結果を確認してみてください。

デモのキャプチャ

デモページ

このタイムライン範囲表示ツールを使用して気がついたことがあります。範囲名と範囲オフセットの組み合わせてで、2種類の範囲を対象にできるものがあります。たとえば、entry 0%, entry-crossing 0%, cover 0%はすべて同じ範囲を対象としています。

範囲開始と範囲終了が同じ範囲名をターゲットにしており、0%から100%までの全範囲を対象とする場合、値を短縮して単に範囲名とすることができます。たとえば、animation-range: entry 0% entry 100%;は、より短いanimation-range: entry;に書き換えることができます。

重要
これらの範囲は、要素の未変換の主ボックスから導き出されることに注意してください。つまり、範囲を導き出す際にスケールやトランスレートなどの変換は考慮されません。これは良いことで、スクロール中に利用可能なスクロール領域に影響を与えることなく、要素を拡大縮小できます。変換されたボックスを使用した場合は、スクロール領域の変化に応じて常に再計算する必要があるため、アニメーションがちらつく可能性があります。

下記のデモでは、スクロールポートに入ると画像がフェードインします。これは匿名ビュータイムラインを使用しています。アニメーションの範囲が調整され、各画像がスクロールポートの半分に入ったときに完全な不透明になるようにしました。

デモのキャプチャ

デモページ

この拡張したエフェクトは、アニメーション化されたクリップパスで実現されています。CSSは、下記の通りです。

CSSで名前付きビュー進行状況タイムラインを作成する

スクロール進行状況タイムラインに名前付きがあるのと同様に、ビュー進行状況タイムラインにも名前付きのバージョンを作成できます。scroll-timeline-*の代わりに、view-timeline-を使用して、view-timeline-nameview-timeline-axisを使用します。

同じタイプの値が適用され、名前付きタイムラインを検索するための同じルールが適用されます。

前述の画像公開のデモを再加工し、修正されたコードは下記の通りです。

view-timeline-name: revealing-imageを使用すると、要素はもっとも近いスクローラー内で追跡されます。そして、同じ値がanimation-timelineプロパティの値として使用されます。視覚的な出力は、前述とまったく同じです。

JavaScriptでビュー進行状況タイムラインを作成する

JavaScriptでビュー進行状況タイムラインを作成するには、ViewTimelineクラスの新しいインスタンスを作成します。追跡するsubject, axis, insetをプロパティバッグに渡します。

  • subject: 独自のスクローラー内で追跡する要素への参照。
  • axis: 追跡する軸。CSSのバリアントと同様にblock, inline, x, yが使用可能な値です。
  • inset: ボックスが表示されているかどうかを決定するときのスクロールポートのインセット(正)またはアウトセット(負)の調整。

これをアニメーションにするにはtimelineプロパティとして渡し、durationがある場合は省略します。

アニメーションさせる要素$elsubjectは同じ要素である必要はありません。つまり、DOMツリーのどこかにある遠くの要素をアニメーションさせながら、そのスクローラーで要素を追跡できることを意味します。

さらに試してみたいこと

1つのキーフレームで複数のビュータイムライン範囲にアタッチする

この連絡先リストのデモでは、アイテムがアニメーションで表示されます。アイテムが下からスクロールポートに入るとスライド+フェードインし、上から出るとスライド+フェードアウトします。

デモのキャプチャ

デモページ

このデモでは、各要素は1つのビュー進行状況タイムラインで実装されており、スクロールポートを通過する要素を追跡し、2つのスクロール駆動アニメーションがそれにアタッチされています。animate-inのアニメーションはタイムラインのentry範囲に、animate-outのアニメーションはexit範囲にアタッチされます。

2つの異なる範囲にアタッチされた2つの異なるアニメーションを実行する代わりに、範囲情報をすでに含む1セットのキーフレームを作成することもできます。

キーフレームには範囲情報が含まれているため、アニメーション範囲を指定する必要はありません。 結果は前述とまったく同じです。

非祖先のスクロールタイムラインにアタッチする

この機能はChrome 116で実験的Webプラットフォーム機能フラグを有効にしてください。

名前付きスクロールタイムラインと名前付きビュータイムラインの検索メカニズムは、スクロールの祖先のみに限定されています。しかし、アニメーションが必要な要素が追跡する必要のあるスクロールの子ではないことがよくあります。

これを実現させるのが、timeline-scopeプロパティです。このプロパティを使用すると、タイムラインを実際に作成することなく、その名前のタイムラインを宣言できます。これにより、その名前を持つタイムラインの範囲が広がります。実際には、親要素を共有する際にtimeline-scopeプロパティで子スクローラーのタイムラインを親要素にアタッチできます。

このCSSを説明すると、

  • .parent要素は--tlという名前のタイムラインを宣言しています。その子であればanimation-timelineプロパティの値として使用できます。
  • .scroller要素は実際には--tlという名前のタイムラインを宣言しています。デフォルトでは子要素のみ表示されますが、.parentscroll-timeline-rootとして認定されているため、子要素にアタッチされます。
  • .subject要素は--tlという名前のタイムラインを宣言しています。祖先のツリーをたどると、.parent--tl.scroller--tlを指しているため、.subjectは実質的には.scrollerのスクロール進行状況タイムラインを追跡します。

つまり、timeline-rootを使用すると、タイムラインを祖先に移動 (別名ホイスティング) させることができ、祖先のすべての子がタイムラインにアクセスできるようになります。

timeline-scopeプロパティは、スクロールタイムラインとビュータイムラインの両方で使用できます。

その他のデモとリソース

この記事で解説したデモはすべて、Scroll-driven Animationsにあります。このサイトではスクロール駆動のアニメーションで何が可能になるのかを伝えるためにさらに多くのデモがあります。

サイトのキャプチャ

Scroll-driven Animations

デモだけでなく、ツールもたくさんあります。
スクロール駆動のアニメーションについては、Google I/O ’23のWhat’s new in Web Animationsでも取り上げられています。

sponsors

top of page

©2024 coliss