スクロールアニメーションをCSSだけで実装。scroll-driven animationsの使い方

はじめに

スクロールに合わせて進捗バーを伸ばしたり、セクションが入ってくるタイミングで自然に見せたりしたい場面は、いまもかなり多いです。これまではscrollイベントやIntersectionObserverを組み合わせて作ることが多かったですが、最近はCSSのscroll-driven animationsでかなり素直に書けるようになりました。

特にうれしいのは、「スクロール量に応じてアニメーションを進める」という本質の部分をCSS側に寄せられることです。JavaScriptで毎フレーム値を計算しなくてよくなるので、実装も見通しも軽くなります。この記事では、scroll()view()の違いから、進捗バーとセクションの出現演出をどう実務に落とし込むかまで、試しやすい形でまとめます。

機能やライブラリの概要

scroll-driven animationsは、時間ではなくスクロール位置をタイムラインとして使う仕組みです。MDNでは、通常のCSSアニメーションが時間ベースのDocumentTimelineで進むのに対して、この機能ではスクロール進捗や要素の見え具合を使ってアニメーションを進めると整理されています。

実務でまず覚えておくと使いやすいのは次の2つです。

  • scroll()
    ページやスクロールコンテナのスクロール量そのものをタイムラインにします。読了率バーや横スクロールUIの進捗表示と相性がいいです。
  • view()
    要素が画面内にどれだけ入ってきたかをタイムラインにします。カード一覧や記事本文の見出し、画像のフェードインに向いています。

さらに、animation-rangeを使うと「入ってきた最初の30%だけでアニメーションを終える」といった調整もできます。この組み合わせがあるので、単なるデモではなく、実務でもちょうどいい強さの演出を作りやすいです。

基本の使い方

最初はページ上部の読了率バーです。このコードでは、ページ全体のスクロール量に合わせてバーを左から右へ伸ばしています。

HTML
<div class="reading-progress"></div>

<main class="article">
  <section>
    <h2>導入</h2>
    <p>...</p>
  </section>
  <section>
    <h2>本文</h2>
    <p>...</p>
  </section>
</main>
CSS
@keyframes grow-progress {
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
}

.reading-progress {
  position: fixed;
  inset: 0 0 auto;
  block-size: 4px;
  z-index: 1000;
  background: linear-gradient(90deg, #0f766e, #14b8a6);
  transform-origin: left center;

  animation: grow-progress auto linear both;
  animation-timeline: scroll();
}

やっていることはかなりシンプルで、通常の@keyframesに対してanimation-timeline: scroll()を指定しているだけです。ポイントはanimation-durationを秒数ではなくautoにしているところで、これによって時間ではなくスクロール進捗でアニメーションが進みます。

従来の実装だと、scrollイベントで割合を計算してwidthtransformを更新することが多かったはずです。scroll-driven animationsなら、バー自体の見た目をCSSに閉じ込めやすいので、記事ページやドキュメントページのような定番UIほど差が出ます。

便利な使いどころ

一番使いやすいのは、「スクロール量と1対1で結びつく演出」です。

  • 記事ページやLPの読了率バー
  • 横スクロールカルーセルの進捗インジケーター
  • セクション見出しやカード一覧の軽いフェードイン
  • 長いフォームや設定画面での現在地表示

この機能が実務向きなのは、派手な演出よりも「補助UIを自然にする」方向で効くからです。たとえば記事詳細で読了率バーを付けるなら、JavaScriptでスクロール監視を書かなくても済みます。カード一覧のフェードインでも、IntersectionObserverでクラスを付け外しする実装より、CSSの責務として整理しやすいです。

もうひとつ大きいのは、view()を使うと「見え始めたタイミングだけ少し動かす」が書きやすいことです。全部の要素を大きく動かすとやりすぎですが、数十ピクセルの移動と透明度の変化だけなら、コンテンツの読みやすさを壊さずに導線を整えられます。

応用コード

ここでは、記事一覧カードがビューポートに入ってきたときだけ、軽く持ち上がるように見せる例です。view()で要素の見え具合を取り、animation-rangeで「入ってくる最初の区間だけ」動かしています。

HTML
<section class="news-list">
  <article class="news-card">
    <h2>Scroll-driven animationsを試す</h2>
    <p>スクロール位置に応じて進むアニメーションをCSSだけで実装します。</p>
  </article>

  <article class="news-card">
    <h2>view()で見出しを整える</h2>
    <p>出現タイミングを揃えると、一覧の読みやすさがかなり変わります。</p>
  </article>
</section>
CSS
@keyframes reveal-card {
  from {
    opacity: 0;
    transform: translateY(24px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.news-list {
  display: grid;
  gap: 24px;
}

.news-card {
  padding: 24px;
  border: 1px solid #d7e0ea;
  border-radius: 20px;
  background: #ffffff;
  box-shadow: 0 16px 40px rgba(15, 23, 42, 0.08);

  animation: reveal-card linear both;
  animation-timeline: view();
  animation-range: entry 10% cover 35%;
}

@supports not (animation-timeline: view()) {
  .news-card {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .reading-progress,
  .news-card {
    animation: none;
  }
}

このコードでは、カードが見え始めて少し入ってきたところでアニメーションを終えるようにしています。entry 10% cover 35%としているので、画面内に完全に入るまで引っ張らず、読み始める頃にはもう落ち着いた状態になっています。

実務ではこのくらいの短いレンジにしておくと使いやすいです。LPでも管理画面でも、長く動き続ける演出より、「出現だけ少し整う」ほうが邪魔になりません。prefers-reduced-motionもセットで置いておけば、演出を足しつつ過剰にはなりにくいです。

注意点

  • animationショートハンドのあとにanimation-timelineを書く
    MDNでも案内されていますが、animationショートハンドはanimation-timelineを初期値に戻します。順番を逆にすると、設定したタイムラインが消えてはまりやすいです。
  • animation-rangeは段階導入前提で考える
    2026年3月31日時点で、Can I useではAnimation APIのtimeline関連は高い普及率が出ていますが、MDNではanimation-timelineanimation-rangeはいずれもLimited availabilityです。つまり、多くの環境で使えても、全面的な前提にはまだしにくい段階です。
  • transform中心で組む
    進捗バーならwidthよりscaleX()、カードの出現ならtopよりtranslateY()のほうが素直です。スクロール演出は更新頻度が高いので、レイアウト変化を起こしやすいプロパティは避けたほうが安定します。
  • 演出は「補助」に留める
    スクロール量に連動できるぶん、やろうと思えばかなり多くの要素を動かせます。ただ、本文や管理画面では動く要素が多いほど読みにくくなります。最初は進捗バー、画像、カード一覧あたりから入れるのが安全です。

まとめ

scroll-driven animationsは、スクロール演出をJavaScriptで頑張る前に試したい機能です。読了率バーのような定番UIは特に相性がよく、view()まで使えると、要素の出現演出もかなり整理しやすくなります。

全部を置き換える必要はありません。まずは記事ページの進捗バーやカード一覧の軽いフェードインのように、効果が分かりやすくて失敗しにくい場所から入れるのがおすすめです。うまくはまると、コード量よりも「責務の分かれ方」がかなりきれいになります。

ポイント

  • scroll()は読了率バーや進捗表示、view()はカードや画像の出現演出に使い分けやすい
  • animation-rangeを短めに切ると、やりすぎないスクロール演出にしやすい
  • @supportsprefers-reduced-motionをセットにすると、実務導入しやすい

参考リンク

read next