CSS Scroll-driven Animations 完全ガイド - JavaScriptなしでスクロール連動アニメーション

CSS Scroll-driven Animations 完全ガイド - JavaScriptなしでスクロール連動アニメーション

作成日:
更新日:

CSS Scroll-driven Animationsとは

CSS Scroll-driven Animationsは、JavaScriptを使わずにスクロール位置に連動したアニメーションをCSSだけで実装できる新しいWeb標準機能です。2023年にChrome 115で実装され、現在ではChrome、Edge、Safariなど主要ブラウザで広くサポートされています。

従来、スクロール連動アニメーションを実装するには、JavaScriptのscrollイベントリスナーやIntersection Observer API、あるいはGSAPなどのライブラリを使用する必要がありました。しかし、これらの方法には以下のような課題がありました:

課題詳細
パフォーマンスJavaScriptはメインスレッドで実行されるため、スクロール中に処理が重くなる可能性がある
複雑さイベントリスナーの登録/解除、デバウンス処理など、ボイラープレートコードが必要
バッテリー消費頻繁なJavaScript実行はモバイルデバイスのバッテリーを消費する

CSS Scroll-driven Animationsは、これらの問題を解決し、宣言的で高パフォーマンスなスクロールアニメーションを実現します。

主な特徴

  • 純粋なCSS: JavaScriptのコードが不要
  • GPUアクセラレーション: コンポジターで処理されるため60fpsを維持
  • 宣言的: アニメーションの意図が明確で保守しやすい
  • プログレッシブエンハンスメント: 未サポートブラウザでは通常のページ表示にフォールバック

2種類のスクロールタイムライン

CSS Scroll-driven Animationsには、2種類のタイムラインがあります:

1. Scroll Progress Timeline(スクロール進捗タイムライン)

スクローラー(スクロール可能な要素)のスクロール位置をタイムラインとして使用します。ページ全体や特定のコンテナのスクロール量に基づいてアニメーションを制御します。

使用例:

  • ページの読了プログレスバー
  • スクロールに連動した背景のパララックス
  • ナビゲーションの表示/非表示

2. View Progress Timeline(表示進捗タイムライン)

特定の要素がビューポート(または他のスクローラー)内でどの程度表示されているかをタイムラインとして使用します。

使用例:

  • 要素が画面に入った時のフェードイン
  • カードのスタック表示
  • スクロールに連動したリビール効果

scroll() 関数:スクロール進捗タイムライン

scroll()関数は、最も基本的なスクロールタイムラインを作成します。スクローラーのスクロール位置0%〜100%をアニメーションの進行度0%〜100%にマッピングします。

基本構文

CSS
.element {
  animation: my-animation linear;
  animation-timeline: scroll();
}
 
@keyframes my-animation {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(100px);
  }
}

scroll() のパラメータ

CSS
animation-timeline: scroll(<scroller> <axis>);
パラメータ説明
scrollernearest最も近い祖先のスクローラー(デフォルト)
rootドキュメントのルート要素(ページ全体)
self要素自身
axisblockブロック軸(縦書きなら横、横書きなら縦)(デフォルト)
inlineインライン軸
y垂直軸
x水平軸

デモ:scroll() の基本

以下のデモでは、scroll()関数の基本的な動作を確認できます。コンテナ内をスクロールすると、以下の効果が見られます:

  • プログレスバー: スクロール位置に応じて横幅が変化
  • 回転する要素: スクロール量に比例して回転
  • 色が変化する背景: スクロールに応じてグラデーションが変化

animation-timeline: scroll()を指定することで、通常の時間ベースのアニメーションがスクロール位置ベースに変換されます。これはCSSのみで実現されており、JavaScriptは一切使用していません。

↓ このコンテナ内をスクロールしてください

scroll() 関数のデモ

スクロール位置に連動してアニメーションが進行します

セクション 1

このセクションはスクロール位置 0% 〜 20% の範囲でアニメーションします。

セクション 2

このセクションはスクロール位置 20% 〜 40% の範囲でアニメーションします。

セクション 3

このセクションはスクロール位置 40% 〜 60% の範囲でアニメーションします。

セクション 4

このセクションはスクロール位置 60% 〜 80% の範囲でアニメーションします。

セクション 5

このセクションはスクロール位置 80% 〜 100% の範囲でアニメーションします。

このデモのポイントは、animation-timing-function: linearを使用していることです。スクロールベースのアニメーションでは、線形(linear)のイージングが最も直感的な動きを生み出します。easeease-in-outを使用すると、スクロール速度に関係なく加速・減速が発生するため、ユーザーの操作と動きがずれて感じることがあります。


view() 関数:表示進捗タイムライン

view()関数は、要素がスクローラーのビューポート内に表示される度合いをタイムラインとして使用します。要素が画面外から入ってきて、完全に表示され、また画面外に出ていくまでの進捗を追跡します。

基本構文

CSS
.element {
  animation: appear linear;
  animation-timeline: view();
}
 
@keyframes appear {
  from {
    opacity: 0;
    transform: translateY(50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

view() のパラメータ

CSS
animation-timeline: view(<axis> <inset>);
パラメータ説明
axisblock, inline, y, xscroll()と同様
inset<length-percentage>ビューポートの内側オフセット

inset の例:

CSS
/* 上下から20%の余白を持たせる */
animation-timeline: view(block 20%);
 
/* 上から100px、下から50pxの余白 */
animation-timeline: view(block 100px 50px);

デモ:view() の基本

以下のデモは、view()関数を使用したビューポートベースのアニメーションです。各カードがスクロールによってビューポート内に入ると、フェードイン+スライドインのアニメーションが発生します。

view()関数とscroll()関数の違いを理解することが重要です:

関数タイムラインの基準典型的な用途
scroll()スクローラー全体のスクロール位置(0%〜100%)ページ全体に連動するプログレスバー、背景のパララックス
view()特定の要素がビューポート内にどれだけ表示されているか要素が見えた時のフェードイン、リビール効果

↓ スクロールして各要素が表示領域に入る様子を確認してください

↓ 下にスクロール ↓
1

view() アニメーション #1

この要素は view() タイムラインにより、 ビューポートに入る際にアニメーションします。animation-range で開始・終了位置を制御しています。

2

view() アニメーション #2

この要素は view() タイムラインにより、 ビューポートに入る際にアニメーションします。animation-range で開始・終了位置を制御しています。

3

view() アニメーション #3

この要素は view() タイムラインにより、 ビューポートに入る際にアニメーションします。animation-range で開始・終了位置を制御しています。

4

view() アニメーション #4

この要素は view() タイムラインにより、 ビューポートに入る際にアニメーションします。animation-range で開始・終了位置を制御しています。

5

view() アニメーション #5

この要素は view() タイムラインにより、 ビューポートに入る際にアニメーションします。animation-range で開始・終了位置を制御しています。

↑ 上にスクロールして戻る ↑

このデモでスクロールを逆方向に動かすと、アニメーションも逆再生されることを確認してください。これはCSS Scroll-driven Animationsの大きな特徴の一つです。従来のIntersection Observerを使った実装では、一度トリガーされたアニメーションは逆再生されませんでした。Scroll-driven Animationsでは、スクロール位置とアニメーションが常に同期しているため、自然な双方向性が実現されています。


animation-range:アニメーション範囲の制御

animation-rangeプロパティを使用すると、タイムラインのどの範囲でアニメーションを実行するかを細かく制御できます。

基本構文

CSS
animation-range: <start> <end>;
 
/* または個別に指定 */
animation-range-start: entry 0%;
animation-range-end: cover 50%;

利用可能なキーワード

キーワード説明
cover要素が完全にビューポートを覆う範囲(入り始めから出終わりまで)
contain要素がビューポート内に完全に収まっている範囲
entry要素がビューポートに入り始める範囲
exit要素がビューポートから出る範囲
entry-crossing要素がentry境界を横切る範囲
exit-crossing要素がexit境界を横切る範囲

パーセンテージとの組み合わせ

CSS
/* entryの0%から50%の範囲でアニメーション */
animation-range: entry 0% entry 50%;
 
/* coverの25%から75%の範囲でアニメーション */
animation-range: cover 25% cover 75%;
 
/* 混合指定も可能 */
animation-range: entry 0% exit 100%;

デモ:animation-range の比較

以下のデモでは、異なるanimation-rangeキーワードがどのように動作するかを比較できます。各カラーバーは、それぞれ異なるアニメーション範囲を持っています。

各キーワードの詳細な動作

  • cover(デフォルト): 要素が「見え始めてから」「完全に見えなくなるまで」の全範囲。最も広い範囲をカバーします。
  • contain: 要素が「完全にビューポート内に収まっている」範囲のみ。要素がビューポートより大きい場合は、ビューポートが要素内に完全に収まっている範囲になります。
  • entry: 要素がビューポートに「入り始める」フェーズのみ。要素の上端がビューポートの下端に触れてから、要素全体がビューポート内に入るまで。
  • exit: 要素がビューポートから「出ていく」フェーズのみ。要素の上端がビューポートの上端に触れてから、要素全体がビューポートから出るまで。

animation-range キーワードの動作を比較

左端が進行度0%、右端が進行度100%を表します

↓ スクロールして各 range の動作を確認 ↓

cover

要素が完全にビューポートを覆う範囲

0%animation-range: cover100%

contain

要素がビューポート内に完全に収まる範囲

0%animation-range: contain100%

entry

要素がビューポートに入り始める範囲

0%animation-range: entry100%

exit

要素がビューポートから出る範囲

0%animation-range: exit100%

entry-crossing

entry境界を横切る範囲

0%animation-range: entry-crossing100%

exit-crossing

exit境界を横切る範囲

0%animation-range: exit-crossing100%
↑ 上にスクロール ↑

このデモをスクロールしながら、各バーのアニメーションが開始・終了するタイミングの違いを観察してください。animation-rangeを適切に使い分けることで、ユーザーの視線の動きに合わせた自然なアニメーションを設計できます。

例えば、フェードインアニメーションにはentryを、フェードアウトにはexitを使うことで、要素が見える範囲では常に100%の不透明度を維持できます。


実用例1:読了プログレスバー

記事の読了率を表示するプログレスバーは、Scroll-driven Animationsの最も実用的な使用例の一つです。ブログやドキュメントサイトでよく見かけるこの機能は、従来はJavaScriptで実装する必要がありましたが、CSSだけで簡単に実現できるようになりました。

実装のポイント

  1. position: fixedでバーを画面上部に固定
  2. transform-origin: leftで左端を基点に伸縮
  3. scroll(root)でページ全体のスクロールをタイムラインとして使用
  4. scaleX()でバーの幅を0〜1に変化させる

scaleX()を使用することで、widthプロパティを直接アニメーションするよりもパフォーマンスが向上します。transformはGPUで処理されるため、レイアウトの再計算が発生しません。

CSS
.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 4px;
  background: linear-gradient(to right, #06B6D4, #8B5CF6);
  transform-origin: left;
  animation: progress-grow linear;
  animation-timeline: scroll(root);
}
 
@keyframes progress-grow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

デモ:プログレスバー

実用例:記事の読了プログレスバー

読了率

サンプル記事タイトル

CSS Scroll-driven Animations を使用すると、JavaScriptを使わずにスクロールに連動したプログレスバーを実装できます。 これにより、パフォーマンスが向上し、コードもシンプルになります。

第1章:基礎知識

従来、スクロール連動のアニメーションはJavaScriptの scroll イベントリスナーや Intersection Observer API を使用して実装されていました。 しかし、これらの方法はメインスレッドで実行されるため、パフォーマンスに影響を与える可能性がありました。

第2章:animation-timeline

animation-timeline プロパティは、 アニメーションの進行を制御するタイムラインを指定します。scroll() 関数を使用すると、 スクロール位置がタイムラインになります。

第3章:実装例

プログレスバーの実装は非常にシンプルです。@keyframes で幅を0%から100%に変化させ、animation-timeline: scroll() を指定するだけです。 ブラウザが自動的にスクロール位置をアニメーションの進行度にマッピングします。

第4章:応用テクニック

animation-range を使用することで、 アニメーションが開始・終了するスクロール位置を細かく制御できます。 これにより、特定のセクションでのみアニメーションを実行するなど、 より高度な演出が可能になります。

🎉 最後までスクロールしました!

実用例2:パララックス効果

パララックス(視差)効果は、複数のレイヤーを異なる速度で移動させることで、奥行き感を演出するテクニックです。映画のオープニングやゲームの背景でよく使われており、Webサイトでも印象的なビジュアル体験を提供できます。

パララックスの原理

人間は、近くにあるものは速く動き、遠くにあるものはゆっくり動くと認識します。この視覚的な性質を利用して、複数のレイヤーに異なる移動量を設定することで、3D的な奥行き感を2D画面上で表現できます。

レイヤー移動速度視覚的な効果
背景(遠景)遅い(小さい値)遠くにあるように見える
中景中程度中間の距離に見える
前景速い(大きい値)近くにあるように見える

従来のJavaScript実装では、scrollイベントのスロットリングやGPUアクセラレーションの管理が必要でしたが、CSS Scroll-driven Animationsではこれらが自動的に最適化されます。

CSS
.background-layer {
  animation: parallax-slow linear;
  animation-timeline: scroll();
}
 
.foreground-layer {
  animation: parallax-fast linear;
  animation-timeline: scroll();
}
 
@keyframes parallax-slow {
  to { transform: translateY(-50px); }
}
 
@keyframes parallax-fast {
  to { transform: translateY(-200px); }
}

デモ:パララックス効果

以下のデモでは、3つのレイヤー(背景、中景、前景)が異なる速度で移動します。コンテナ内をスクロールしながら、各レイヤーの動きの違いを観察してください。

CSSのみで実装されたパララックス効果(レイヤーごとに異なる速度で移動)

CSS Parallax

スクロールすると各レイヤーが異なる速度で移動します

1

Scene 1

パララックス効果により、背景・中景・前景が異なる速度で移動し、 奥行き感のある体験を提供します。

2

Scene 2

パララックス効果により、背景・中景・前景が異なる速度で移動し、 奥行き感のある体験を提供します。

3

Scene 3

パララックス効果により、背景・中景・前景が異なる速度で移動し、 奥行き感のある体験を提供します。

このデモでは、背景(星空)は最もゆっくり、前景(装飾的な要素)は最も速く動きます。これにより、ユーザーがスクロールするたびに奥行きのある空間を移動しているような感覚が得られます。

パフォーマンスに関する注意: パララックス効果は視覚的に魅力的ですが、過度に使用するとユーザーの目を疲れさせたり、モーション酔いを引き起こす可能性があります。prefers-reduced-motionメディアクエリを使用して、ユーザーの設定を尊重することを忘れないでください。


実用例3:カードのスタック展開

スティッキーポジション(position: sticky)とスクロールアニメーションを組み合わせることで、スクロールに応じてカードが順番に展開・重なっていく印象的な効果を作成できます。このパターンは、製品紹介ページやポートフォリオサイトで特に効果的です。

実装のキーポイント

  1. position: sticky: カードを特定の位置に固定しつつ、スクロール可能にする
  2. view()タイムライン: 各カードが見えているかどうかでアニメーションを制御
  3. animation-range: entryフェーズでフェードイン、coverで最大状態を維持
  4. z-indexの制御: 後のカードが前のカードの上に重なるように設定

この効果の魅力は、ユーザーがスクロールするたびに新しい情報が「明らかになる」感覚を与えられることです。ストーリーテリング型のコンテンツや、ステップバイステップの説明に最適です。

CSS
.card {
  position: sticky;
  top: 20px;
  animation: card-reveal linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 50%;
}
 
@keyframes card-reveal {
  from {
    opacity: 0.5;
    transform: scale(0.9) translateY(20px);
    filter: blur(2px);
  }
  to {
    opacity: 1;
    transform: scale(1) translateY(0);
    filter: blur(0);
  }
}

デモ:カードスタック

以下のデモでは、スクロールに応じてカードが順番にフォーカスされ、上に重なっていきます。各カードにはposition: stickyが設定されており、ビューポート内に留まりながら次のカードに押し上げられていきます。

スクロールに応じてカードが順番に展開されるアニメーション

↓ スクロールしてカードを展開 ↓

パフォーマンス

メインスレッドをブロックしないGPUアクセラレーション

シンプルさ

JavaScriptなしでスクロール連動アニメーション

🌐

互換性

最新ブラウザで広くサポート(Chrome, Edge, Safari)

🎯

柔軟性

animation-range で細かいタイミング制御

🛠️

保守性

CSSのみで実装、コードの見通しが良い

🎉 すべてのカードが展開されました

デモの操作方法

  1. コンテナ内をゆっくりスクロールしてください
  2. 各カードがフェードイン&スケールアップする様子を観察
  3. 新しいカードが現れると、前のカードの上に重なっていきます

この効果は、ランディングページの機能紹介、チームメンバーの紹介、製品の特徴説明など、複数の項目を順番に見せたい場面で効果的です。


名前付きタイムライン

より複雑なレイアウトでは、scroll-timeline-nameview-timeline-nameを使用して名前付きタイムラインを作成し、異なる要素間でタイムラインを共有できます。

scroll-timeline の使用

CSS
/* スクローラーにタイムラインを定義 */
.scroller {
  overflow-y: scroll;
  scroll-timeline-name: --my-scroller;
  scroll-timeline-axis: block;
}
 
/* または短縮形 */
.scroller {
  scroll-timeline: --my-scroller block;
}
 
/* アニメーション要素でタイムラインを参照 */
.animated {
  animation: slide-in linear;
  animation-timeline: --my-scroller;
}

view-timeline の使用

CSS
/* トラッキングする要素にタイムラインを定義 */
.tracked-element {
  view-timeline-name: --card-timeline;
  view-timeline-axis: block;
}
 
/* 別の要素でそのタイムラインを使用 */
.indicator {
  animation: progress linear;
  animation-timeline: --card-timeline;
}

timeline-scope

デフォルトでは、名前付きタイムラインは子孫要素からのみ参照できます。timeline-scopeを使用すると、タイムラインのスコープを拡張し、兄弟要素からも参照できるようになります。

CSS
.container {
  timeline-scope: --my-timeline;
}
 
.container .source {
  view-timeline-name: --my-timeline;
}
 
.container .target {
  animation-timeline: --my-timeline;
}

JavaScriptとの連携

CSS Scroll-driven Animationsは、Web Animations APIと連携することで、JavaScriptからより細かい制御が可能になります。

ScrollTimeline API

JavaScript
// ScrollTimelineの作成
const scrollTimeline = new ScrollTimeline({
  source: document.scrollingElement,
  axis: 'block',
});
 
// アニメーションにタイムラインを適用
element.animate(
  [
    { transform: 'translateX(-100%)' },
    { transform: 'translateX(0)' }
  ],
  {
    timeline: scrollTimeline,
    fill: 'both',
  }
);

ViewTimeline API

JavaScript
// ViewTimelineの作成
const viewTimeline = new ViewTimeline({
  subject: document.querySelector('.target'),
  axis: 'block',
});
 
// アニメーションに適用
element.animate(
  [
    { opacity: 0, transform: 'scale(0.8)' },
    { opacity: 1, transform: 'scale(1)' }
  ],
  {
    timeline: viewTimeline,
    fill: 'both',
  }
);

ブラウザサポートとフォールバック

現在のサポート状況(2026年1月時点)

ブラウザサポート状況
Chrome115+ ✓
Edge115+ ✓
Safari18+ ✓
Firefoxフラグ付きで実験的サポート

フォールバック戦略

CSS
/* 基本のスタイル(すべてのブラウザ) */
.element {
  opacity: 1;
  transform: translateY(0);
}
 
/* Scroll-driven Animations をサポートするブラウザ */
@supports (animation-timeline: scroll()) {
  .element {
    animation: fade-in linear;
    animation-timeline: scroll();
  }
}

JavaScript でのフィーチャーディテクション

JavaScript
if (CSS.supports('animation-timeline', 'scroll()')) {
  console.log('Scroll-driven Animations がサポートされています');
} else {
  // Intersection Observer などでフォールバック
  const observer = new IntersectionObserver(/* ... */);
}

パフォーマンス最適化

transform と opacity を使用

CSS Scroll-driven Animationsでも、アニメーションにはtransformopacityを使用することが重要です。これらはコンポジターで処理されるため、レイアウトの再計算が発生しません。

CSS
/* ✅ Good: transform と opacity */
@keyframes good-animation {
  from {
    opacity: 0;
    transform: translateY(50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
 
/* ❌ Avoid: レイアウトプロパティ */
@keyframes bad-animation {
  from {
    margin-top: 50px;
    height: 0;
  }
  to {
    margin-top: 0;
    height: 100px;
  }
}

will-change の適切な使用

必要な場合のみwill-changeを使用し、アニメーション完了後は削除します。

CSS
.animated-element {
  will-change: transform, opacity;
}

prefers-reduced-motion の尊重

アクセシビリティのため、ユーザーがモーションを減らす設定をしている場合は、アニメーションを無効化または簡略化します。

CSS
@media (prefers-reduced-motion: reduce) {
  .element {
    animation: none;
  }
}

まとめ

CSS Scroll-driven Animationsは、Webアニメーションの実装方法を大きく変える可能性を秘めた技術です。

メリット

メリット詳細
パフォーマンスGPUアクセラレーションによる60fps
シンプルさJavaScriptなしでスクロール連動アニメーション
保守性宣言的なCSSで意図が明確
プログレッシブ未サポートブラウザでも基本機能は動作

使いどころ

  • 読了プログレスバー
  • スクロールリビール(フェードイン)効果
  • パララックス背景
  • スティッキーヘッダーのアニメーション
  • ストーリーテリング型のウェブサイト

注意点

  • ブラウザサポートの確認(特にFirefox)
  • transform/opacity中心のアニメーション設計
  • アクセシビリティへの配慮

JavaScript無しでここまで高度なスクロールアニメーションが実装できるのは、Web開発における大きな進歩です。ぜひプロジェクトに取り入れてみてください!


参考リンク