とほほのスクロールドリブンアニメーション入門
スクロールドリブンアニメーションとは
- 通常のアニメーションは経過時間に応じて変化しますが、スクロール量に応じて変化するアニメーションです。
- 2023年7月の Chrome 115 でサポートされました。現時点(2025年3月)では Safari や Firefox は未対応です。
- まだ仕様が揺れ動いている部分もありますので、今後仕様変更される可能性があります。
プロパティ
下記のプロパティが追加されました。
- animation-timeline : タイムラインを指定する
- scroll-timeline : スクロール進捗タイムラインを定義する
- view-timeline : ビュー進捗タイムラインを定義する
- timeline-scope : タイムライン名のスコープを拡大する
- animation-range : アニメーションの開始・終了位置を指定する
基本的サンプル(animation-timeline)
スクロール進捗タイムライン(scroll())
方法は簡単で、通常のアニメーションの時間の代わりに animation-timeline: scroll() を指定するだけです。
@keyframes my-frame {
0% { scale: 100% 100%; }
50% { scale: 10% 100%; }
100% { scale: 100% 100%; }
}
.my-example-scroll {
width: 300px;
height: 200px;
border: 1px solid #ccc;
overflow: auto;
padding: 40px 8px;
.inner-box {
height: 1000px;
background-color: #f88;
animation: my-frame ease-in;
animation-timeline: scroll();
}
}
<div class="my-example-scroll"> <div class="inner-box"></div> </div>
scroll() には下記の引数を指定することができます。
scroll(<scroller> <axis>)
<scroller> はアニメーションがどの要素のスクロールに追従するかを指定します。
- nearest : スクロールバーを持つ最も近い祖先要素。デフォルト値。
- root : ルート要素。
- self : 自分自身の要素。
<axis> はアニメーションがどの方向のスクロールに追従するかを指定します。
- block : ブロック方向。デフォルト値。
- inline : インライン方向。
- x : 水平方向(X軸方向)。
- y : 垂直方向(Y軸方向)。
ビュー進捗タイムライン(view())
scroll() はスクロールバーのスクロール開始を 0%、スクロール終了を 100% としますが、view() を指定すると、アニメーション対象のコンテンツが画面の表示されている部分(スクロールポート)に入ってくるタイミングを 0%、出ていくタイミングを 100% としたアニメーションにすることができます。
.my-example-view {
width: 300px;
height: 200px;
border: 1px solid #ccc;
overflow: auto;
.inner-box {
height: 10px;
margin: 300px auto;
background-color: #f88;
animation: my-frame ease-in;
animation-timeline: view();
}
}
<div class="my-example-view"> <div class="inner-box"></div> </div>
アニメーション対象コンテンツがスクロールポートに入り始めた時は横幅 100% ですが、中間地点(50%)では 10% の長さになり、出る時に 100% の長さに戻ります。
view() には下記の引数を指定できます。
view(<axis> <view-timeline-inset>)
<axis> はアニメーションがどの方向のスクロールに追従するかを指定します。
- block : ブロック方向。デフォルト値。
- inline : インライン方向。
- x : 水平方向(X軸方向)。
- y : 垂直方向(Y軸方向)。
<view-timeline-inset> はアニメーションのインセット情報(終了位置と開始位置)を長さまたはパーセントで指定します。view(30% 20%) は入り始めて 20% の箇所でアニメーションが始まり、終わりから 30% の箇所でアニメーションが終わります。終了・開始の順で指定するので注意してください。値をひとつのみ記述した場合は開始・終了同じ値と見なされます。値には負数を指定することもできます。
下記の例では、アニメーション対象要素がスクロールポートに入ってから 20% 経過したところでアニメーションが始まり、出ていく 30% 前のところでアニメーションが終了します。
animation-timeline: view(30% 20%);
タイムライン名指定
スクロール進捗タイムライン名(scroll-timeline-*)
スクロールバーを持つ要素とアニメーション対象要素の関係は、スクロールバーを持つ要素で scroll-timeline-name, scroll-timeline-axis を用いてタイムライン名とスクロール方向を定義し、アニメーション対象要素から animation-timeline で名前参照することもできます。
ただし名前参照が可能なのは祖先要素のみです。祖先要素以外で参照したい時は後述の timeline-scopt を使用します。
.my-example-scroll-name {
width: 300px;
height: 200px;
border: 1px solid #ccc;
overflow: auto;
padding: 40px 8px;
scroll-timeline-name: --my-scroll-name; /* タイムライン名を定義 */
scroll-timeline-axis: block;
.inner-box {
height: 1000px;
background-color: #f88;
animation: my-frame ease-in;
animation-timeline: --my-scroll-name; /* タイムライン名を参照 */
}
}
<div class="my-example-scroll-name"> <div class="inner-box"></div> </div>
scroll-timeline は scroll-timeline-name と scroll-timeline-axis のショートハンド(一括指定プロパティ)です。
.my-example-scroll-name {
...
/* scroll-timeline-name: --my-scroll-name; */
/* scroll-timeline-axis: block; */
scroll-timeline: --my-scroll-name block;
...
}
ビュー進捗タイムライン名(view-timeline-*)
ビュー進捗タイムラインも同様、スクロールポートの出入りを判断する要素で view-timeline-name, view-timeline-axis, view-timeline-inset を用いてタイムライン名、スクロール方向、インセット情報を定義し、アニメーション対象要素から animation-timeline で名前参照することができます。
こちらも、view-timeline-name を指定する要素は、名前を参照する要素の祖先である必要があります。
.my-example-view-name {
width: 300px;
height: 200px;
border: 1px solid #ccc;
overflow: auto;
.inner-box {
margin: 300px auto;
padding: 20px;
background-color: #f88;
view-timeline-name: --my-view-name;
view-timeline-axis: block;
view-timeline-inset: 30% 20%;
.animation-box {
height: 10px;
background-color: #88f;
animation: my-frame ease-in;
animation-timeline: --my-view-name;
}
}
}
<div class="my-example-view-name"> <div class="inner-box"></div> <div class="animation-box"></div> </div>
サンプルでは、赤い領域がスクロールポートに入った時を 0%、出る時を 100% として、青い領域がアニメーションします。
view-timeline は view-timeline-name と view-timeline-axis と view-timeline-inset のショートハンド(一括指定プロパティ)です。
.my-example-view-name {
.inner-box {
...
/* view-timeline-name: --my-view-name; */
/* view-timeline-axis: block; */
/* view-timeline-inset: 30% 20%; */
view-timeline: --my-view-name block 30% 20%;
}
}
タイムラインスコープ(timeline-scope)
ある要素 A で scroll-timeline-name や view-timeline-name を用いて定義したタイムライン名は A の子孫要素からしか参照できませんが、A の祖先要素 X で timeline-scope を宣言するとスコープが拡大され、X の子孫要素すべてがタイムライン名を参照できるようになります。
X {
timeline-scope: --my-name; /* タイムライン名スコープを拡大する */
A {
scroll-timeline-name: --my-name; /* タイムライン名を定義する */
}
B {
animation-timeline: --my-name; /* タイムライン名を参照できるようになる */
}
}
.my-example-scope-A {
display: flex;
gap: 8px;
timeline-scope: --my-scope-name;
.my-example-scope-X {
height: 200px;
width: 200px;
border: 1px solid #ccc;
overflow: auto;
scroll-timeline-name: --my-scope-name;
.inner-box {
height: 1000px;
}
}
.my-example-scope-Y {
.inner-box {
height: 30px;
width: 200px;
background-color: #f88;
animation: my-frame ease;
animation-timeline: --my-scope-name;
}
}
}
<div class="my-example-scope-A">
<div class="my-example-scope-X">
<div class="inner-box"></div>
</div>
<div class="my-example-scope-Y">
<div class="inner-box"></div>
</div>
</div>
アニメーションレンジ(animation-range-*)
animation-rangeは、アニメーション対象要素に設定するプロパティで、表示領域(スクロールポート)におけるアニメーションの開始・終了タイミングを指定します。
animation: my-frame linear; animation-timeline: view(); animation-range: entry;
タイミングには下記のいずれかを指定します。
- normal : タイムラインの開始から終わりまでと説明されています。時間駆動アニメーションの場合は時間の開始から終わりまで、スクロールドリブンアニメーションの場合は
coverと同じ動作のようです。 - cover : スクロールポートに触れてる時(入り始めてから、出終わるまで)アニメーションします。
- contain : スクロールポートに含まれている間(入り終わってから、出始めるまで)アニメーションします。
- entry : スクロールポートに入る時(入り始めてから、入り終わるまで)アニメーションします。
- exit : スクロールポートから出る時(出始めてから、出終わるまで)アニメーションします。
- entry-crossing : スクロールポートの終了側エッジを横切り始めてから、横切り終わるまでアニメーションします。
- exit-crossing : スクロールポートの開始側エッジを横切り始めてから、横切り終わるまでアニメーションします。
cover, contain, entry, exit の違いについては下記のデモで確認してください。
animation-range: cover; /* 入り始めてから出終わるまで */ animation-range: contain; /* 入り終わってから出始めるまで */ animation-range: entry; /* 入り始めてから入り終わるまで */ animation-range: exit; /* 出始めてから出終わるまで */
entry-crossing は entry と、exit-crossing は exit と似ていますが、アニメーション対象要素の高さがスクロールよりも高いときの動作が異なります。entry はスクロール対象の上部がスクロールポートの上部に達するとアニメーションが終了するのに対して、entry-crossing はスクロール対象の下部がスクロールポートの下部(終了側エッジ)を横切り終えるまでアニメーションは終了しません。
animation-range: entry; /* 入り始めてから出始めるまで */ animation-range: entry-crossing; /* 入り始めてから入り終わるまで */ animation-range: exit; /* 出始めてから入り終わるまで */ animation-range: exit-crossing; /* 出始めてから出終わるまで */
レンジ名を二つ記述することで、開始と終了に別のレンジ名を指定することもできます。
animation-range: contain cover;
レンジ名の後に長さやパーセンテージを記述することで、開始や終了のタイミングをずらすことができます。cover と contain の場合はスクロールポートの下限を 0%、上限を 100% と見なします。entry と exit の場合はスクロール要素の高さの上限を 0%、下限を 100% と見なします。
animation-range: cover 0% contain 70%; /* 入り始めてから70%進むまで */ animation-range: contain 0% exit 50%; /* 入り終わってから50%出るまで */ animation-range: contain 20% contain 80%; /* 入り終わってから20%~80%の間で */ animation-range: entry 70% exit 70%; /* 70%入ってから70%出るまで */
リンク
- https://www.w3.org/TR/scroll-animations-1/
- https://www.w3.org/TR/css-animations-2/
- https://developer.chrome.com/blog/scroll-animation-performance-case-study?hl=ja
- https://developer.chrome.com/docs/css-ui/scroll-driven-animations?hl=ja
- https://scroll-driven-animations.style/