view() — 要素のビューポート進入
その要素がビューポートに対してどれだけ見えているかをタイムラインに。 リビール(ふわっと表示)/ パララックス / カードホバー演出に最強。
構文
animation-timeline: view(<axis> <inset>);
/* <axis>: block | inline | x | y */
/* <inset>: ビューポート境界からの内側オフセット */
例: 要素が見えると現れる
.card {
opacity: 0;
transform: translateY(40px);
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
@keyframes reveal {
to { opacity: 1; transform: translateY(0); }
}
進入度のメンタルモデル
ビューポート
┌──────────────────┐ ← top
│ │
│ │
├──────────────────┤ ← 要素の上端がここに来た瞬間 = entry 0%
│ [要素] │
├──────────────────┤ ← 要素の下端がここに来た瞬間 = entry 100%
│ │
│ │
├──────────────────┤ ← 要素の上端がここに来た瞬間 = exit 0%
│ │ ← 要素の下端がここを越えた = exit 100%
└──────────────────┘ ← bottom
cover = 要素が画面に被っている全体(0% = 入り始め, 100% = 出終わり)
contain = 要素が完全に収まっている間
entry = 入り始め 〜 完全に収まる(部分的に見える区間)
exit = 完全に収まる 〜 完全に出ていく
animation-range で範囲を切り出す
animation-range: entry 0% entry 100%;
/* 「画面に入りつつある区間」だけアニメさせる */
animation-range: entry 0% cover 30%;
/* 「入りつつ + 完全表示まで残り 70% の地点」まで */
animation-range: cover 0% cover 100%;
/* 「現れて 〜 消えるまで」全行程 */
animation-range: contain 0% contain 100%;
/* 「完全に画面内に収まっている間」のみ */
典型レシピ集
1. ふわっと現れる
.fade-up {
opacity: 0;
transform: translateY(40px);
animation: fade-up linear both;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
@keyframes fade-up {
to { opacity: 1; transform: translateY(0); }
}
2. 見えるとスケールアップ
.zoom-in {
transform: scale(0.8);
opacity: 0;
animation: zoom linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes zoom {
to { transform: scale(1); opacity: 1; }
}
3. パララックス
.hero-bg {
animation: parallax linear both;
animation-timeline: view();
animation-range: cover 0% cover 100%;
}
@keyframes parallax {
from { transform: translateY(-20%); }
to { transform: translateY(20%); }
}
4. ピン留め(中央に滞在)
.pinned {
position: sticky;
top: 0;
height: 100vh;
animation: pulse linear both;
animation-timeline: view();
animation-range: contain 0% contain 100%;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
5. 横スライドイン
.slide-in-left {
opacity: 0;
transform: translateX(-50px);
animation: slide-x linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
.slide-in-right {
opacity: 0;
transform: translateX(50px);
animation: slide-x linear both reverse;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes slide-x {
to { opacity: 1; transform: translateX(0); }
}
6. 退場アニメ(exit)
.fade-out-on-scroll {
animation: fade linear both;
animation-timeline: view();
animation-range: exit 0% exit 100%;
}
@keyframes fade {
to { opacity: 0; transform: translateY(-40px); }
}
7. 入って→留まって→出る(3 段階)
.entry-stay-exit {
animation:
fade-in linear both,
fade-out linear both;
animation-timeline: view(), view();
animation-range:
entry 0% entry 100%,
exit 0% exit 100%;
}
view() の inset
ビューポート境界を内側に縮める:
/* 上下 100px ずつ内側で計算 */
animation-timeline: view(block 100px 100px);
/* 上だけ 200px 内側 */
animation-timeline: view(block 200px 0);
「画面の中央 50% に入ったら発動」のようなチューニングが可能。
水平方向の view()
animation-timeline: view(inline);
/* 横スクロールギャラリー内で各カードが横方向に進入する間 */
animation-range のバリエーション
| 記述 | 意味 |
|---|---|
| entry 0% entry 100% | 入る区間だけ |
| cover 0% cover 100% | 表示されている全体 |
| contain 0% contain 100% | 完全に表示されている間 |
| exit 0% exit 100% | 出ていく区間だけ |
| entry 0% exit 100% | 「現れてから消えるまで」(= cover) |
| entry 50% cover 50% | 半分入ったら 〜 完全表示の真ん中まで |
ステアリングで stagger
.list .item {
opacity: 0; transform: translateY(20px);
animation: in linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
.list .item:nth-child(1) { animation-delay: 0s; }
.list .item:nth-child(2) { animation-delay: 0.1s; }
.list .item:nth-child(3) { animation-delay: 0.2s; }
scroll-driven では animation-delay はスクロール量で解釈されることに注意。
fill-mode の必要性
- fill-mode: both — 範囲の前後でも初期値 / 終端値を保つ
- 付けないと範囲外で「アニメなし状態」に戻る → ちらつき
失敗パターン
| 症状 | 対処 |
|---|---|
| 下まで来ても発火しない | animation-range が狭すぎる / fill-mode 抜け |
| 画面外で初期値に戻る | fill-mode: both / forwards |
| カクつく | transform / opacity 以外を変えてる |
| position: fixed 要素で動かない | view() は要素位置に依存。固定要素なら scroll() |
使い分け
| 状況 | 選択 |
|---|---|
| 要素ごとに「現れる」を作る | view() |
| ページ全体の進捗 | scroll(root) |
| パララックス | view() (要素ごと) or scroll() (全体) |
| 横スクロールでカードを切り替え | view(inline) |
view() が一番楽しい
IntersectionObserver で「見えたらクラスを付与」していたのが、 CSS だけで連続的に変化させられる。リビール演出は爆速で書ける。