スクロール タイムラインとビュー タイムラインを使用して、スクロールドリブン アニメーションを宣言的に作成する方法を学習します。
スクロールドリブン アニメーション
対応ブラウザ
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
スクロールドリブン アニメーションは、ウェブ上で一般的な UX パターンです。スクロールドリブン アニメーションは、スクロール コンテナのスクロール位置にリンクされます。つまり、上下にスクロールすると、リンクされたアニメーションが直接的な反応として前方または後方にスクラブされます。たとえば、視差効果のある背景画像や、スクロールに応じて移動する読書インジケーターなどがこれに該当します。
<ph type="x-smartling-placeholder">同様のスクロールドリブン アニメーションは、スクロール コンテナ内の要素の位置にリンクされたアニメーションです。たとえば、要素がビューに入ったときにフェードインできます。
<ph type="x-smartling-placeholder">このような効果を実現する従来の方法は、メインスレッドのスクロール イベントに応答することですが、これには次の 2 つの大きな問題が発生します。
- 最新のブラウザではスクロールが別のプロセスで行われるため、スクロール イベントは非同期で配信されます。
- メインスレッドのアニメーションがジャンクの影響を受けやすい。
そのため、スクロールと同期して高パフォーマンスのスクロールドリブン アニメーションを作成することは不可能、または非常に困難になります。
Chrome バージョン 115 以降、宣言型スクロールドリブン アニメーションを有効にするために使用できる新しい API とコンセプト(スクロール タイムラインとビュー タイムライン)が導入されました。
これらの新しいコンセプトは、既存の Web Animations API(WAAPI)および CSS Animations API と統合されるため、既存の API の利点を継承できます。これには、スクロールドリブン アニメーションをメインスレッドの外で実行する機能も含まれます。数行のコードを追加するだけで、メインスレッドから離れるスクロールによって動作し、滑らかなアニメーションを実現できるようになりました。気に入らないところはないか?
ウェブでのアニメーション(簡単なまとめ)
CSS を使用したウェブ上でのアニメーション
CSS でアニメーションを作成するには、@keyframes
ルールを使用してキーフレーム セットを定義します。animation-name
プロパティを使用して要素を要素にリンクしつつ、animation-duration
を設定してアニメーションの所要時間を決定します。animation-*
の短縮形のプロパティには他にも animation-easing-function
と animation-fill-mode
があり、これらをすべて animation
の短縮形に統合できます。
たとえば、次のアニメーションでは、X 軸で要素をスケールアップしながら、背景色も変更しています。
@keyframes scale-up {
from {
background-color: red;
transform: scaleX(0);
}
to {
background-color: darkred;
transform: scaleX(1);
}
}
#progressbar {
animation: 2.5s linear forwards scale-up;
}
をご覧ください。
JavaScript を使用したウェブでのアニメーション
JavaScript では、Web Animations API を使用して、まったく同じことを実現できます。これを行うには、新しい Animation
インスタンスと KeyFrameEffect
インスタンスを作成するか、はるかに短い Element
animate()
メソッドを使用します。
document.querySelector('#progressbar').animate(
{
backgroundColor: ['red', 'darkred'],
transform: ['scaleX(0)', 'scaleX(1)'],
},
{
duration: 2500,
fill: 'forwards',
easing: 'linear',
}
);
上記の JavaScript スニペットの視覚的な結果は、前の CSS バージョンと同じです。
アニメーションのタイムライン
デフォルトでは、要素に適用されているアニメーションはドキュメント タイムラインで実行されます。ページの読み込み時にオリジン時刻は 0 から始まり、時計の時間の経過に伴って時間とともに進んでいきます。これはアニメーション タイムラインのデフォルトですが、これまでは、この他に利用できるアニメーション タイムラインはありませんでした。
スクロール駆動アニメーションの仕様では、使用できる 2 つの新しいタイプのタイムラインが定義されています。
- スクロール進行状況タイムライン: 特定の軸でスクロール コンテナのスクロール位置にリンクされたタイムライン。
- ビュー進行状況タイムライン: スクロール コンテナ内の特定の要素の相対位置にリンクされたタイムライン。
スクロール進行状況タイムライン
スクロール進行状況タイムラインは、特定の軸でのスクロール コンテナのスクロール位置の進行状況にリンクされるアニメーション タイムラインです(スクロールポート、スクローラーともいいます)。スクロール範囲内の位置を進行状況の割合に変換します。
スクロールの開始位置の進行状況は 0%、終了位置は 100% です。以下の可視化では、スクローラーを上から下にスクロールすると、進行状況が 0% から 100% でカウントされます。
<ph type="x-smartling-placeholder">✨ ぜひお試しください
スクロール進行状況タイムラインは通常、単に「スクロール タイムライン」と省略されます。
ビュー進行状況タイムライン
このタイプのタイムラインは、スクロール コンテナ内の特定の要素の相対的な進行状況にリンクしています。スクロール進行状況タイムラインと同様に、スクローラーのスクロール オフセットが追跡されます。スクロール進行状況タイムラインとは異なり、スクローラー内の対象の相対位置によって進行状況が決まります。
これは、スクローラーで要素がどの程度表示されているかをトラッキングできる IntersectionObserver
の仕組みと若干類似しています。要素がスクローラー内に表示されていなければ、要素は交差していません。わずかでもスクローラー内に表示されていれば、要素は交差しています。
ビュー進行状況タイムラインは、対象がスクローラーとの交差を開始した瞬間から開始し、スクローラーとの交差を停止すると終了します。以下の可視化では、対象がスクロール コンテナに入ると進行状況が 0% から始まり、対象がスクロール コンテナから離れた瞬間に 100% になります。
<ph type="x-smartling-placeholder">✨ ぜひお試しください
多くの場合、ビュー進行状況タイムラインは単に「ビュー タイムライン」と省略されます。被写体のサイズに基づいてビュー タイムラインの特定の部分をターゲットにすることもできますが、これについては後で詳しく説明します。
スクロール進行状況タイムラインの実践演習
CSS で匿名のスクロール進行状況タイムラインを作成する
CSS でスクロール タイムラインを作成する最も簡単な方法は、scroll()
関数を使用することです。これにより、匿名のスクロール タイムラインが作成され、新しい animation-timeline
プロパティの値として設定できます。
例:
@keyframes animate-it { … }
.subject {
animation: animate-it linear;
animation-timeline: scroll(root block);
}
scroll()
関数は、<scroller>
引数と <axis>
引数を受け入れます。
<scroller>
引数で使用できる値は次のとおりです。
nearest
: 最も近い祖先スクロール コンテナを使用します(デフォルト)。root
: ドキュメント ビューポートをスクロール コンテナとして使用します。self
: 要素自体をスクロール コンテナとして使用します。
<axis>
引数で使用できる値は次のとおりです。
block
: スクロール コンテナのブロック軸で進行状況を測定します(デフォルト)。inline
: スクロール コンテナのインライン軸で進行状況を測定します。y
: スクロール コンテナの Y 軸で進行状況を測定します。x
: スクロール コンテナの X 軸で進行状況を測定します。
たとえば、アニメーションをブロック軸のルート スクローラーにバインドする場合、scroll()
に渡す値は root
と block
です。まとめると、値は scroll(root block)
です。
デモ: 読み上げの進行状況インジケーター
このデモでは、ビューポートの上部に読書の進行状況インジケーターが固定されています。ページを下にスクロールすると進行状況バーが大きくなり、ドキュメントの最後に達したときにビューポートの幅いっぱいにまで表示されます。匿名のスクロール進行状況タイムラインを使用してアニメーションが動作します。
<ph type="x-smartling-placeholder">✨ ぜひお試しください
読書の進行状況インジケーターは、固定位置を使用してページの上部に配置されます。合成されたアニメーションを利用するため、width
がアニメーション化されるのではなく、transform
を使用して要素が x 軸で縮小されます。
<body>
<div id="progress"></div>
…
</body>
@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();
}
#progress
要素のアニメーション grow-progress
のタイムラインは、scroll()
を使用して作成された匿名のタイムラインに設定されます。scroll()
には引数が指定されていないため、デフォルト値にフォールバックします。
追跡するデフォルトのスクローラーは nearest
で、デフォルトの軸は block
です。これにより、ブロックの方向を追跡しながら、#progress
要素の最も近いスクローラーであるルート スクローラーを効果的にターゲットにできます。
CSS で名前付きスクロール進行状況タイムラインを作成する
また、名前付きのスクロール進行状況タイムラインを定義する方法もあります。やや冗長ですが、親スクローラーまたはルート スクローラーをターゲットとしていない場合、ページで複数のタイムラインを使用している場合、自動ルックアップが機能しない場合には便利です。この方法では、任意の名前でスクロール進行状況タイムラインを識別できます。
要素に名前付きスクロール進行状況タイムラインを作成するには、スクロール コンテナの scroll-timeline-name
CSS プロパティに任意の識別子を設定します。値は --
で始まる必要があります。
追跡する軸を調整するには、scroll-timeline-axis
プロパティも宣言します。使用可能な値は、scroll()
の <axis>
引数と同じです。
最後に、アニメーションをスクロール進行状況タイムラインにリンクします。アニメーションを再生する要素の animation-timeline
プロパティに、scroll-timeline-name
で使用した ID と同じ値を設定します。
コード例:
@keyframes animate-it { … }
.scroller {
scroll-timeline-name: --my-scroller;
scroll-timeline-axis: inline;
}
.scroller .subject {
animation: animate-it linear;
animation-timeline: --my-scroller;
}
必要に応じて、scroll-timeline
の省略形で scroll-timeline-name
と scroll-timeline-axis
を組み合わせることができます。例:
scroll-timeline: --my-scroller inline;
デモ: 水平カルーセルのステップ インジケーター
このデモでは、各画像カルーセルの上にステップ インジケーターが表示されます。カルーセルに 3 つの画像がある場合、インジケーター バーの幅が 33% から始まり、現在 3 つの画像のうちの 1 番目が表示されていることを示します。最後の画像がビュー内にあるとき(スクローラーが最後までスクロールしたかどうかによって判別)、インジケーターはスクローラーの幅いっぱいに表示されます。名前付きのスクロール進行状況タイムラインを使用してアニメーションが動作します。
<ph type="x-smartling-placeholder">✨ ぜひお試しください
ギャラリーの基本マークアップは次のとおりです。
<div class="gallery" style="--num-images: 2;">
<div class="gallery__scrollcontainer">
<div class="gallery__progress"></div>
<div class="gallery__entry">…</div>
<div class="gallery__entry">…</div>
</div>
</div>
.gallery__progress
要素は .gallery
ラッパー要素内に絶対的に配置されます。初期サイズは、--num-images
カスタム プロパティによって決まります。
.gallery {
position: relative;
}
.gallery__progress {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 1em;
transform: scaleX(calc(1 / var(--num-images)));
}
.gallery__scrollcontainer
は、含まれている .gallery__entry
要素を水平方向にレイアウトし、スクロールする要素です。スクロール位置をトラッキングすることで、.gallery__progress
をアニメーション化します。これは、名前付きのスクロール進行状況タイムライン --gallery__scrollcontainer
を参照することによって行われます。
@keyframes grow-progress {
to { transform: scaleX(1); }
}
.gallery__scrollcontainer {
overflow-x: scroll;
scroll-timeline: --gallery__scrollcontainer inline;
}
.gallery__progress {
animation: auto grow-progress linear forwards;
animation-timeline: --gallery__scrollcontainer;
}
JavaScript を使用してスクロール進行状況タイムラインを作成する
JavaScript でスクロール タイムラインを作成するには、ScrollTimeline
クラスの新しいインスタンスを作成します。トラッキングする source
と axis
を含むプロパティ バッグを渡します。
source
: スクローラーをトラッキングする要素への参照。document.documentElement
を使用して、ルート スクローラーをターゲットにします。axis
: 追跡する軸を決定します。CSS バリエーションと同様に、指定できる値はblock
、inline
、x
、y
です。
const tl = new ScrollTimeline({
source: document.documentElement,
});
ウェブ アニメーションにアタッチするには、これを timeline
プロパティとして渡し、duration
が存在する場合は省略します。
$el.animate({
opacity: [0, 1],
}, {
timeline: tl,
});
デモ: 読書の進捗状況インジケーター(再確認)
JavaScript で読み上げの進行状況インジケーターを同じマークアップで再作成するには、次の JavaScript コードを使用します。
const $progressbar = document.querySelector('#progress');
$progressbar.style.transformOrigin = '0% 50%';
$progressbar.animate(
{
transform: ['scaleX(0)', 'scaleX(1)'],
},
{
fill: 'forwards',
timeline: new ScrollTimeline({
source: document.documentElement,
}),
}
);
見た目は CSS バージョンでも同じです。作成された timeline
はルート スクローラーをトラッキングし、ページをスクロールすると x 軸で #progress
を 0% から 100% に拡大します。
✨ ぜひお試しください
ビュー進行状況タイムラインの演習
CSS で匿名ビュー進行状況タイムラインを作成する
ビュー進行状況タイムラインを作成するには、view()
関数を使用します。使用できる引数は <axis>
と <view-timeline-inset>
です。
<axis>
はスクロール進行状況タイムラインと同じで、追跡する軸を定義します。デフォルト値はblock
です。<view-timeline-inset>
では、オフセット(正または負)を指定して、要素がビュー内にある(またはない)とみなされる境界を調整できます。値は割合またはauto
で指定する必要があります。デフォルト値はauto
です。
たとえば、ブロック軸でスクローラーと交差する要素にアニメーションをバインドするには、view(block)
を使用します。scroll()
と同様に、これを animation-timeline
プロパティの値として設定します。必ず animation-duration
を auto
に設定してください。
次のコードを使用すると、スクロール中にすべての img
がビューポートを横切るときにフェードインします。
@keyframes reveal {
from { opacity: 0; }
to { opacity: 1; }
}
img {
animation: reveal linear;
animation-timeline: view();
}
Intermezzo: タイムラインの範囲を表示
デフォルトでは、ビュー タイムラインにリンクされているアニメーションは、タイムライン範囲全体に付加されます。対象がスクロールポートに入った瞬間から開始し、スクロールポートを完全に離れた時点で終了します。
ビュー タイムラインの特定の部分にリンクすることも可能で、その場合は適用する範囲を指定します。たとえば、対象がスクローラーに入った瞬間にリンクできます。以下の例では、対象がスクロール コンテナに入り始めたら進行状況が 0% で開始し、完全に交差した瞬間に 100% になります。
<ph type="x-smartling-placeholder">ターゲットに設定できるビュー タイムラインの範囲は次のとおりです。
cover
: ビュー進行状況タイムラインの全範囲を表します。entry
: プリンシパル ボックスがビュー進行状況の表示範囲に入っている状態の範囲を表します。exit
: プリンシパル ボックスがビュー進行状況の表示範囲から出ている途中の状態の範囲を表します。entry-crossing
: プリンシパル ボックスが終了境界エッジと交差している状態の範囲を表します。exit-crossing
: プリンシパル ボックスが開始境界エッジと交差している状態の範囲を表します。contain
: プリンシパル ボックスがスクロールポート内のビュー進行状況の表示範囲に完全に含まれているか、完全にカバーしている範囲を表します。これは、対象がスクローラーよりも長いか短いかによって変わります。
範囲を定義するには、range-start と range-end を設定する必要があります。各要素は、範囲名(上記のリストを参照)と、その範囲名内の位置を決定する範囲オフセットで構成されます。範囲オフセットは通常、0%
~100%
の範囲の割合ですが、20em
などの固定長を指定することもできます。
たとえば、対象が入った瞬間からアニメーションを実行する場合は、範囲の開始値として entry 0%
を選択します。対象が入力されるまでに終了するには、範囲終了の値として entry 100%
を選択します。
CSS では、animation-range
プロパティを使用して設定します。例:
animation-range: entry 0% entry 100%;
JavaScript では、rangeStart
プロパティと rangeEnd
プロパティを使用します。
$el.animate(
keyframes,
{
timeline: tl,
rangeStart: 'entry 0%',
rangeEnd: 'entry 100%',
}
);
以下の埋め込みツールを使用して、それぞれの範囲名が何を表しているか、割合が開始位置と終了位置にどのように影響するかを確認してください。範囲の開始を entry 0%
に、範囲の終了を cover 50%
に設定し、スクロールバーをドラッグしてアニメーションの結果を確認します。
録画を視聴する
このビュー タイムライン範囲ツールを試しているとお気づきのように、一部の範囲は、2 つの異なる範囲名と範囲とオフセットの組み合わせでターゲットに設定できます。たとえば、entry 0%
、entry-crossing 0%
、cover 0%
はすべて同じエリアを対象とします。
範囲の始点と範囲の終点が同じ範囲名を対象とし、範囲全体(0% ~ 100%)に及ぶ場合は、値を短縮して範囲名にすることができます。たとえば、animation-range: entry 0% entry 100%;
ははるかに短い animation-range: entry
に書き換えられます。
デモ: 画像の表示
このデモでは、画像がスクロールポートに入ると、画像がフェードインします。これは、匿名ビュー タイムラインを使用して行われます。アニメーションの範囲が微調整され、スクローラーの半分に達したときに各画像が最大不透明度になります。
<ph type="x-smartling-placeholder">✨ ぜひお試しください
展開効果は、アニメーション化されたクリップパスを使用することで実現されます。この効果に使用される CSS は次のとおりです。
@keyframes reveal {
from { opacity: 0; clip-path: inset(0% 60% 0% 50%); }
to { opacity: 1; clip-path: inset(0% 0% 0% 0%); }
}
.revealing-image {
animation: auto linear reveal both;
animation-timeline: view();
animation-range: entry 25% cover 50%;
}
CSS で名前付きビュー進行状況タイムラインを作成する
スクロール タイムラインに名前付きバージョンがあるのと同様に、名前付きビュー タイムラインを作成することもできます。scroll-timeline-*
プロパティの代わりに、view-timeline-
プレフィックスを持つバリエーション、つまり view-timeline-name
と view-timeline-axis
を使用します。
同じ種類の値が適用され、名前付きタイムラインの検索と同じルールが適用されます。
デモ: 画像の表示(再確認)
先ほどの画像表示のデモを再構築すると、修正したコードは次のようになります。
.revealing-image {
view-timeline-name: --revealing-image;
view-timeline-axis: block;
animation: auto linear reveal both;
animation-timeline: --revealing-image;
animation-range: entry 25% cover 50%;
}
view-timeline-name: revealing-image
を使用すると、要素は最も近いスクローラー内で追跡されます。同じ値が animation-timeline
プロパティの値として使用されます。視覚的な出力は以前とまったく同じです。
✨ ぜひお試しください
JavaScript でビュー進行状況タイムラインを作成する
JavaScript でビュー タイムラインを作成するには、ViewTimeline
クラスの新しいインスタンスを作成します。トラッキングする subject
、axis
、inset
を含むプロパティ バッグを渡します。
subject
: 自身のスクローラー内でトラッキングする要素への参照。axis
: 追跡する軸。CSS バリエーションと同様に、指定できる値はblock
、inline
、x
、y
です。inset
: ボックスが表示中かどうかを判別する際のスクロールポートのインセット(正)またはアウトセット(負)。
const tl = new ViewTimeline({
subject: document.getElementById('subject'),
});
ウェブ アニメーションにアタッチするには、これを timeline
プロパティとして渡し、duration
が存在する場合は省略します。必要に応じて、rangeStart
プロパティと rangeEnd
プロパティを使用して範囲情報を渡します。
$el.animate({
opacity: [0, 1],
}, {
timeline: tl,
rangeStart: 'entry 25%',
rangeEnd: 'cover 50%',
});
✨ ぜひお試しください
その他のおすすめ
1 つのキーフレーム セットで複数のビュー タイムライン範囲に適用する
この連絡先リストのデモを見てみましょう。ここでは、リストのエントリがアニメーション化されています。リストエントリが下からスクロールポートに入ると、スライドしてフェードインします。一番上でスクロールポートを出ると、スライドしてフェードアウトします。
<ph type="x-smartling-placeholder">✨ ぜひお試しください
このデモでは、各要素が 1 つのビュー タイムラインで装飾されます。ビュー タイムラインは、スクロールポートを通過するときに要素を追跡する一方で、2 つのスクロールドリブン アニメーションがアタッチされます。animate-in
アニメーションはタイムラインの entry
範囲にアタッチされ、animate-out
アニメーションはタイムラインの exit
範囲にアタッチされます。
@keyframes animate-in {
0% { opacity: 0; transform: translateY(100%); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes animate-out {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-100%); }
}
#list-view li {
animation: animate-in linear forwards,
animate-out linear forwards;
animation-timeline: view();
animation-range: entry, exit;
}
2 つの異なる範囲にアタッチされた 2 つの異なるアニメーションを実行する代わりに、すでに範囲情報を含む 1 つのキーフレーム セットを作成することもできます。
@keyframes animate-in-and-out {
entry 0% {
opacity: 0; transform: translateY(100%);
}
entry 100% {
opacity: 1; transform: translateY(0);
}
exit 0% {
opacity: 1; transform: translateY(0);
}
exit 100% {
opacity: 0; transform: translateY(-100%);
}
}
#list-view li {
animation: linear animate-in-and-out;
animation-timeline: view();
}
キーフレームには範囲情報が含まれているため、animation-range
を指定する必要はありません。結果は以前とまったく同じです。
✨ ぜひお試しください
祖先以外のスクロール タイムラインへのアタッチ
名前付きスクロール タイムラインと名前付きビュー タイムラインの検索メカニズムは、スクロール祖先のみに制限されます。ただし、多くの場合、アニメーション化する必要がある要素は、追跡する必要があるスクローラーの子ではありません。
これを行うために、timeline-scope
プロパティが役立ちます。このプロパティを使用すると、タイムラインを実際に作成せずに、その名前でタイムラインを宣言できます。これにより、その名前のタイムラインの範囲が広がります。実際には、子スクローラーのタイムラインをアタッチできるように、共有の親要素で timeline-scope
プロパティを使用します。
例:
.parent {
timeline-scope: --tl;
}
.parent .scroller {
scroll-timeline: --tl;
}
.parent .scroller ~ .subject {
animation: animate linear;
animation-timeline: --tl;
}
このスニペットでは:
.parent
要素は、--tl
という名前のタイムラインを宣言します。そのすべての子は、このプロパティを見つけてanimation-timeline
プロパティの値として使用できます。.scroller
要素は、実際には--tl
という名前のスクロール タイムラインを定義しています。デフォルトでは子にのみ表示されますが、.parent
がscroll-timeline-root
として設定しているため、アタッチされます。.subject
要素は--tl
タイムラインを使用します。祖先ツリーを遡って.parent
で--tl
を見つけます。.parent
の--tl
が.scroller
の--tl
を指す場合、.subject
は基本的に.scroller
のスクロール進行状況タイムラインを追跡します。
言い換えると、timeline-root
を使用してタイムラインを祖先まで移動(ホイスティング)すると、その祖先のすべての子がタイムラインにアクセスできるようになります。
timeline-scope
プロパティは、スクロール タイムラインとビュー タイムラインの両方で使用できます。
その他のデモとリソース
この記事で取り上げたすべてのデモは、scroll-driven-アニメーション.style ミニサイトにあります。ウェブサイトには、スクロールドリブン アニメーションでできることを紹介するデモが他にも多数用意されています。
追加のデモの 1 つは、このアルバムカバーのリストです。中央のスポットライトを捉えると、カバーが 3D で回転します。
<ph type="x-smartling-placeholder">✨ ぜひお試しください
または、position: sticky
を活用するこのスタッキング カードのデモ。カードが積み重なると、すでにスタックしたカードがスケールダウンされ、奥行き効果が生まれます。最終的に、スタック全体がグループとしてビューの外にスライドアウトします。
✨ ぜひお試しください
また、 scroll-driven-アニメーション.style では、この投稿ですでに紹介したビュー タイムラインの範囲の可視化などのツールのコレクションが紹介されています。
スクロールドリブン アニメーションについては、Google I/O 2023 のウェブ アニメーションの新機能でも取り上げています。