CSS スクロールトリガーアニメーションの使用
CSS のスクロールトリガーアニメーションは、ユーザーが要素(または別の要素)をスクロールポート内の指定位置までスクロールしたときに、その要素の DocumentTimeline ベースの CSS アニメーションを開始、一時停止、停止、または逆再生する宣言的な仕組みを提供します。
この記事では、CSS スクロールトリガーアニメーションの作成方法を説明します。
スクロールトリガーアニメーションの概念
よくある UI パターンとして、ユーザーがコンテンツ内の特定の位置までスクロールしたときに、ウェブページ上のアニメーションを起動する方法があります。例えば、追加の UI 要素を表示したり、特定の詳細にユーザーの注意を向けたりできます。
CSS スクロールトリガーアニメーションを使うと、通常の時間ベースの CSS アニメーションを開始および停止する、スクロールベースのトリガーを定義できます。スクロールコンテナー内にトリガー位置を定義すると、追跡対象の要素がスクロールポート内のその位置に達したときに、その要素またはまったく別の要素に適用されたアニメーションの再生状態を切り替えられます。
メモ: スクロールトリガーアニメーションは、スクロール時にアニメーションを起動するために、フレームワークや交差オブザーバー API などの JavaScript 機能を使う方法に代わるものです。CSS スクロールトリガーアニメーションはパフォーマンスに優れ、実装もより簡単と言えるでしょう。
スクロールトリガーアニメーションとスクロール駆動アニメーションの比較
スクロールトリガーアニメーションは CSS スクロール駆動アニメーションと似ていますが、次の点が異なります。
- スクロールトリガーアニメーションは、トリガーが有効になると再生される通常の時間ベースのアニメーションです。アニメーションの開始ごとに
animation-delayが適用され、ユーザーのスクロール速度にかかわらず、各反復はanimation-durationで定義された時間で必ず完了します。 - スクロール駆動アニメーションでは、通常の時間ベースのアニメーションタイムラインがスクロールベースのタイムラインに置き換わります。そのため、コンテンツの先頭や末尾に向けてスクロールするとアニメーションがそれぞれ順方向または逆方向に進み、速くスクロールするほどアニメーションも速くなります。スクロール駆動アニメーションは
animation-durationとanimation-delayプロパティを無視します。
スクロールトリガーアニメーションの基本
スクロールトリガーアニメーションの動作を示す基本的な例を見てみましょう。画像が表示領域に入ったり、そこから出たりすると、その画像のキャプションがフェードインまたはフェードアウトします。この例では次のようにします。
<figcaption>要素に@keyframesアニメーション、つまりフェードイン効果を設定します。このアニメーションがトリガーされるアニメーションです。- アニメーションアクションとして、トリガーが有効化されたときにアニメーションを順方向に再生してキャプションをフェードインし、トリガーが無効化されたときに逆方向に再生してキャプションをフェードアウトするよう指定します。
- アニメーショントリガーは
<img>要素に定義します。<img>がスクロールポートに入り始めると有効化され、スクロールポートから完全に出ると無効化されます。つまり、スクロールポート全体がタイムライン範囲、<img>が追跡対象の要素、<figcaption>がアニメーション対象の要素です。 view()関数で作成した無名ビュー進行タイムラインを、<img>要素のトリガーソースに設定します。
コンテンツを上下にスクロールすると、<img> がスクロールポート内に現れ始めた時点でキャプションのアニメーションが始まり、<img> がスクロールポートから出るとアニメーションが逆再生されます。この基本的な例では望みどおりの効果にはなりませんが、ほかの機能を学びながら改善していくためのよい出発点です。
HTML には複数の段落があり、その途中に <figure> 要素があり、その中に <img> と <figcaption> を含みます。簡潔にするため、ソース全体は示していません。
...
<p>...</p>
<figure>
<img
src="jungle-coast.jpg"
alt="木々の向こうに岩場の海岸を望む景色" />
<figcaption>ジャングルの海岸の景色</figcaption>
</figure>
<p>...</p>
...
まず、<figcaption> に適用する fade-in アニメーションの @keyframes を定義します。
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
最初の宣言ブロックでは、アニメーション、アニメーショントリガー、およびアニメーションアクションを <figcaption> 要素に適用します。
animation一括指定を使って、fade-inアニメーションを<figcaption>に適用します。トリガーがなければ、ページの読み込み直後に<figcaption>がフェードインします。animation-triggerプロパティを使い、どの要素がトリガーを提供するか、およびトリガーのアクションを指定して、<img>要素が表示領域にスクロールされるまでアニメーションの開始を遅らせます。animation-triggerの値は次の成分で構成されます。<dashed-ident>値--t。トリガー要素のtimeline-trigger-nameプロパティの値として設定する識別子です。<animation-action>値 2 つ。トリガーの有効化時と無効化時にアニメーションがどう動作するか(アニメーションアクション)を指定します。トリガーが有効化されたときは<figcaption>のアニメーションを順方向に再生し、無効化されたときは逆方向に再生します。
figcaption {
animation: fade-in 1s ease-in both;
animation-trigger: --t play-forwards play-backwards;
}
2 つ目の宣言ブロックで、アニメーショントリガーを作成します。
timeline-trigger-nameプロパティを使い、<img>要素に作成するトリガーの識別名を指定します。これは、<figcaption>要素のanimation-trigger値で参照するトリガー名と同じ二重ダッシュ付き識別子です。timeline-trigger-sourceプロパティを使って、アニメーショントリガーの種類を作成します。view()関数を指定すると、トリガーの種類は無名ビュー進行タイムラインになります。そのデフォルトの有効化範囲はcoverのタイムライン範囲と同じです。
img {
timeline-trigger-name: --t;
timeline-trigger-source: view();
}
デフォルトでは、view() 関数で作成される ViewTimeline は、最も近い親スクローラーのブロック軸に沿って <img> 要素の位置を追跡します。この例で追跡される <img> 要素を主体または追跡対象の要素と呼びます。
デフォルトでは、ブロック方向に追跡対象の要素がタイムライン範囲の先頭までスクロールされるとトリガーが有効化され、末尾までスクロールされると無効化されます。これにより、<figcaption> のアニメーションは順方向および逆方向に再生されます。追跡対象の要素が有効化範囲に入ると有効化され、作動範囲から出ると無効化されます。timeline-trigger-source を view() に設定した場合、デフォルトの有効化範囲と作動範囲は cover になります。これは、追跡対象の要素の先頭側の境界線がスクロールポートに入り始めてから、末尾側の境界線が完全にスクロールポートから出るまでの範囲です。
この例は次のように描画されます。
画像を下側または上側からスクロールしてくると、画像の一部がスクロールポート内に見えた時点で、すぐにキャプションがフェードインします。画像全体がスクロールポートから出るまではフェードアウトしないため、フェードアウト効果は確認できません。画像を再び表示領域内にスクロールすると、キャプションはもう一度フェードインします。
同じ要素でのトリガーの作成
前の例では、トリガーは <img> 要素に定義し、<figcaption> をアニメーションさせました。アニメーション対象の要素自体にトリガーを定義することもできます。前の例を変更して、アニメーション対象の <figcaption> 要素にトリガーを作成してみましょう。
HTML は前の例と同じです。CSS は timeline-trigger-* プロパティを設定する場所だけが異なります。
今回は、animation、animation-trigger、timeline-trigger-name、timeline-trigger-source の各プロパティをすべて <figcaption> 要素に設定します。これにより、<figcaption> がスクロールポートに現れたときにアニメーションします。前の例では <figcaption> がアニメーション対象、<img> が追跡対象でしたが、今度はキャプションが両方の役割を担います。
figcaption {
animation: fade-in 1s ease-in both;
animation-trigger: --t play-forwards play-backwards;
timeline-trigger-name: --t;
timeline-trigger-source: view();
}
更新後の描画は次のようになります。
この場合、画像ではなく <figcaption> 自体が初めてスクロールポートに入ったときにフェードインします。
トリガーの有効化範囲の調整
前の例では、追跡対象の要素のブロック辺がスクロールポートの一方の端から入り始めるとトリガーが有効化され(fade-in が始まり)、反対側の端から完全に出ると無効化されます(fade-in が逆再生され、フェードアウトが始まります)。その結果、フェードアウトは表示されません。これは、view() を timeline-trigger-source に指定したとき、デフォルトの有効化範囲と作動範囲(<timeline-range-name> を参照)がどちらも cover になるためです。
フェードアウトアニメーションを表示するには、timeline-trigger-activation-range-start プロパティと timeline-trigger-activation-range-end プロパティで有効化範囲の先頭と末尾をそれぞれずらすか、timeline-trigger-activation-range 一括指定で両方の値を 1 つの宣言に設定します。これらのプロパティには次の値を指定できます。
- デフォルト値の
normal。 - デフォルト範囲内の位置を指定する
<length-percentage>値。 - 名前付き範囲を指定する
<timeline-range-name>キーワード。 - 名前付き範囲内の位置を指定する
timeline-range-nameと<length-percentage>。
パーセント値は <timeline-range-name> の長さを基準とします。この例のビュー進行タイムラインでは cover に解決されます。timeline-trigger-source に scroll()を設定した場合、デフォルトの <timeline-range-name> は scroll に解決されます。<timeline-range-name> の値についてはタイムライン範囲名を参照してください。
次の例では、entry 範囲の途中、つまり追跡対象の要素の 50% がスクロールポートのブロック辺の一方から入った時点でトリガーが有効化されます。また、exit 範囲の 0% の位置、つまり追跡対象の要素の 50% が反対側のブロック辺からスクロールポートの外に出た時点で無効化されます。
timeline-trigger-activation-range: entry 50% exit 0%;
効果を確認できるよう、最初の例にこの指定を適用してみましょう。img の宣言ブロックを次のように更新します。
img {
timeline-trigger-name: --t;
timeline-trigger-source: view();
timeline-trigger-activation-range: entry 50% exit 0%;
}
更新後の描画は次のようになります。
<figcaption> のアニメーションが少し実用的になりました。<img> の大部分が末尾側の端からスクロールポートに入ったときにのみフェードインが始まり、<img> が先頭側の端からスクロールポートの外へ出始めたときにフェードアウトが始まります。コンテンツを再び下方向へスクロールすると、トリガーが再有効化され、スクロールポートの先頭側の端で再びフェードインし、末尾側の端で再び無効化されます。
独自の作動範囲の設定
作動範囲は、トリガーが有効化された後、有効な状態を維持する範囲です。デフォルトでは作動範囲は有効化範囲と同じです。そのため、これまでの例では有効化と無効化の範囲は同じでした。
timeline-trigger-active-range-start プロパティと timeline-trigger-active-range-end プロパティを使うと、有効化範囲とは異なる作動範囲を設定できます。timeline-trigger-active-range 一括指定を使えば、両方の値を 1 つの宣言で設定できます。
例えば、アニメーショントリガーは狭い有効化範囲内でのみ有効化しつつ、より広い作動範囲にわたって有効な状態を保ちたい場合など、アニメーションが完了するまでの時間を延ばすために使えます。追跡対象の要素が作動範囲の外に出たときにのみトリガーは無効になります。その後、主体を有効化範囲内に戻すと、再び有効化できます。
作動範囲の効果を示すため、これまでの例を発展させてみましょう。HTML は同じですが、クラス .one と .two を持つ同一の <figure> 要素を 2 つ用意し、フレックスボックスを使って横に並べています。そのため、両方が同時に表示領域に出入りします。どちらの場合も、<img> が兄弟のアニメーション対象 <figcaption> の追跡対象になります。
<div class="figure-wrapper">
<figure class="one">
<img
src="https://mdn.github.io/shared-assets/images/examples/learn/gallery/pic5.jpg"
alt="葉の上にとまり、赤、白、金色の模様がある羽を持つ蝶" />
<figcaption>ケアンズ近くのジャングルで見られる美しい蝶</figcaption>
</figure>
<figure class="two">
<img
src="https://mdn.github.io/shared-assets/images/examples/learn/gallery/pic5.jpg"
alt="葉の上にとまり、赤、白、金色の模様がある羽を持つ蝶" />
<figcaption>ケアンズ近くのジャングルで見られる美しい蝶</figcaption>
</figure>
</div>
前の例と同じ animation を両方の <figcaption> 要素に適用しますが、それぞれの animation-trigger プロパティ値では異なる timeline-trigger-name 値を参照します。
figcaption {
animation: fade-in 0.4s ease-in both;
}
.one figcaption {
animation-trigger: --t1 play-forwards play-backwards;
}
.two figcaption {
animation-trigger: --t2 play-forwards play-backwards;
}
両方の <img> 要素に同じ timeline-trigger-source と timeline-trigger-activation-range を設定します。それぞれの <img> 要素の timeline-trigger-name は、前のコードブロックで指定した 2 つの異なる二重ダッシュ付き識別子を参照します。つまり、各 <img> に作成されたトリガーが、それぞれの兄弟 <figcaption> のアニメーションを起動します。
timeline-trigger-activation-range: contain 40% contain 60% 宣言は、追跡対象の要素が狭い範囲、つまりスクロールポートの中央 20% に達したときにトリガーが有効化され、アニメーションが再生されることを意味します。主体がこの狭い範囲から出ると、トリガーは無効化されます。
デフォルトでは、有効化範囲と作動範囲は同じです。ただし、2 つ目の <img> には timeline-trigger-active-range に entry 50% exit 100% の範囲も設定しています。これにより、2 つ目の <figcaption> は一度フェードインすると、2 つ目の <img> が exit 100% までスクロールされたとき、つまりスクロールポートから完全に出たときにのみ、再びフェードアウトします。
img {
timeline-trigger-source: view();
timeline-trigger-activation-range: contain 40% contain 60%;
}
.one img {
timeline-trigger-name: --t1;
}
.two img {
timeline-trigger-name: --t2;
timeline-trigger-active-range: entry 50% exit 100%;
}
メモ:
効果を得るには、timeline-trigger-active-range を timeline-trigger-activation-range より広い範囲に設定する必要があります。
この例は次のように描画されます。
画像を表示領域に入れた後、注意しながら上下にスクロールしてみてください。埋め込みページの下からおよそ 3 分の 1 の位置で、両方のキャプションが同時にフェードインします。1 つ目のキャプションは少し上にスクロールするとフェードアウトしますが、2 つ目は完全にスクロールポートの外に出るまでフェードアウトしません。これは、両方の <img> トリガーの有効化範囲は同じ一方、2 つ目には有効化範囲よりかなり広い作動範囲を適用しているためです。
timeline-trigger 一括指定
これまでは、各プロパティとその値を詳しく説明するため、スクロールトリガーアニメーションの CSS を一括指定と個別指定を組み合わせて記述してきました。しかし、この方法は煩雑で冗長です。概念を理解できたので、timeline-trigger 一括指定を使って、同じ内容を最短で記述できます。今後のプロジェクトでは、この一括指定を選ぶことになるでしょう。
次の宣言を例にします。
img {
timeline-trigger-name: --t;
timeline-trigger-source: view();
timeline-trigger-activation-range: contain 25% contain 75%;
timeline-trigger-active-range: entry 0% exit 100%;
}
これらは timeline-trigger 一括指定を使って、1 行の CSS に書き換えられます。
img {
timeline-trigger: --t view() contain 25% contain 75% / entry 0% exit 100%;
}
アニメーションのアクションの調整
このガイドのこれまでの例では、fade-in アニメーションをトリガーし、キャプションをフェードインおよびフェードアウトさせてきました。順方向または逆方向への再生は、アニメーション対象の要素に設定した animation-trigger 宣言で制御します。
animation-trigger: --t play-forwards play-backwards;
<animation-action> 値 play-forwards と play-backwards は、トリガーが有効化されたときはアニメーションを順方向に、無効化されたときは逆方向に再生することを指定します。最初の値はアニメーションの有効化アクション、2 つ目の値は無効化アクションです。
同じ要素に次の animation 宣言を設定すると、
animation: fade-in 1s ease-in both;
トリガーが有効化されたときにアニメーションが 1 回だけ再生され、無効化されたときに逆方向に 1 回再生されます。animation 一括指定で animation-iteration-count を指定していないため、デフォルト値の 1 が使われます。
ほかの animation-action 値を設定すると、異なる効果を得ることができます。例えば、
play-onceはアニメーションを 1 回だけ再生します。再生が終わると、その後に有効化または無効化されても再生されません。playは、直前に再生していた方向でアニメーションを再生します。一方、play-forwardsとplay-backwardsは、アニメーションのplaybackRateと連動し、それぞれ絶対値の正の値、またはその値に-1を掛けた値を設定します。その結果、アニメーションは順方向または逆方向に再生されます。animation-directionの値には影響しません。pauseはアニメーションを一時停止します。例えば、アニメーションの反復回数をinfiniteにし、対応するanimation-triggerに--t play pauseを設定できます。これにより、トリガーが有効化されたときにアニメーションが再生され、無効化されたときに一時停止します。resetはpauseと同じ効果に加えて、アニメーションの進行状況を0に戻します。
これらの値の中には、組み合わせて使うよう設計されたものもあります。例えば play-forwards play-backwards は、アニメーションの最終的な視覚効果で再生方向を交互にし、UI 要素が画面に現れるときに「アニメーションで表示」し、画面外に出るときに再び「アニメーションで非表示」にしたい場合に使います。一方、play pause は、要素が現れるときにアニメーションさせ、画面外に出始めたときに一時停止する用途でよく使われます。
簡単な例を見てみましょう。最初の例を変更し、<figure> がスクロールポートに完全に入ったときに 1 回だけフェードインし、ページが再読み込みされるまではフェードアウトも再アニメーションもしないようにします。
animation-trigger プロパティの animation-action に play-once を設定すると、<figure> が初めて有効化範囲に入ったときにのみアニメーションが再生されます。また、timeline-trigger-activation-range を contain に設定し、<figure> が画面内に完全に入ったときだけアニメーションするようにします。再生は 1 回だけなので、見逃さないようにします。
figure {
animation: fade-in 1s ease-in both;
animation-trigger: --t play-once;
timeline-trigger: --t view() contain;
}
この例は次のように描画されます。
最初に <figure> を画面内にスクロールすると、フェードインします。その後は、スクロールポート内で何度上下にスクロールしても、不透明度 100% のままです。再びフェードインさせるには、ページを更新するか、埋め込み例の <iframe> を再読み込みする必要があります。
トリガーのスコープ
複数のトリガーが同じ timeline-trigger-name を使う場合、デフォルトではブラウザーがトリガーを決定する方法により、その値を持つ HTML ソース順で最後の要素にトリガーが関連付けられます。これは望ましい動作ではないでしょう。
例えば、スクロールトリガーアニメーションを含む同じコンポーネントが文書内に複数あり、それぞれのアニメーション対象と追跡対象が異なる要素だとします。コンポーネントごとに異なる timeline-trigger-name を使うか、サブツリーで名前のスコープを限定しない限り、すべてのアニメーション対象要素のアニメーションが、最後のコンポーネントのトリガーによって制御されます。
trigger-scope プロパティは、timeline-trigger-name 値の可視性、つまり「スコープ」を特定のサブツリーに限定します。その結果、各アニメーション対象要素は、同じスコープのサブツリー内に作成されたトリガーによってのみアニメーションを起動できます。詳しくは trigger-scope リファレンスページと、trigger-scope の例を参照してください。
複数のスクロールトリガーアニメーション
これまでの例では、1 つの要素に単一のスクロールトリガーアニメーションを設定しました。しかし、このガイドで説明した animation-* および timeline-trigger-* プロパティはすべて、複数のトリガーから複数のアニメーションを起動できるよう、カンマ区切りの値リストを受け取ります。この節では、同じ要素に複数のスクロールトリガーアニメーションを設定する、少し複雑な例を作ります。
animation-trigger プロパティは、複数の値の設定に関して、animation 一括指定やほかの animation 個別指定プロパティとまったく同じように動作します。複数の animation-name 値が設定されているのに animation-trigger 値が 1 つだけの場合、その値がすべてのアニメーションに適用されます。animation-trigger 値が 2 つ設定されている場合は、すべてのアニメーションに値が設定されるまで、アニメーション間で順に繰り返し適用されます。
この例では、ページをスクロールして新しいトリガーが有効化されるたびに、追加のアニメーションを適用して要素を段階的にアニメーションさせます。ユーザーがスクロールすると、要素はまず画面の右側からスライドインし、次に内容を表示し、その後画面を下方向にスライドして背景色を変えます。
HTML はこれまでの例と似ていますが、強調表示するコンテンツを含む <section> 要素を先頭に追加し、メインコンテンツの途中に空の <div> 要素をいくつか挿入しています。これらの要素にトリガーを定義してアニメーションを起動します。
<section>
<h2>このコンテンツはアニメーションします!</h2>
<p>
オーストラリア東部に位置するケアンズの周辺地域は、多様な景観、豊かな緑、独特の自然の驚異に恵まれた、息をのむほど美しい地域です。
</p>
</section>
<h1>ケアンズの情報</h1>
...
最初、強調表示するコンテンツを含む <section> は画面外に隠れています。まず CSS で <section> 要素をスタイルし、position を fixed にして、スクロールポートの左上付近に配置します。また、アニメーションの開始時と戻り先となる初期スタイルを定義します。続いて animation 値を 3 つ設定し、<section> 要素を slide-from-right でスライドインさせ、次に内容を reveal し、最後に画面を slide-down させて背景色を変えます。それぞれのアニメーションに animation-trigger を設定し、異なるトリガーの有効化によって起動するようにします。
各アニメーションが完了した後も、その最終状態を維持したいので、アニメーションには適切な animation-fill-mode 値を、animation-trigger 値には適切な <animation-action> 値を設定することが重要です。最後のアニメーションには from キーフレームがないため、animation-fill-mode を both ではなく forwards にする必要がありました。
section {
position: fixed;
left: 1em;
top: 1em;
height: 240px;
background: red;
width: 400px;
transform-origin: top;
animation:
slide-from-right 1s both,
reveal 1s both,
slide-down 1s forwards;
animation-trigger:
--t1 play-forwards pause,
--t2 play-forwards pause,
--t3 play-forwards pause;
}
次に、<div> 要素にトリガーを作成し、それぞれの timeline-trigger-name を <section> 要素の animation-trigger プロパティ値で指定した識別子と一致させます。これにより、ユーザーがスクロールして追跡対象の <div> がスクロールポートに入るたびに、異なるアニメーションが有効化されます。この例では、追跡対象の要素は不可視です。有用なコンテンツを含まず、トリガーを作成するためだけに置かれています。
#one {
timeline-trigger: --t1 view();
}
#two {
timeline-trigger: --t2 view();
}
#three {
timeline-trigger: --t3 view();
}
最後に、先ほど <section> 要素の animation プロパティで参照したアニメーションの @keyframes を定義します。
@keyframes slide-from-right {
from {
translate: 400%;
}
to {
translate: 0;
}
}
@keyframes reveal {
from {
color: #ffffff00;
transform: scaleY(0.2);
}
to {
color: white;
transform: scaleY(1);
}
}
@keyframes slide-down {
to {
translate: 0 100%;
background: blue;
}
}
この例は次のように描画されます。
例を注意しながらスクロールしてみてください。それぞれの <div> までスクロールしたときに、各アニメーションがどのように <section> に適用されるか確認してください。
同じアニメーションに対する複数のトリガー
アニメーション対象の要素に対し、同じアニメーションを起動するトリガーを複数の異なる要素に定義したい場合は、アニメーション対象の要素に同じ名前のアニメーションを複数回指定し、それぞれのアニメーションに異なるトリガーを与える必要があります。詳しくは同じアニメーションに対する複数のトリガーを参照してください。
関連情報
- CSS アニメーショントリガーモジュール
- CSS アニメーションモジュール
- CSS スクロール駆動アニメーションモジュール
- ウェブアニメーション API の使用
- CSS のスクロールトリガーアニメーションが登場!(英語) - developer.chrome.com (2025)