メインコンテンツへスキップ
Code & Craft
Web制作 約10分で読めます

CSS Scroll-Driven Animations 実践ガイド|JSなしで実現するスクロール連動アニメーション【2026年版】

CSS scroll()・view() を使ったスクロール駆動アニメーションの実装方法を解説。プログレスバー、フェードイン、パララックスをJavaScriptなしで実現する具体例付き。

スクロールに連動したアニメーションは、これまで JavaScript の scroll イベントや Intersection Observer で実装するのが一般的でした。しかし CSS Scroll-Driven Animations の登場により、JavaScript を一行も書かずにスクロール連動アニメーションを実現できるようになりました。しかもメインスレッドをブロックしないため、60fps の滑らかなアニメーションが保証されます。

CSS Scroll-Driven Animations とは

CSS Scroll-Driven Animations は、従来の CSS Animations(時間ベース)を拡張し、スクロール位置をタイムラインとして使う仕組みです。2つのタイムラインが用意されています。

Scroll Progress Timeline(scroll())

スクロールコンテナのスクロール位置(0%〜100%)に連動するタイムラインです。ページ全体のスクロールに応じたアニメーションに使います。

/* ページスクロールに連動するプログレスバー */
.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  background: linear-gradient(to right, #0ea5e9, #a855f7);
  transform-origin: left;
  animation: grow-progress linear;
  animation-timeline: scroll();
}

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

たったこれだけで、ページ上部に読了プログレスバーが実装できます。JavaScript は不要です。

View Progress Timeline(view())

要素がビューポートに入ってから出るまでの進行状況(0%〜100%)に連動するタイムラインです。要素が画面に現れたタイミングでのフェードインなどに使います。

/* スクロールで画面に入ったらフェードイン */
.fade-in {
  animation: fade-in-up linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

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

animation-range: entry 0% entry 100% は、要素がビューポートに入り始めてから完全に入りきるまでの範囲でアニメーションを実行します。

ブラウザ対応状況(2026年4月)

ブラウザサポート状況
Chrome 115+完全サポート
Edge 115+完全サポート
Safari 26+完全サポート
Opera 101+完全サポート
Firefoxフラグ付き(部分サポート)

2026年4月時点で主要ブラウザの大半が対応済みです。Firefox はまだフラグの背後にありますが、layout.css.scroll-driven-animations.enabled を有効にすることで利用可能です。

プログレッシブ・エンハンスメント

未対応ブラウザへのフォールバックは @supports で簡単に実現できます。

/* 基本スタイル(すべてのブラウザ) */
.card {
  opacity: 1;
  transform: none;
}

/* Scroll-Driven Animations 対応ブラウザのみ */
@supports (animation-timeline: view()) {
  .card {
    animation: fade-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
  }
}

実践パターン5選

パターン1: 読了プログレスバー

ブログ記事の読了進捗を表示するプログレスバーです。

<div class="progress-bar" aria-hidden="true"></div>
<article class="post-content">
  <!-- 記事本文 -->
</article>
.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background: linear-gradient(90deg, #0ea5e9, #a855f7, #f97316);
  transform-origin: left;
  transform: scaleX(0);
  z-index: 100;
  animation: progress linear;
  animation-timeline: scroll();
}

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

パフォーマンスのポイント: transform: scaleX() はコンポジターレイヤーで処理されるため、width のアニメーションよりも圧倒的に高速です。JavaScript の scroll イベントリスナーと比較して、CPU 使用率が 50% → 2% に低減した事例も報告されています。

パターン2: カードのスタッガーフェードイン

記事カードが順番にフェードインするアニメーションです。

.post-card {
  animation: card-appear linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 80%;
}

@keyframes card-appear {
  from {
    opacity: 0;
    transform: translateY(40px) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

JavaScript の Intersection Observer で同等の機能を実装すると約20行のコードが必要ですが、CSS Scroll-Driven Animations では 6行の CSS で済みます。

パターン3: パララックス背景

スクロールに応じて背景がゆっくり動くパララックス効果です。

.hero-section {
  position: relative;
  overflow: hidden;
}

.hero-bg {
  position: absolute;
  inset: -20% 0;
  animation: parallax linear;
  animation-timeline: scroll();
}

@keyframes parallax {
  from { transform: translateY(-10%); }
  to   { transform: translateY(10%); }
}

inset: -20% 0 で背景を上下に20%拡張しておくことで、スクロール時に背景が切れるのを防ぎます。

パターン4: ヘッダーの縮小アニメーション

スクロールするとヘッダーがコンパクトになるアニメーションです。

.site-header {
  position: sticky;
  top: 0;
  animation: shrink-header linear forwards;
  animation-timeline: scroll();
  animation-range: 0px 200px;
}

@keyframes shrink-header {
  from {
    padding-block: 1.5rem;
    font-size: 1.25rem;
  }
  to {
    padding-block: 0.5rem;
    font-size: 1rem;
    backdrop-filter: blur(12px);
    background: rgba(255, 255, 255, 0.8);
  }
}

animation-range: 0px 200px で、スクロール開始から200pxまでの間でアニメーションが実行されます。200px以降はアニメーション終了状態が維持されます。

パターン5: 画像のスケールイン

スクロールで画像が拡大しながら表示されるシネマティックな効果です。

.hero-image {
  animation: scale-in linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
}

@keyframes scale-in {
  from {
    transform: scale(1.3);
    clip-path: inset(15%);
  }
  to {
    transform: scale(1);
    clip-path: inset(0%);
  }
}

clip-pathtransform: scale() を組み合わせることで、画像が枠から溢れずにスケールインする効果を実現しています。

animation-range の詳細

animation-range は Scroll-Driven Animations の中でも最も重要なプロパティです。View Progress Timeline で使える範囲キーワードを整理します。

キーワード意味
entry要素がビューポートに入り始める〜完全に入る
exit要素がビューポートから出始める〜完全に出る
contain要素が完全にビューポート内にある期間
cover要素がビューポートと交差している全期間
/* 要素が画面に入り始めて30%入ったところで完了 */
animation-range: entry 0% entry 30%;

/* 要素が画面の中央付近にある時だけ */
animation-range: contain 20% contain 80%;

/* 画面に入り始めてから出終わるまで全期間 */
animation-range: cover 0% cover 100%;

JavaScript 実装との比較

同じフェードインアニメーションを JavaScript(Intersection Observer)と CSS Scroll-Driven Animations で比較します。

JavaScript(Intersection Observer)

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add('visible');
        observer.unobserve(entry.target);
      }
    });
  },
  { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }
);

document.querySelectorAll('.fade-in').forEach((el) => {
  observer.observe(el);
});
.fade-in {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.6s, transform 0.6s;
}
.fade-in.visible {
  opacity: 1;
  transform: translateY(0);
}

合計: JS 14行 + CSS 10行 = 24行

CSS Scroll-Driven Animations

.fade-in {
  animation: fade-in linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

@keyframes fade-in {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

合計: CSS 9行のみ

パフォーマンス比較

指標JS (IO)CSS SDA
コード量24行9行
メインスレッド負荷ありなし
FPS状況依存常に60fps
バンドルサイズ+0.5KB0

CSS Scroll-Driven Animations はコンポジタースレッドで実行されるため、メインスレッドの処理(DOM 操作、API コール等)に影響しません。これが最大のメリットです。

アクセシビリティ対応

prefers-reduced-motion メディアクエリで、動きを減らしたいユーザーに配慮します。

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

これにより、OS の「視差効果を減らす」設定を有効にしているユーザーには、アニメーションが即座に完了状態になります。

Astro / Tailwind CSS での活用例

本ブログ(Code & Craft)で使用している Astro + Tailwind CSS v4 環境での実装例です。

/* global.css に追加 */
@supports (animation-timeline: view()) {
  .scroll-fade {
    animation: scroll-fade-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 80%;
  }
}

@keyframes scroll-fade-in {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
<!-- Astro コンポーネントで使用 -->
<section class="scroll-fade">
  <h2>最新記事</h2>
  <!-- ... -->
</section>

@supports でラップしているため、未対応ブラウザでは要素が最初から表示された状態になり、レイアウトが崩れることはありません。

まとめ

CSS Scroll-Driven Animations は、2026年の Web 開発における最も実用的な新機能の一つです。

導入すべき理由:

  • JavaScript コードの削減(平均60-80%のコード削減)
  • メインスレッドをブロックしない60fpsアニメーション
  • @supports による安全なプログレッシブ・エンハンスメント
  • 読了プログレスバー、フェードイン、パララックスなど多彩な表現

注意点:

  • Firefox はまだフラグの背後にある(2026年4月時点)
  • 複雑な条件分岐が必要なアニメーションには向かない(JavaScript が適切)
  • prefers-reduced-motion への対応は必須

まずは 読了プログレスバー(パターン1)から導入し、効果を実感してみてください。JavaScript のスクロールイベントを一掃できるインパクトは、一度体験すると戻れなくなるはずです。

参考リンク

#CSS #アニメーション #パフォーマンス #Web API #フロントエンド
シェア: