CSSスクロール駆動アニメーション:ゼロJSで視差スクロール、プログレスバー、リビール効果を実現

前端工程(更新: 2026年6月2日)

scrollイベントリスナーとの決別

従来のスクロールアニメーションはJavaScriptのscrollイベント監視に依存していました:

// ❌ 従来の方法:JSリスナー + 強制同期レイアウト
window.addEventListener('scroll', () => {
  const progress = window.scrollY / (document.body.scrollHeight - window.innerHeight);
  progressBar.style.width = `${progress * 100}%`;
  // 各scrollでリフローが発生!
});

CSSスクロール駆動アニメーションはこれらを純粋なCSSで実現し、コンポジタースレッドで実行され、メインスレッドをブロックしません:

/* ✅ 純粋なCSS:読書プログレスバー */
.progress-bar {
  width: 100%;
  height: 3px;
  background: blue;
  transform-origin: left;
  animation: progress linear;
  animation-timeline: scroll();
}

@keyframes progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

scroll-timeline構文

匿名スクロールタイムライン

/* 最も近いスクロール可能な祖先のスクロールに基づく */
.animated {
  animation-timeline: scroll();
}

/* 方向を指定 */
.animated-x {
  animation-timeline: scroll(x); /* 水平スクロール */
}

.animated-y {
  animation-timeline: scroll(y); /* 垂直スクロール */
}

名前付きスクロールタイムライン

/* スクロールコンテナにタイムライン名を宣言 */
.scroll-container {
  scroll-timeline: --page-scroll y;
  overflow-y: auto;
}

/* アニメーション要素で参照 */
.animated-element {
  animation: fade-in linear;
  animation-timeline: --page-scroll;
}

view-timeline構文

view-timelineは要素がビューポートに出入りする進行状況に基づきます:

.reveal-item {
  view-timeline: --item-reveal;
  animation: reveal linear both;
  animation-timeline: --item-reveal;
}

@keyframes reveal {
  from {
    opacity: 0;
    transform: translateY(50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

view-timeline-range:アニメーション範囲の正確な制御

.reveal-item {
  view-timeline: --item-reveal;
  animation: reveal linear both;
  animation-timeline: --item-reveal;
  /* 要素がビューポートに入る最初の30%でのみアニメーションを再生 */
  animation-range: entry 0% entry 30%;
}
range値 意味
entry 要素がビューポートに入り始める
exit 要素がビューポートから出始める
cover 要素が完全にビューポート内にある
contain 要素が完全に入ってから出始めるまで
entry-crossing 要素がビューポート端を越えて入っている

実践例1:視差スクロール

.parallax-container {
  overflow-y: auto;
  scroll-timeline: --parallax y;
  height: 100vh;
}

.parallax-bg {
  animation: parallax-move linear;
  animation-timeline: --parallax;
}

@keyframes parallax-move {
  from { transform: translateY(0); }
  to { transform: translateY(-200px); }
}

/* 異なる深度の視差速度 */
.parallax-layer-1 { animation: parallax-move-slow linear; animation-timeline: --parallax; }
.parallax-layer-2 { animation: parallax-move-medium linear; animation-timeline: --parallax; }
.parallax-layer-3 { animation: parallax-move-fast linear; animation-timeline: --parallax; }

@keyframes parallax-move-slow { to { transform: translateY(-50px); } }
@keyframes parallax-move-medium { to { transform: translateY(-150px); } }
@keyframes parallax-move-fast { to { transform: translateY(-300px); } }

実践例2:読書プログレスバー

/* ページ上部の読書進捗インジケーター */
.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: linear-gradient(90deg, #3b82f6, #8b5cf6);
  transform-origin: left;
  z-index: 1000;
  animation: reading-progress linear;
  animation-timeline: scroll(root y);
}

@keyframes reading-progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

実践例3:スクロールリビール(Scroll Reveal)

/* 下からフェードイン */
.reveal-up {
  view-timeline: --reveal;
  animation: reveal-up linear both;
  animation-timeline: --reveal;
  animation-range: entry 0% entry 40%;
}

@keyframes reveal-up {
  from {
    opacity: 0;
    transform: translateY(80px) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* 左からスライドイン */
.reveal-left {
  view-timeline: --reveal;
  animation: reveal-left linear both;
  animation-timeline: --reveal;
  animation-range: entry 0% entry 30%;
}

@keyframes reveal-left {
  from {
    opacity: 0;
    transform: translateX(-60px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

スタガードディレイ

/* animation-delayでスタガード効果を実現 */
.stagger-item:nth-child(1) { animation-delay: 0ms; }
.stagger-item:nth-child(2) { animation-delay: 80ms; }
.stagger-item:nth-child(3) { animation-delay: 160ms; }
.stagger-item:nth-child(4) { animation-delay: 240ms; }
.stagger-item:nth-child(5) { animation-delay: 320ms; }

実践例4:水平スクロールエリア

.horizontal-scroll {
  overflow-x: auto;
  scroll-timeline: --hscroll x;
}

.card {
  animation: card-appear linear both;
  animation-timeline: --hscroll;
  animation-range: entry 0% entry 20%;
}

@keyframes card-appear {
  from {
    opacity: 0;
    transform: scale(0.9);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

実践例5:スティッキーヘッダーの縮小

.page {
  scroll-timeline: --page y;
  overflow-y: auto;
}

.sticky-header {
  position: sticky;
  top: 0;
  animation: header-shrink linear both;
  animation-timeline: --page;
  animation-range: 0px 100px;
}

@keyframes header-shrink {
  from {
    padding: 24px;
    font-size: 2rem;
  }
  to {
    padding: 12px;
    font-size: 1.25rem;
  }
}

実践例6:画像スプライトシーケンスアニメーション

.sprite-container {
  scroll-timeline: --sprite y;
  overflow-y: auto;
}

.sprite-image {
  animation: sprite-steps steps(24) both;
  animation-timeline: --sprite;
}

@keyframes sprite-steps {
  from { background-position: 0 0; }
  to { background-position: -2400px 0; } /* 24フレーム × 100px幅 */
}

JS vs CSS スクロールアニメーション比較

次元 JS(scrollイベント) CSS scroll-timeline
パフォーマンス メインスレッド、カクつきの可能性 コンポジタースレッド、60fps
コード量 20-50行 5-10行
互換性 全ブラウザ Chrome 115+, Firefox 125+
柔軟性 任意のロジック 線形/区分アニメーション
デバッグ console.log DevTools Animationsパネル
メモリ 手動でリスナークリーンアップ必要 自動管理

Polyfill戦略

// サポート検出
const supportsScrollTimeline = CSS.supports('animation-timeline', 'scroll()');

if (!supportsScrollTimeline) {
  // IntersectionObserver + CSSクラス切り替えにフォールバック
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      entry.target.classList.toggle('in-view', entry.isIntersecting);
    });
  }, { threshold: 0.2 });

  document.querySelectorAll('.reveal-item').forEach(el => {
    observer.observe(el);
  });
}

ブラウザ互換性

ブラウザ scroll-timeline view-timeline animation-range
Chrome 115+
Edge 115+
Firefox 125+
Safari 17.4+

Safariは現在実装中です。機能検出 + IntersectionObserverフォールバックの使用を推奨します。


ベストプラクティス

  1. view-timelineを優先:ほとんどのスクロールリビール効果はscroll-timelineよりもview-timelineの方がシンプル
  2. animation-rangeの正確な制御:要素が見えないときにアニメーションが実行されないようにする
  3. will-changeとの併用:アニメーション要素にwill-change: transform, opacityを追加
  4. 同時アニメーション要素を制限:20以上の同時実行スクロールアニメーションはパフォーマンスに影響する可能性
  5. 機能検出フォールバック:Safariがサポートしていない場合はIntersectionObserverでフォールバック

まとめ

CSSスクロール駆動アニメーションは2026年のフロントエンドで最もエキサイティングな新機能の1つです。視差スクロール、読書プログレスバー、スクロールリビールなどのクラシックな効果をJSからCSSに戻し、パフォーマンスが大幅に向上します(コンポジタースレッドで実行され、メインスレッドをブロックしません)。Safariのサポートはまだ完全ではありませんが、IntersectionObserverフォールバックと組み合わせることで、今すぐ本番環境で使用できます。

ブラウザローカルツールを無料で試す →

#滚动驱动动画#CSS动画#scroll-timeline#视差滚动#无JS动画