animation-range

タイムラインのどの区間でアニメを進めるかを決めるプロパティ。 scroll-driven の表現力はここで決まる。

構文

animation-range: <start> <end>;
animation-range-start: <start>;
animation-range-end: <end>;

値の種類

意味
normalタイムライン全体(既定)
length / percentage絶対値(scroll())
cover <%>cover の何 %
contain <%>contain の何 %
entry <%>entry の何 %
exit <%>exit の何 %
entry-crossing境界を越える瞬間
exit-crossing境界を越え終わる瞬間

cover / contain / entry / exit の関係

要素のライフサイクル

時間 →

[要素は画面外(下)]
       ↓
   ┌─ entry  始まる        ─┐
   │                          │
   │  ←  cover 開始             │
   │                          │
[要素の上端が画面下端に来た]   │
       ↓                        │
   │                          │
   │  ←  ここまで entry 進行    │
   │                          │
[要素全体が画面内に入った]
   └─ entry 終わり = contain 開始 ─┘
       ↓
[完全表示中]
       ↓
   ┌─ contain 終わり = exit 開始 ─┐
   │                          │
[要素の下端が画面上端に来た]   │
   │                          │
   │  ←  exit 進行              │
   │                          │
   │  ←  ここで cover 終わり    │
   │                          │
   └─ exit 終わり ─┘
       ↓
[要素は画面外(上)]
      

例で理解する

「現れたら 1 回フェードイン」

animation-range: entry 0% entry 100%;

「現れてから完全表示の真ん中まで」

animation-range: entry 0% cover 30%;

「完全表示中だけ動く」

animation-range: contain 0% contain 100%;

「消えていく時だけ」

animation-range: exit 0% exit 100%;

「現れてから消えるまで全部」

animation-range: cover 0% cover 100%;

scroll() で使う場合

/* % */
animation-range: 0% 100%;        /* 全スクロール */
animation-range: 30% 70%;        /* 中央 40% */

/* 絶対 px */
animation-range: 200px 800px;

/* vh */
animation-range: 0vh 80vh;

個別指定

animation-range-start: entry 0%;
animation-range-end:   cover 30%;

複数 animation の範囲を別々に

.parallax-card {
  animation:
    fade-in linear both,
    drift   linear both;
  animation-timeline: view(), view();
  animation-range:
    entry 0% entry 100%,           /* fade-in は入る間 */
    cover 0% cover 100%;           /* drift は cover 全体 */
}

境界周辺の挙動

animation-delay / animation-duration

scroll-driven ではdelay と duration もスクロール量で解釈される(時間ではなく)。

fill-mode と組合せ

.elem {
  animation: reveal linear both;
  animation-range: entry 0% entry 100%;
  /*                        ↑
   * 範囲外(画面外 / 完全表示後)でも
   * fill-mode: both で値を保持
   */
}

chained range(複数段階)

現状、1 つの animation に対して 1 つの range のみ。多段階は複数の animationを組合せる:

.story {
  animation:
    appear  linear both,
    move    linear both,
    fade    linear both;
  animation-timeline: view(), view(), view();
  animation-range:
    entry 0% entry 100%,
    contain 0% contain 100%,
    exit 0% exit 100%;
}

@keyframes appear { from { opacity: 0; } to { opacity: 1; } }
@keyframes move   { from { transform: translateY(0); } to { transform: translateY(-30px); } }
@keyframes fade   { from { opacity: 1; } to { opacity: 0; } }

視覚化のための DevTools

キーフレームの中の % との対応

@keyframes story {
  0%   { opacity: 0; transform: translateY(40px); }
  50%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-40px); }
}

.elem {
  animation: story linear both;
  animation-timeline: view();
  animation-range: cover 0% cover 100%;
}

@keyframes の 0% / 50% / 100% がanimation-range の 0% / 50% / 100% に対応する。

パーセントは「区間の中の %」

entry 50%entry の半分まで進んだ時点。 全体の 50% ではない。

失敗パターン

症状対処
カードが画面下から半分くらいで終わるentry 0% cover 30% → entry 0% entry 100% に拡張
狭すぎてアニメが見えないanimation-range をもっと広く
初期値に戻ってちらつくfill-mode: both / forwards
contain が短くて使えない要素が小さい / 画面が大きい場合は contain は短い
よく使う組合せ

entry 0% cover 30% がリビールの黄金比。 現れ始めから完全表示まで(+α)の体感が良い。