とほほのスクロールドリブンアニメーション入門

目次

スクロールドリブンアニメーションとは

プロパティ

下記のプロパティが追加されました。

基本的サンプル(animation-timeline)

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

方法は簡単で、通常のアニメーションの時間の代わりに animation-timeline: scroll() を指定するだけです。

CSS
@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: red;
    animation: my-frame ease-in;
    animation-timeline: scroll();
  }
}
CSS
<div class="my-example-scroll">
  <div class="inner-box"></div>
</div>
表示

scroll() には下記の引数を指定することができます。

scroll(<scroller> <axis>)

<scroller> はアニメーションがどの要素のスクロールに追従するかを指定します。

<axis> はアニメーションがどの方向のスクロールに追従するかを指定します。

ビュー進捗タイムライン(view())

scroll() はスクロールバーのスクロール開始を 0%、スクロール終了を 100% としますが、view() を指定すると、アニメーション対象のコンテンツが画面の表示されている部分(スクロールポート)に入ってくるタイミングを 0%、出ていくタイミングを 100% としたアニメーションにすることができます。

CSS
.my-example-view {
  width: 300px;
  height: 200px;
  border: 1px solid #ccc;
  overflow: auto;
  .inner-box {
    height: 10px;
    margin: 300px auto;
    background-color: red;
    animation: my-frame ease-in;
    animation-timeline: view();
  }
}
HTML
<div class="my-example-view">
  <div class="inner-box"></div>
</div>

アニメーション対象コンテンツがスクロールポートに入り始めた時は横幅 100% ですが、中間地点(50%)では 10% の長さになり、出る時に 100% の長さに戻ります。

表示

view() には下記の引数を指定できます。

view(<axis> <view-timeline-inset>)

<axis> はアニメーションがどの方向のスクロールに追従するかを指定します。

<view-timeline-inset> はアニメーションのインセット情報(終了位置と開始位置)を長さまたはパーセントで指定します。view(30% 20%) は入り始めて 20% の箇所でアニメーションが始まり、終わりから 30% の箇所でアニメーションが終わります。終了・開始の順で指定するので注意してください。値をひとつのみ記述した場合は開始・終了同じ値と見なされます。値には負数を指定することもできます。

下記の例では、アニメーション対象要素がスクロールポートに入ってから 20% 経過したところでアニメーションが始まり、出ていく 30% 前のところでアニメーションが終了します。

表示
CSS
animation-timeline: view(30% 20%);
表示

タイムライン名指定

スクロール進捗タイムライン名(scroll-timeline-*)

スクロールバーを持つ要素とアニメーション対象要素の関係は、スクロールバーを持つ要素で scroll-timeline-name, scroll-timeline-axis を用いてタイムライン名とスクロール方向を定義し、アニメーション対象要素から animation-timeline で名前参照することもできます。

ただし名前参照が可能なのは祖先要素のみです。祖先要素以外で参照したい時は後述の timeline-scopt を使用します。

CSS
.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: red;
    animation: my-frame ease-in;
    animation-timeline: --my-scroll-name;   /* タイムライン名を参照 */
  }
}
HTML
<div class="my-example-scroll-name">
  <div class="inner-box"></div>
</div>
表示

scroll-timelinescroll-timeline-namescroll-timeline-axis のショートハンド(一括指定プロパティ)です。

CSS
.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 を指定する要素は、名前を参照する要素の祖先である必要があります。

CSS
.my-example-view-name {
  width: 300px;
  height: 200px;
  border: 1px solid #ccc;
  overflow: auto;
  .inner-box {
    margin: 300px auto;
    padding: 20px;
    background-color: red;
    view-timeline-name: --my-view-name;
    view-timeline-axis: block;
    view-timeline-inset: 30% 20%;
    .animation-box {
      height: 10px;
      background-color: blue;
      animation: my-frame ease-in;
      animation-timeline: --my-view-name;
    }
  }
}
HTML
<div class="my-example-view-name">
  <div class="inner-box"></div>
  <div class="animation-box"></div>
</div>

サンプルでは、赤い領域がスクロールポートに入った時を 0%、出る時を 100% として、青い領域がアニメーションします。

表示

view-timelineview-timeline-nameview-timeline-axisview-timeline-inset のショートハンド(一括指定プロパティ)です。

CSS
.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-nameview-timeline-name を用いて定義したタイムライン名は A の子孫要素からしか参照できませんが、A の祖先要素 X で timeline-scope を宣言するとスコープが拡大され、X の子孫要素すべてがタイムライン名を参照できるようになります。

X {
  timeline-scope: --my-name;         /* タイムライン名スコープを拡大する */
  A {
    scroll-timeline-name: --my-name; /* タイムライン名を定義する */
  }
  B {
    animation-timeline: --my-name;   /* タイムライン名を参照できるようになる */
  }
}
CSS
.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;
      x-background-image: linear-gradient(to bottom, red, blue);
    }
  }
  .my-example-scope-Y {
    .inner-box {
      height: 30px;
      width: 200px;
      background-color: red;
      animation: my-frame ease;
      animation-timeline: --my-scope-name;
    }
  }  
}
HTML
<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;

タイミングには下記のいずれかを指定します。

cover, contain, entry, exit の違いについては下記のデモで確認してください。

cover
contain
entry
exit

entry-crossingentry と、exit-crossingexit と似ていますが、アニメーション対象要素の高さがスクロールよりも高いときの動作が異なります。entry はスクロール対象の上部がスクロールポートの上部に達するとアニメーションが終了するのに対して、entry-crossing はスクロール対象の下部がスクロールポートの下部(終了側エッジ)を横切り終えるまでアニメーションは終了しません。

entry
entry-crossing
exit
exit-crossing

レンジ名を二つ記述することで、開始と終了に別のレンジ名を指定することもできます。

animation-range: contain cover;
contain
cover contain
contain cover
cover

レンジ名の後に長さやパーセンテージを記述することで、開始や終了のタイミングをずらすことができます。covercontain の場合はスクロールポートの下限を 0%、上限を 100% と見なします。entryexit の場合はスクロール要素の高さの上限を 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%出るまで */
cover 0% contain 70%
contain 0% exit 50%
contain 20% contain 80%
entry 70% exit 70%