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 の必要性

失敗パターン

症状対処
下まで来ても発火しない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 だけで連続的に変化させられる。リビール演出は爆速で書ける。