scroll() — スクロール連動
スクロールコンテナのスクロール量をタイムラインにする。 ページ進捗バー、横スクロールギャラリー、章リーディング進捗に最適。
構文
animation-timeline: scroll(<scroller> <axis>);
/* <scroller>: nearest | root | self */
/* <axis>: block | inline | x | y */
- nearest(既定): 最寄りのスクロール可能な祖先
- root: ページのスクロール(<html>)
- self: 自分自身(要素自体がスクロール可能なら)
- block: 縦方向(既定)
- inline: 横方向
例 1: ページ全体の進捗バー
<div class="progress"></div>
.progress {
position: fixed; top: 0; left: 0;
width: 100%; height: 4px;
background: orange;
transform-origin: left;
transform: scaleX(0);
animation: grow linear both;
animation-timeline: scroll(root);
}
@keyframes grow {
to { transform: scaleX(1); }
}
例 2: 横スクロールでカードがフェード
<div class="gallery">
<div class="card">...</div>
<div class="card">...</div>
...
</div>
.gallery {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 16px;
}
.card {
flex: 0 0 80%;
scroll-snap-align: start;
animation: highlight linear;
animation-timeline: scroll(nearest inline); /* gallery の横スクロール */
}
@keyframes highlight {
from { opacity: 0.5; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
例 3: 階層を跨ぐ scroll(root)
/* どこに配置されている要素でも、ページのスクロールを使う */
.shared-progress {
animation: spin linear infinite;
animation-timeline: scroll(root);
}
@keyframes spin {
to { transform: rotate(360deg); }
}
scroll(self) で自要素のスクロール
.scrollbox {
height: 300px;
overflow-y: auto;
animation: shadow linear;
animation-timeline: scroll(self);
}
@keyframes shadow {
from { box-shadow: 0 0 0 transparent; }
to { box-shadow: 0 8px 16px rgba(0,0,0,.2); }
}
scroll() の進行度
進行度 = (現在のスクロール位置) / (最大スクロール位置)
- 上端: 0
- 下端: 1
- 真ん中: 0.5
animation-range で範囲を絞る
/* スクロールの 20%〜80% の間でアニメ */
animation-range: 20% 80%;
%, vh, vw, px の指定
animation-range: 100px 500px; /* 絶対 px */
animation-range: 0vh 50vh; /* ビューポート */
animation-range: 0% 100%; /* スクロール全体 */
典型レシピ集
1. リーディング進捗
.reading-bar {
position: fixed; top: 0; left: 0;
width: 100%; height: 3px;
background: linear-gradient(to right, orange, red);
transform-origin: left;
transform: scaleX(0);
animation: read linear both;
animation-timeline: scroll(root);
}
@keyframes read { to { transform: scaleX(1); } }
2. ヘッダーが小さくなる
.header {
height: 80px;
animation: shrink linear both;
animation-timeline: scroll(root);
animation-range: 0 200px;
}
@keyframes shrink {
to { height: 50px; backdrop-filter: blur(8px); }
}
3. 文字色がスクロールで変わる
h1 {
animation: color-shift linear both;
animation-timeline: scroll(root);
}
@keyframes color-shift {
0% { color: #111; }
50% { color: #c33; }
100% { color: #06f; }
}
4. 背景に円形の進捗
body {
background: conic-gradient(orange 0%, transparent 0%);
animation: ring linear both;
animation-timeline: scroll(root);
}
@keyframes ring {
to { background: conic-gradient(orange 100%, transparent 0%); }
}
5. 横スクロールに沿った変形
.scroller {
display: flex;
overflow-x: auto;
width: 100%;
}
.item {
flex: 0 0 100%;
animation: tilt linear both;
animation-timeline: scroll(nearest inline);
}
@keyframes tilt {
0% { transform: rotate(-3deg); }
100% { transform: rotate(3deg); }
}
scroll-snap との相性
scroll-snap で要素単位にスナップさせると、視覚的に切れ目がはっきりする。
animation-timeline と組合せて1 ページずつ進む演出が作れる。
失敗パターン
| 症状 | 対処 |
|---|---|
| 動かない | scroll() 対象が「実際にスクロール可能」か |
| scroll(root) なのに動かない | html / body に overflow:hidden が無いか |
| 横スクロール認識しない | scroll(nearest inline) を明示 |
| カクつく | transform / opacity 以外をアニメしている |
| iOS で挙動が変 | Safari の対応状況確認 / polyfill |
パフォーマンス
- コンポジタスレッドで動く(メインスレッドが詰まっても進む)
- scroll イベントを1 度も使わない
- 大量要素にかけても問題ない(要素数より変更プロパティの種類が支配的)
- transform / opacity / filter が GPU 加速
最初に触るレシピ
「scroll(root) で進捗バー」が一番感動しやすい。10 行で書けて、JS のスクロールリスナを完全に置き換えられる。