画面を下にスクロールして目的のエリアに到達した瞬間、フワッと見出しが現れたり、画像がスライドインしたり、カード要素が順番に立ち上がったりする演出。
WebデザインやLP(ランディングページ)、ブランドサイトにおいて、ユーザーのスクロールに合わせて視線を惹きつける「スクロール連動アニメーション」は、もはや欠かすことのできない定番の手法です。
しかし従来のWeb制作では、これを実現するためにJavaScriptの Intersection Observer APIを使って交差判定を行ったり、GSAP(ScrollTrigger)などの外部ライブラリを読み込んでクラスを付け替えたりするのが必須でした。ライブラリの読み込みコストや、JSとCSS間の状態管理に煩わしさを感じていたエンジニアも多いはずです。
そんな長年の開発フローを根本から変える注目のWeb標準仕様が、Chrome・Edge 146よりサポートされた「CSSスクロールトリガーアニメーション(Scroll-triggered Animations)」です!
JavaScriptを1行も書くことなく、CSSの新プロパティである timeline-trigger と animation-trigger を指定するだけで、スクロール位置をきっかけにしたアニメーション発火・逆再生・再実行がCSSネイティブで完結します。
本記事では、スクロール駆動(Scroll-driven)とスクロールトリガー(Scroll-triggered)の違いから、基本構文、有効範囲の指定方法、1文字ずつのテキスト表示や複数要素の連動、離れた要素との同期テクニック、そして対応ブラウザ状況まで、現役フロントエンドエンジニア向けに余すところなく徹底解説します!
1. 「スクロール駆動」と「スクロールトリガー」の決定的な違い
CSSのスクロールアニメーションを学ぶにあたり、まず整理しておきたいのが以下の2つの概念の違いです。
| 種類 | 挙動・特徴 | 主なCSSプロパティ | 代表的なユースケース |
|---|---|---|---|
| スクロール駆動 (Scroll-driven) |
スクロールした「移動量そのもの」に進捗が1対1で完全連動する(スクラブ再生)。 | animation-timeline: scroll() / view(); |
読了率プログレスバー、パララックス背景、3Dモデルの回転 |
| スクロールトリガー (Scroll-triggered) |
特定のスクロール位置に到達したことを「きっかけ」に、時間ベースのアニメーションが自動再生される。 | timeline-triggeranimation-trigger |
画面イン時のフェードイン、見出しの立ち上がり、要素の順次表示 |
本記事で扱うのは後者の「スクロールトリガーアニメーション」です。スクロールの手を止めても通常のアニメーション(1秒かけてフェードインするなど)が最後まで自律して走り抜けるため、UIパーツを印象的に登場させたい場面に最も適しています。
2. 基本構文:timeline-trigger と animation-trigger の仕組み
スクロールトリガーアニメーションは、主に2つのCSSプロパティをペアで指定して実装します。
timeline-trigger:トリガー名と、有効化する判定基準・スクロール範囲を定義する。animation-trigger:定義されたトリガー名を参照し、有効時・無効時の再生方法を紐付ける。
基本の実装コード例
以下の例では、対象要素がビューポート(画面)に完全に入ると右からスライドインし、画面外に出ると元の位置へと逆再生して戻ります。
/* 右からスライドしながらフェードインするキーフレーム */
@keyframes motion {
from {
opacity: 0;
translate: 100px 0;
}
}
.target {
opacity: 1;
translate: 0 0;
/* ① トリガーの定義: 要素全体が画面に入ると有効化、完全に出ると無効化 */
timeline-trigger: --target view() contain / cover;
/* 通常の時間ベースのアニメーション(1秒かけてイージング) */
animation: motion 1s var(--ease-expo-out) both;
/* ② アニメーションの紐付け: 有効時は順再生、無効時は逆再生 */
animation-trigger: --target play-forwards play-backwards;
}
構文のパラメータ解説
view():要素がビューポートに入り始めてから完全に出るまでのスクロールを基準とします。contain / cover:スラッシュの前(contain)が「トリガーが有効化される範囲」、後ろ(cover)が「有効状態を保ち続ける範囲」を表します。つまり「要素全体が画面に収まったら有効になり、画面から完全に出るまで有効を維持する」という意味になります。play-forwards play-backwards:1つ目の値が「有効化された時の挙動(順再生)」、2つ目の値が「無効化された時の挙動(逆再生)」を指定します。
3. animation-trigger に指定できる4つの再生方法
animation-trigger には、UIの要件に応じて以下の4つの再生キーワードを組み合わせて指定できます。
| キーワード | 動作・役割 | 適した演出 |
|---|---|---|
| play-forwards | 通常方向(0% → 100%)にアニメーションを再生する。 | 画面イン時の標準的な登場演出。 |
| play-backwards | 逆方向(100% → 0%)にアニメーションを巻き戻して再生する。 | 画面外へ出たときに元の非表示状態へ戻す演出。 |
| play-once | 一度だけ再生し、以降はトリガーが再発火しても何もしない。 | 一度読ませた見出しや本文をそのまま固定表示させたい場合。 |
| replay | トリガーが有効になるたび、毎回最初からリセットして再生し直す。 | 何度スクロールしても同じアクションを繰り返させたいギミック。 |
4. 実践作例で学ぶ!現場で使える3つの応用テクニック
ここからは、実際のWeb制作現場で即座に役立つ3つの実践的なユースケースを紹介します。
作例①:見出しテキストを1文字ずつ立ち上げる演出(trigger-scope と sibling-index)
画面内に見出しが入ったとき、文字が1文字ずつ下から跳ね上がるリッチな演出です。一度きりの再生(play-once)と、スコープ制限(trigger-scope)を組み合わせて実装します。
.hero {
/* 見出し全体が見えた位置でトリガーを有効化 */
timeline-trigger: --title view() contain / cover;
/* 【重要】トリガー名の参照範囲をこの.hero要素内に閉じ込める */
trigger-scope: --title;
.char {
/* sibling-index()で文字順から遅延を自動計算 */
animation-delay: calc((sibling-index() - 1) * 0.04s);
.char__inner {
animation: rise 0.8s var(--ease-expo-out) both;
animation-trigger: --title play-once; /* 一度だけ再生 */
animation-delay: inherit;
}
}
}
@keyframes rise {
from {
translate: 0 100%;
}
}
実装のポイント:
同じクラス構造を持つコンポーネントがページ内に複数存在する場合、同じトリガー名(--title)を使っていると、1つ目が画面に入った際に2つ目の見出しまで暴発してしまいます。そこで trigger-scope: --title; を親要素に宣言することで、トリガーの影響範囲を自身のツリー内に限定し、各見出しが画面に入ったタイミングで独立して発火させることができます。
作例②:親セクションをトリガーにして複数の要素を順番に表示する
セクション全体が画面に入ったことをきっかけに、写真、見出し、説明文の順番で時間差(ディレイ)をつけて表示させるレイアウトです。
.work {
/* セクションが画面に60%入った位置でトリガーを有効化 */
timeline-trigger: --work-trigger view() entry 60% cover;
trigger-scope: --work-trigger;
/* 写真のフレーム表示 */
.work__visual .work__frame {
animation: reveal-frame 1.4s var(--ease-expo-out) both;
animation-trigger: --work-trigger play-once;
}
/* 見出しテキスト(ディレイをずらして追従) */
.work__content .line:nth-child(1) .line__inner {
animation: rise-focus 1.25s var(--ease-expo-out) both;
animation-trigger: --work-trigger play-once;
animation-delay: 0.4s;
}
.work__content .line:nth-child(2) .line__inner {
animation: rise-focus 1.25s var(--ease-expo-out) both;
animation-trigger: --work-trigger play-once;
animation-delay: 0.52s;
}
}
entry 60% という指定により、「要素が画面の端にかすった瞬間」ではなく、「60%ほどしっかり画面内に入り込んでから」アニメーションを開始させています。コンテンツの可読性を高めるための実務的な微調整テクニックです。
作例③:離れた要素同士を同一トリガーで連動させる(replay の活用)
画面の右側にあるスクロールエリアのテキスト(色名)が中央に到達したとき、左側に固定配置されている別要素(カラーボール)がポンと飛び出すような、レイアウトをまたいだ連動演出です。
/* 右側のスクロールエリア内にある見出し */
.story--red h2 {
/* 文字が枠内に入ると有効、完全に出ると無効 */
timeline-trigger: --red view() contain / cover;
}
/* 左側の別エリアにあるボール要素 */
.ball.ball--red {
animation: appear 0.8s both;
/* 右側の見出しのトリガーを参照し、有効になるたび最初から再再生 */
animation-trigger: --red replay;
}
@keyframes appear {
0% { translate: -50% 80px; }
50% { translate: -50% -10px; }
100% { translate: -50% 80px; }
}
HTML構造上まったく別の場所にある要素同士であっても、カスタムプロパティ名(--red)を介してシームレスに同期させることが可能です。
5. 対応ブラウザと今後の導入指針
2026年秋時点におけるブラウザのサポート状況は以下の通りです。
| ブラウザ | 対応バージョン | 対応ステータス |
|---|---|---|
| Google Chrome | Chrome 146(2026年3月)以降 | 完全対応 |
| Microsoft Edge | Edge 146(2026年3月)以降 | 完全対応 |
| Safari | 実装中(仕様策定・開発進行中) | 今後のアップデートで対応期待 |
| Firefox | 未対応(標準化プロセス追従中) | 今後のアップデートで対応期待 |
制作現場での使い分けアプローチ
現時点ではChromium系ブラウザでの先行サポートとなっているため、プロダクション環境での使い分けとしては以下の2つのアプローチが現実的です。
- 全ブラウザ完全担保が必須の商用案件:これまで通り
GSAP ScrollTriggerやIntersection Observerを主軸にしつつ、コードのシンプル化を検証する。 - プログレッシブ・エンハンスメント前提の社内ツールや先行プロモーション:非対応ブラウザでは単に最初から要素を表示(
opacity: 1)しておき、対応ブラウザでのみCSSトリガーを走らせる軽量設計を採用する。
6. まとめ:JavaScript不要のスクロール演出が切り拓く未来
本記事では、CSSスクロールトリガーアニメーションの基本から実践的な応用例までを網羅して解説しました。
timeline-triggerでスクロール位置を定義し、animation-triggerで発火方法を紐付ける。- スクロール量連動(駆動)とは異なり、時間ベースのキーフレームアニメーションを自律実行できる。
play-onceやreplay、trigger-scopeを駆使することで、複雑な順次表示やスコープ管理が可能。- 離れた場所にある要素同士も、同じトリガー名を指定するだけで簡単に連動できる。
- Chrome・Edge 146以降で利用可能であり、今後のSafari/Firefoxへの普及によってWebの標準技術となっていく。
重たいJavaScriptライブラリをロードすることなく、ブラウザネイティブの圧倒的なパフォーマンスで軽快なスクロール演出が組める時代が目の前に来ています。ぜひ皆さんも最新仕様に触れ、次世代のフロントエンド開発をいち早く体験してみてください!
