GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

css スクロール アニメーション 9選 — 区間値で切り替える

css スクロール アニメーション の9パターンです。画面を下げるとカードが一枚ぼやけたまま浮かび、通り過ぎるうちに次第にはっきりしてくる。そのぼかしの度合いはスクロール位置ではなく、その要素が画面を通過する区間の値ひとつで決まる。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

01区間値に合わせて大きく鮮明になるカード

カードが画面に入る区間の値が0から1に満ちる間、不透明度の値と大きさの値が共に増え、次第に鮮明に大きく現れます。LPの機能カードがスクロールで視界に入るたびに整っていくような使い方です。

区間値に合わせて大きく鮮明になるカード
新しいタブで開く

01 · 区間値に合わせて大きく鮮明になるカード — 計測 累積面積 16.106% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

animation-range: entryanimation-timeline: view()--fade-scale-start
.supports-vt .fsc__card {
  animation-name: fscEnter;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: entry;
}
@keyframes fscEnter {
  0%   { opacity: 0; transform: scale(var(--fade-scale-start, .8)); }
  100% { opacity: 1; transform: scale(1); }
}

計算を担う値は --fade-scale-start ひとつです。キーフレームはこの値から 1 へ補間するだけなので、不透明度と大きさが別々のタイマーでずれることなく、同じ区間値に揃って従います。

測定値: 累積面積は画面の16.1%、強度平均138.8、動いたフレームは23/23です。

02境界値が移動して現れるサムネイル

区間の値が満ちる間、clip-path: inset() の境界値が一方の端からもう一方の端まで動き、画像を少しずつ現します。ポートフォリオのサムネイルが画面中央に来た頃にちょうど見え終わる使い方です。

境界値が移動して現れるサムネイル
新しいタブで開く

02 · 境界値が移動して現れるサムネイル — 計測 累積面積 17.112% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

clip-path: insetanimation-range: entrycprReveal
.supports-vt .cpr__panel {
  animation-name: cprReveal;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: entry;
}
@keyframes cprReveal {
  0%   { clip-path: inset(0 100% 0 0); }
  100% { clip-path: inset(0 0% 0 0); }
}

右側の inset の値が 100%(全部隠れた状態)から 0% まで数を減らしていくだけの計算です。本来なら不透明度に使う区間値を、ここでは箱の中の距離として読み替えています。

測定値: 累積面積は画面の17.1%、強度平均196.9、動いたフレームは23/23です。

03斜めの境界値が通り過ぎる背景画像

区間の値に沿って、登録済みのカスタムプロパティが斜め135度の境界を画像の上で動かし、カーテンをめくるように背景画像を現します。

斜めの境界値が通り過ぎる背景画像
新しいタブで開く

03 · 斜めの境界値が通り過ぎる背景画像 — 計測 累積面積 21.765% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

@property --wipemask-image135deg
@property --wipe {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}
.supports-vt .mwi__pic {
  animation-name: mwiWipe;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: entry;
}
@keyframes mwiWipe {
  0%   { --wipe: 0%; }
  100% { --wipe: 100%; }
}

--wipe は @property で <percentage> として登録されているからこそ滑らかに補間でき、mask-image のグラデーションはこの一つの値を二回読んで(境界線そのものと、その12ポイント手前)硬い線ではなく柔らかいにじみを作ります。

測定値: 累積面積は画面の21.8%、強度平均76.3、動いたフレームは23/23です。

04区間値に沿ってピントが外れたり合ったりするカード

要素が画面を通過する区間の値に応じてぼかしの値が大きくなったり小さくなったりを繰り返し、入るときと出るときそれぞれピントが外れたり合ったりします。プロダクト紹介カードが画面中央を通る瞬間だけくっきり見える使い方です。

区間値に沿ってピントが外れたり合ったりするカード
新しいタブで開く

04 · 区間値に沿ってピントが外れたり合ったりするカード — 計測 累積面積 28.216% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

animation-range: coverfilter: blurhump
.supports-vt .bfs__card {
  animation-name: bfsFocus;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: cover;
}
@keyframes bfsFocus {
  0%   { filter: blur(var(--blur-max, 12px)); opacity: .55; }
  50%  { filter: blur(0px); opacity: 1; }
  100% { filter: blur(var(--blur-max, 12px)); opacity: .55; }
}

計算の核は50%のキーフレームで、cover は通過全体を0から1で測るので、フォールバックのスクリプト側も hump = 1 - |cov * 2 - 1| という三角形の式で同じ山を再現しています。中央に近いほどぼかしが浅く、両端に近いほど --blur-max に近づきます。

測定値: 累積面積は画面の28.2%、強度平均62.5、動いたフレームは23/23です。

05区間値が角度値に変わる3Dカード

同じ区間の値を角度の値に変えてカードを立体的に裏返し、区間が中間を過ぎるとカードの裏面が現れます。サービスカードが読み進めると詳細面に裏返る使い方です。

区間値が角度値に変わる3Dカード
新しいタブで開く

05 · 区間値が角度値に変わる3Dカード — 計測 累積面積 17.417% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

preserve-3drotateYbackface-visibility
.supports-vt .r3f__inner {
  animation-name: r3fFlip;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: cover;
}
@keyframes r3fFlip {
  0%   { transform: rotateY(0deg); }
  100% { transform: rotateY(180deg); }
}

裏返る合図が別にあるわけではなく、rotateY が0から1の区間値をそのまま 180deg 倍しているだけなので、角度が 90deg を超える区間値0.5の瞬間に backface-visibility: hidden が裏面を見せます。

測定値: 累積面積は画面の17.4%、強度平均161.7、動いたフレームは23/23です。

06傾き値が終わりで解けるパネル

区間の値が開始点に近いほどパネルの傾き値は大きく残り、値が終わりに達した瞬間、傾き値が0に解けてまっすぐ整列します。料金表パネルが視界に入りきると同時にまっすぐ整う使い方です。

傾き値が終わりで解ける パネル
新しいタブで開く

06 · 傾き値が終わりで解ける パネル — 計測 累積面積 17.512% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

skewXanimation-range: entrysspShear
.supports-vt .ssp__panel {
  animation-name: sspShear;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: entry;
}
@keyframes sspShear {
  0%   { transform: skewX(-18deg); }
  100% { transform: skewX(0deg); }
}

計算は skewX の角度ひとつが -18deg から 0deg まで、進入区間の値をそのままなぞるだけです。値がひとつしかないので、パネルが表示のきっかけと違う理由で途中で止まったまま見えることがありません。

測定値: 累積面積は画面の17.5%、強度平均147.1、動いたフレームは23/23です。

07区間値が色の補間地点になるバッジ

区間の値を色の値の補間地点として使い、バッジの色が一方から他方へゆっくり移り変わります。新着バッジが所定の位置に落ち着く頃に色も変わり終わる使い方です。

区間値が色の補間地点になるバッジ
新しいタブで開く

07 · 区間値が色の補間地点になるバッジ — 計測 累積面積 10.083% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

background-colorCOLOR_FROMcmbMorph
.supports-vt .cmb__pill {
  animation-name: cmbMorph;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: entry;
}
@keyframes cmbMorph {
  0%   { background-color: $subject-blue-deep; }
  100% { background-color: $subject-orange-deep; }
}

background-color は区間の値を二つの色の間の位置としてそのまま受け取るだけで mix() もカスタムプロパティも挟まっておらず、9種の中でこの項目の強度平均が最も低いのも色だけの変化は形や位置の変化より動くピクセルが少ないからです。

測定値: 累積面積は画面の10.1%、強度平均58.5、動いたフレームは23/23です。

08区間値の分だけ浮き上がる影のカード

区間の値が大きくなるほど影の広がり距離値とぼかし値が共に増え、カードが画面から次第に浮き上がって見えます。カードリストの各行が視界を通るたびにわずかに持ち上がる使い方です。

区間値の分だけ浮き上がる影のカード
新しいタブで開く

08 · 区間値の分だけ浮き上がる影のカード — 計測 累積面積 28.618% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

box-shadowtranslateY--shadow-spread
.supports-vt .sld__card {
  animation-name: sldLift;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: cover;
}
@keyframes sldLift {
  0%   { box-shadow: 0 6px 10px 0px rgba(23, 20, 26, .35); transform: translateY(0px); }
  100% {
    box-shadow: 0 calc(6px + var(--shadow-spread, 24px) * .3) calc(10px + var(--shadow-spread, 24px) * .6) var(--shadow-spread, 24px) rgba(23, 20, 26, .35);
    transform: translateY(-14px);
  }
}

--shadow-spread という一つの値が三つの数字を同時に動かします。縦方向のずれはその30%分、ぼかし半径は60%分、box-shadow の第四値(にじみ幅そのもの)はその全部です。ひとつの値が「どれだけ持ち上がったか」を語っていて、ばらばらの三つの調整ではありません。

測定値: 累積面積は画面の28.6%、強度平均93.9、動いたフレームは23/23です。

09区間値が文字間隔値として広がる見出し

区間の値を文字間隔の値に変え、見出しは狭く詰まった状態から値が満ちるほど文字の間隔が広がっていきます。セクションタイトルが視界に入りきると同時に広がり終わる使い方です。

区間値が文字間隔値として広がる見出し
新しいタブで開く

09 · 区間値が文字間隔値として広がる見出し — 計測 累積面積 4.267% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

letter-spacingIntersectionObserverlssStretch
.supports-vt .lss__title {
  animation-name: lssStretch;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-timeline: view();
  animation-range: entry;
}
@keyframes lssStretch {
  0%   { letter-spacing: -0.04em; opacity: .35; }
  100% { letter-spacing: 0.4em; opacity: 1; }
}

letter-spacing は -0.04em から 0.4em まで、幅にして 0.44em を同じ0から1の区間値の上で動かします。不透明度も .35 から 1 まで同じ値に従うので、文字を広げるだけのこの項目が9種の中で最も面積が小さい測定結果になっています。

測定値: 累積面積は画面の4.3%、強度平均136.4、動いたフレームは23/23です。

実際にどの画面で使うかを説明します

機能カードの並ぶセクションなら1番がそのまま収まります。訪問者がスクロールするたびにカードが育っていくので、JavaScriptを一行も書かずにページへ緩急がつきます。料金表やサービス紹介は5番・6番・8番を組み合わせる場面が多く、裏返るカード・まっすぐ整うパネル・少し持ち上がる行が同時に動くことで「ここに収まりつつある」というひとつの印象になります。セクション見出しや新着バッジには7番と9番が向いています。9種の中でも動くピクセルが特に少ないので、すでに重い演出が別にあるページでも足しやすい軽さです。

動かせる3つの値と変わる見え方をまとめます

定数 変更 変わること
VIEWTL_FADE_SCALE_START 0.8 → 0.6 01番のカードがより小さく縮んだ状態から始まり、成長幅が広がって登場が強調されます
VIEWTL_BLUR_MAX_PX 12 → 24 04番のぼかしが強くなり、画面への出入り時のピント切り替えがはっきり見えます
VIEWTL_SHADOW_SPREAD_PX 24 → 48 08番の影が遠くまで広がり、カードがずっと高く浮き上がったように見えます

ネイティブ対応がない環境でどこが壊れるか

どの項目も読み込み時に一度だけ CSS.supports('animation-timeline: view()') を確認し、supports-vt か no-vt のクラスを先に付けます。対応がない環境では51段階のしきい値を持つ IntersectionObserver と scroll リスナーが同じ比率を手計算で肩代わりします。壊れやすいのはここで、01番のスケール式、04番の三角形の hump、08番の影の伸び幅、そして各項目の VIEWTL_* 定数はすべてCSSのカスタムプロパティ側とJS変数側の両方に同じ値を書いておく必要があり、片方だけ編集して食い違っても警告は出ません。低スペック端末ではこのフォールバック経路の方が重くなります — 本来ならコンポジタが無償で処理するはずの区間値の計算を、毎フレームの scroll リスナーと51段階の IntersectionObserver が肩代わりするためです。「スクロール アニメーション css のみ」で探しているなら、このフォールバックが効いている状態でも見た目はCSSだけの版とほぼ揃います。zipを開くにはこのすぐ下にある yagmcszp が要ります — ないと解凍できません。

モーション低減設定がアクセシビリティ面で消すものを説明します

prefers-reduced-motion: reduce は9種すべての animation-name を外し、各要素を最終状態にそのまま固定します。01番のカードは最初から等倍で不透明、06番のパネルは最初から傾きなし、07番のバッジは最初から変化後の色です。reduced-motion を求めた人は区間値が途中を通過する様子を一切見ず、両端の状態だけを見ることになります。

項目 reduced-motionで消えるもの
01 拡大と不透明度の変化 — 最初から等倍・不透明
04 ピントのぼけ山 — 常にくっきり表示
05 3D裏返り — 常に表面のみ表示
08 影の浮き上がり — 静止時の影のまま

値がどう場面を作るかを説明します

animation-timeline: view() が出す値は常に0から1です。animation-range: entry はそれを進入の間だけに絞り、要素の先端が見え始めた瞬間を0、完全に画面へ入り切った瞬間を1にします。01・02・03・06・07・09番はこの一方通行の値を使っています。animation-range: cover は通過全体、つまり最初に触れた瞬間を0、最後の1ピクセルが去る瞬間を1にするので、04・05・08番のように値が一度上がってまた下がる動きを作れます。scrollTop を直接読んでいる項目はひとつもなく、ブラウザがフレームを描くたびにこの値を計算し直しているだけです。

css スクロール 駆動 アニメーション をもっと見るには

【コピペOK】スクロール アニメーション CSS のみ 9選 はこの9つが土台にしている @scroll-timeline と animation-timeline の全体像を扱っていて、こちらのように毎回ひとつの区間値へ絞り込んではいません。スクロール トリガー アニメーション 9選 — GSAPなしのバニラJSで は同じ「スクロールで何かが起きる」を丸ごとJavaScript側で解いていて、animation-timeline: view() が使えない環境でこの9つが落ちる先そのものです。スクロール 慣性 css 9選 — 値が自ら収まっていく9つの計算パターン は位置ではなくスクロールが止まった後に残る勢いという別の値を扱っていて、そちらのカードはこの04番がとっくにピントを結び終えた後もまだ動き続けます。仕様そのものはMDNの animation-range に、entry と cover の両方の値がここまで展開されるところまで載っています。

9つの区間値が実際にどれだけ動いたか — フレーム実測

項目 累積面積 強度平均 動いたフレーム
01 区間値に合わせて大きく鮮明になるカード 16.1% 138.8 23/23
02 境界値が移動して現れるサムネイル 17.1% 196.9 23/23
03 斜めの境界値が通り過ぎる背景画像 21.8% 76.3 23/23
04 区間値に沿ってピントが外れたり合ったりするカード 28.2% 62.5 23/23
05 区間値が角度値に変わる3Dカード 17.4% 161.7 23/23
06 傾き値が終わりで解ける パネル 17.5% 147.1 23/23
07 区間値が色の補間地点になるバッジ 10.1% 58.5 23/23
08 区間値の分だけ浮き上がる影のカード 28.6% 93.9 23/23
09 区間値が文字間隔値として広がる見出し 4.3% 136.4 23/23

解凍する前によくある質問に答えます

animation-range を動かすのにJavaScriptは必要ですか?

いいえ、上の9つのキーフレームが変えている値はすべて animation-timeline: view() に対応していればCSSだけで動きます。各デモのJavaScriptは対応がない環境で同じ値を手計算するためと、画面の進行バッジを表示するためだけに存在します。

「css アニメーション フェードイン スクロール」で探していても、この9つのどれかは使えますか?

使えます。01番と03番はどちらも入場時にフェードしながら現れる系統なので、フェードイン狙いの検索にもそのまま当てはまります。9つとも index.html とスタイルシートが単体で完結しているので、zipから一つだけ取り出して別のページに貼っても動きます。

entryの06番とcoverの04番で値の動き方が違って見えるのはなぜですか?

entry は要素が入ってくる間しか測らないので値は一度だけ増えて止まりますが、cover は通過全体を測るので一度上がってまた下がります。違いはこのキーワードの意味だけで、コードの書き方自体は同じです。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。