基本概念

タイムライン、進行度、範囲、@keyframes との関係。 2 種類のタイムライン(scroll / view)の根本イメージを掴む。

そもそも CSS animation の復習

.box {
  animation: fade 1s ease-in-out forwards;
}
@keyframes fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

タイムラインを差し替える

animation-timeline プロパティで進行を決めるソースを変える:

.box {
  animation: fade auto linear forwards;
  animation-timeline: 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%;
/*                ↑          ↑
                  始まり     終わり */

主要な範囲名

典型: 要素が画面に入ったら 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-durationscroll/view では auto 推奨
animation-timing-functionlinear 推奨
animation-fill-modeboth / forwards が定番
animation-timelinescroll() / view() / 名前
animation-rangeタイムラインの範囲

fill-mode の重要性

イージング

アニメ全体に 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 つが分かれば応用は組合せだけ。