基本概念
タイムライン、進行度、範囲、@keyframes との関係。 2 種類のタイムライン(scroll / view)の根本イメージを掴む。
そもそも CSS animation の復習
.box {
animation: fade 1s ease-in-out forwards;
}
@keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}
- 1 秒かけて 0 → 1 に進む
- 「時間」がアニメーションの進行を決める
- これがdocument timeline(既定)
タイムラインを差し替える
animation-timeline プロパティで進行を決めるソースを変える:
.box {
animation: fade auto linear forwards;
animation-timeline: scroll(root);
}
ポイント:
autoでアニメ duration を timeline に委ねるlinear推奨(スクロールに直結)scroll(root)はページのスクロール位置
2 種類のタイムライン
scroll() — スクロール量
スクロールバーが0% から 100% まで進む間に、アニメも進む。
animation-timeline: scroll(); /* 最寄りのスクロールコンテナ */
animation-timeline: scroll(root); /* ページ全体 */
animation-timeline: scroll(self); /* 自分自身(要素がスクロール可能なら)*/
animation-timeline: scroll(nearest block); /* 縦方向の最寄り */
animation-timeline: scroll(nearest inline); /* 横方向 */
view() — 要素のビューポート進入率
その要素がビューポートに入ってから出ていくまでで進む。
animation-timeline: view(); /* 既定: nearest スクロール基準 */
animation-timeline: view(block);
animation-timeline: view(inline);
進行度の概念
スクロール
0% 100%
│═════════════════════════════│
↑ ↑
ページ最上部 ページ最下部
進行度 0 〜 1 が animation の from 〜 to に対応
view() の進入度の図
ビューポート
┌──────────────┐
│ │
│ │
├──────────────┤ ← entry の起点(要素が下から入る瞬間)
│ │
│ [要素] │ ← 要素が画面内
│ │
├──────────────┤ ← exit の終点(要素が上から消える瞬間)
進行度: entry 0% から exit 100% までの全行程で 0 → 1
animation-range
「タイムラインのどこからどこまで」を実際のアニメに割り当てるか:
animation-range: entry 0% entry 100%;
/* ↑ ↑
始まり 終わり */
主要な範囲名
- cover — 要素がビューポートに被る間の全体(既定)
- contain — 要素が完全に収まっている間
- entry — ビューポートに入る瞬間 〜 完全に収まる
- exit — 完全に収まる 〜 出ていく瞬間
- entry-crossing / exit-crossing — 端を越える瞬間
典型: 要素が画面に入ったら fade in
.card {
opacity: 0;
animation: reveal linear forwards;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
@keyframes reveal {
to { opacity: 1; }
}
animation の各プロパティとの関係
| プロパティ | 意味 |
|---|---|
| animation-name | @keyframes の名前 |
| animation-duration | scroll/view では auto 推奨 |
| animation-timing-function | linear 推奨 |
| animation-fill-mode | both / forwards が定番 |
| animation-timeline | scroll() / view() / 名前 |
| animation-range | タイムラインの範囲 |
fill-mode の重要性
- scroll-driven は進行度に応じて値が変わる
- 範囲外では「初期値 / 最終値で固定」が望ましいことが多い
- fill-mode: both で範囲外も埋める
イージング
アニメ全体に easing を掛けるなら animation-timing-function: linear 以外も使えるが、
スクロール直結が崩れることがある。
キーフレーム単位で easing を入れる方が直感的:
@keyframes reveal {
0% { opacity: 0; transform: translateY(40px); animation-timing-function: ease-out; }
100% { opacity: 1; transform: translateY(0); }
}
複合の例
.hero-image {
animation:
fade-in 1s linear forwards,
parallax linear forwards;
animation-timeline: auto, view();
animation-range: auto, cover 0% cover 100%;
}
複数の animation で時間ベースとスクロールベースを併用できる。
scroll() と view() の使い分け
| 状況 | 選択 |
|---|---|
| ページ進捗バー | scroll(root) |
| ヒーロー画像のパララックス | view() |
| カードが現れるリビール | view() |
| 横スクロールギャラリー | scroll(self inline) |
| 章ごとのアニメ | 名前付きタイムライン |
Reduced Motion
@media (prefers-reduced-motion: reduce) {
.reveal { animation: none; opacity: 1; transform: none; }
}
失敗パターン
| 症状 | 対処 |
|---|---|
| 動かない | animation-duration を auto に / fill-mode を設定 |
| カクつく | transform / opacity 以外を変えてしまっている |
| 非対応ブラウザで動かない | polyfill / @supports でフォールバック |
| view() の範囲が思った所と違う | animation-range を明示 |
| 身長 100vh の section で見えにくい | animation-range entry / contain で調整 |
@supports でフォールバック
@supports (animation-timeline: scroll()) {
.reveal {
opacity: 0;
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
}
@supports not (animation-timeline: scroll()) {
/* 静的に表示する fallback */
.reveal { opacity: 1; }
}
頭の整理
「タイムライン = 進行度のソース」「animation-range = どの範囲を使うか」。 この 2 つが分かれば応用は組合せだけ。