実践パターン

現場でよく使う 10 のレシピ集。コピペで動く形で。

1. ページ進捗バー

.reading-bar {
  position: fixed; top: 0; left: 0;
  height: 3px; width: 100%;
  background: linear-gradient(to right, orange, red);
  transform-origin: left;
  transform: scaleX(0);
  z-index: 1000;
  animation: read linear both;
  animation-timeline: scroll(root);
}
@keyframes read { to { transform: scaleX(1); } }

2. リビール(ふわっと現れる)

.reveal {
  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); }
}

3. パララックス(背景)

.parallax-bg {
  animation: parallax linear both;
  animation-timeline: view();
  animation-range: cover 0% cover 100%;
}
@keyframes parallax {
  from { transform: translateY(-15%) scale(1.1); }
  to   { transform: translateY(15%) scale(1.1); }
}

4. ヒーローでズームアウト

.hero img {
  animation: zoom-out linear both;
  animation-timeline: view();
  animation-range: cover 0% cover 100%;
}
@keyframes zoom-out {
  from { transform: scale(1.2); }
  to   { transform: scale(1); }
}

5. ヘッダーが小さくなる

header {
  position: sticky; top: 0;
  height: 80px;
  animation: shrink linear both;
  animation-timeline: scroll(root);
  animation-range: 0 200px;
}
@keyframes shrink {
  to { height: 50px; backdrop-filter: blur(8px); }
}

6. 円形プログレス

<div class="ring" aria-hidden="true">
  <svg viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="45" stroke="#eee" fill="none" stroke-width="6"/>
    <circle class="bar" cx="50" cy="50" r="45" stroke="orange" fill="none" stroke-width="6"
      stroke-dasharray="282.7" stroke-dashoffset="282.7" transform="rotate(-90 50 50)"/>
  </svg>
</div>
.ring {
  position: fixed; bottom: 20px; right: 20px; width: 50px;
}
.bar {
  animation: fill linear both;
  animation-timeline: scroll(root);
}
@keyframes fill { to { stroke-dashoffset: 0; } }

7. 横スクロールギャラリー

<div class="hscroll">
  <div class="card">1</div>
  <div class="card">2</div>
  <div class="card">3</div>
</div>
.hscroll {
  display: flex; overflow-x: auto;
  scroll-snap-type: x mandatory;
  gap: 16px;
  scroll-timeline: --gallery x;
}
.card {
  flex: 0 0 80%;
  scroll-snap-align: center;
  view-timeline-name: --c;
  view-timeline-axis: x;

  animation: swap linear both;
  animation-timeline: --c;
  animation-range: cover 0% cover 100%;
}
@keyframes swap {
  0%   { opacity: 0.4; transform: scale(0.9); }
  50%  { opacity: 1;   transform: scale(1); }
  100% { opacity: 0.4; transform: scale(0.9); }
}

8. 章ごとのナビハイライト

body { timeline-scope: --s1, --s2, --s3; }
section.s1 { view-timeline-name: --s1; }
section.s2 { view-timeline-name: --s2; }
section.s3 { view-timeline-name: --s3; }

.toc a { color: #999; }
.toc a.t1 { animation: hi linear both; animation-timeline: --s1; animation-range: contain 0% contain 100%; }
.toc a.t2 { animation: hi linear both; animation-timeline: --s2; animation-range: contain 0% contain 100%; }
.toc a.t3 { animation: hi linear both; animation-timeline: --s3; animation-range: contain 0% contain 100%; }
@keyframes hi { to { color: orange; font-weight: 700; } }

9. テキストがスクロールでカラフルに

h1 {
  background: linear-gradient(90deg, #f9c, #fc6, #6cf);
  background-clip: text;
  color: transparent;
  background-size: 200% 100%;

  animation: hue linear both;
  animation-timeline: scroll(root);
}
@keyframes hue { to { background-position: -200% 0; } }

10. スクロールスナップ + ピン留め演出

.story {
  height: 300vh;                      /* 余白を作って長く */
  position: relative;
}
.sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  place-items: center;

  animation: scenes linear both;
  animation-timeline: view();
  animation-range: cover 0% cover 100%;
}
@keyframes scenes {
  0%   { background: #f9c; transform: translateY(0); }
  33%  { background: #fc6; transform: translateY(-10px); }
  66%  { background: #6cf; transform: translateY(0); }
  100% { background: #cf6; transform: translateY(10px); }
}

応用: 時計の針

.hand {
  animation: spin linear infinite;
  animation-duration: 1s;
  animation-timeline: scroll(root);
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

スクロールするたびに針がぐるぐる回る面白い演出。

応用: 数字カウント

@property --num {
  syntax: "<integer>";
  inherits: false;
  initial-value: 0;
}

.counter {
  --num: 0;
  counter-reset: c var(--num);
  animation: count linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 30%;
}
.counter::after { content: counter(c); }
@keyframes count { to { --num: 100; } }

@property + counter()滑らかに数字が増えていく表現。

失敗を防ぐ初期値

.reveal {
  /* 範囲外(=画面外)でも見えてしまわないよう、初期値を明示 */
  opacity: 0;
  transform: translateY(40px);

  animation: reveal linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 30%;
}

パターン化のコツ

  1. 何を動かすか」(要素 / プロパティ)を決める
  2. どこのスクロールに連動するか」(scroll() / view())
  3. どの範囲で動くか」(animation-range)
  4. 初期 / 終端の値を fill-mode: both で固定
推奨ライブラリ

ネイティブで足りないとき:
motion(軽量)
GSAP ScrollTrigger(重量級・万能)
Lenis(スムーズスクロール)
いずれもネイティブ scroll-driven と併用可能