#elm { animation:「「3キーフレーム」」; animation-timeline:scroll()/view(); }
こんにちは、「ふ」です。
今回はCSSで新しく追加されたanimation機能、scroll-drivenについて紹介していきます。
scroll-drivenとは、この記事のtopにあった、スクロールに反応して要素をアニメーションさせる仕組みです。
こういったユーザのスクロール操作に合わせたアニメーションは、これまではJavascriptなしでは実装できませんでした。しかし今回登場したscroll-drivenを使うことによって、CSSだけで実装が可能になったんです。
2023.09時点。
scroll-drivenは、最新のChromeとEdgeしか対応できていません。この後のサンプルを観察するには、ver115以降のChrome/Edgeで当記事を開くことをおすすめします。
まだ主要ブラウザの対応が整っていないとはいえ、今のうちに使い方を知っておきましょう。多少の形式変更はあるかもですが、いずれSafariやFirefoxが対応してくる可能性は大です。
ちなみにdrivenは「ドリブン」と読むそうです。「ふ」はずっと心の中で「ドライヴィン」と呼んでいました。
animation-timeline:scroll() animation-timeline:view()
scroll-drivenはanimation-timelineプロパティを使って指定します。 その値は「scroll( )」「view( )」という2種類の関数が存在します。
⬆︎スクロールしてみて下さい。
スクロールの割合に応じて、要素をアニメーションさせることができます。
もうひとつはview( )です。
ユーザが画面をスクロールさせていくと、画面外にあった要素が表示され、そのあと画面外に消えていきます。
画面に表示され始めを進捗0%、画面から出ていった時を100%としてアニメーションさせるのが「view」の仕組みです。
⬆︎のコンテナをゆっ…くりとスクロールさせてみてください。
下から要素が入り始めると、アニメーションが開始されます。そして画面から消えるタイミングでアニメーションが完了しています。
「timeline」という不慣れなワードが出てきました。
webアニメーションにおけるタイムラインとは「アニメーション進捗の基準となるもの」のことです。
通常のCSSアニメーションは、時間の経過とともに進捗していきます。
言い換えると、アニメーション進行の目安(タイムライン)となるのは「時間」ということになります。
そのタイムラインを時間ではなく「スクロール率」や「表示されている状態」に置き換えたものが、scroll-drivenの考え方です。
このあと具体的な指定方法を見ていきましょう。
それではscroll( )から実装してみましょう。
準備するものは要素とアニメーション、そしてスクロールコンテナです。
スクロールコンテナは文字通り「スクロールさせることのできる領域」のことです。
viewport(ブラウザの表示領域)がそうですよね。webページの多くは<body>がviewportからはみ出しており、ユーザはスクロールさせながら閲覧していく形をとっています。
htmlの内部にもスクロールコンテナを作ることができます。親要素に対して子要素(の合計)をオーバーフローさせ、CSSでoverflow:scrollとすればスクロールコンテナが成立します。
先づはviewportをつかって、scroll( )を実装してみます。
<body style = "height:300vh;"></body>
<body>の高さをviewportの3倍としました。これでスクロールコンテナになっています。 ここに要素を配置します。
<body style = "height:300vh;"> <div id = "sikaku"></div> </body>
#sikaku { width:40%; aspect-ratio:1/1; background-color:forestgreen; border-bottom:solid 5px crimson; position:fixed; top:20%; left:30%; }
要素を配置しました。スクロールしても要素の動きが観察できるよう、positionをfixedで固定しています。
アニメーションを準備しましょう。
@keyframes sikaku { to {transform:rotate(0.5turn);} }
キーフレームは、要素がその場で180°回転するものとしました。
#sikaku { 「「3/* 〜略〜 */」」 animation:sikaku; 「「1animation-timeline:scroll();」」 }
要素に対してのCSS。animationプロパティでキーフレームと紐づけます。
ここで通常のCSSアニメーションであれば、duration(所要時間)の指定は必須です。が、今回はタイムライン(進捗の指標)は時系列ではなく「スクロール」です。そのためdurationは指定しません。
そして次の行。animation-timelineプロパティで、タイムラインをscroll( )と指定します。( )の中には後に紹介しますが、パラメータを指定することができます。
スクロールに応じて、要素がアニメーションするようになりました。
逆方向にスクロールさせると、アニメーションも逆再生されます。
「aspect-ratio」が気になった方は⬇︎の記事をどうぞ。
〽️ heightの相対値問題を解消。
scroll( )の後ろに付いている( )。ここには「scroller」と「axis」という2つのパラメータを指定することができます。
パラメータを利用することで、スクロールアニメーションの細かな設定が可能になります。
animation-timeline:scroll(nearest/root/self)
scrollerは「どのスクロールコンテナに反応させて、アニメーションさせるか」を指定します。
スクロールコンテナは、2重以上の入れ子になっていることも多々あります。
↑の例で、正方形要素を取り巻いているスクロールコンテナを考えてみましょう。先づ<div>要素というコンテナがあり、さらにその外側にviewportというコンテナがあります。
正方形要素は、2重のスクロールコンテナに囲まれていることになります。
その際に「どのコンテナのスクロールに対してドリブンさせるか」を決めるのがscrollerパラメータです。
<div id = "box"> <div id = "nakami"> <div id = "sikaku"></div> </div> </div>
body { height:300vh; } #box { width:60%; aspect-ratio:1/1; position:sticky; top:10%; left:20%; border:solid 1px dodgerblue; overflow:scroll; } #nakami { height:300%; } #sikaku { width:60%; aspect-ratio:1/1; position:sticky; top:20%; left:20%; background-color:forestgreen; border-bottom:solid 5px crimson; }
⬆︎のような状況を用意しました。
<body>の中にスクロールコンテナ#boxがひとつあり、さらにその中に要素#sikakuがある状態です。#nakamiは#boxをオーバーフローさせるための要素です。
アニメーションは前のセクションの180°回転を利用します。
これを使って、パラメータ値の性質を観察しましょう。
animation-timeline:scroll(nearest);
scrollerの初期値です。nearest(最も近い)、つまり直近のスクロールコンテナに反応してアニメーションします。
#sikaku要素直近のスクロールコンテナは#boxですね。scrollerパラメータを特に指定しない場合はすぐ外側にある#boxのスクロールに対し、アニメーションします。
animation-timeline:scroll(root);
rootは、documentのルートに対してドリブンします←要するに、<body>のスクロールのことですね。
#box要素ではなく、<body>のスクロールに対してアニメーションするようになります。
animation-timeline:scroll(self);
selfは「スクロールコンテナ自身をアニメーションさせる」ときに指定します。
#boxに対してアニメーションを仕込み、試してみましょう。
@keyframes box { to { border-width:20px; border-radius:20%; } } #box { 「「3/* 〜略〜 */」」 animation:box; animation-timeline:scroll(self); }
キーフレームは、borderの幅とradiusを変化させるものとしました。
#box自体にアニメーションとscroll(self)を指定します。
#box部分をスクロールさせると、自身のプロパティがアニメーション変化します。
右下方向に広がるのは、position:stickyで絶対配置にしているためです。
animation-timeline:scroll(block/inline/x/y);
axisパラメータは、アニメーションを「どの方向のスクロールに対応させるか」を指定するものです。
animation-timeline:scroll(block);
axisパラメータの初期値で、ブロック要素の並び方向へのスクロールに対応させます。
通常のwebページであればブロック要素は上から下へと配置されていきます。縦方向のスクロールドリブンを実装する場合には、初期値のままで問題ありません。
animation-timeline:scroll(inline);
インライン要素の方向へのスクロールに対応させます。
インライン方向というのは「テキストの向き」と考えて差し支えありません。
言語圏によっては、横書きでも右から左へのテキストであったり、日本語でも縦書きのサイトがあるかもしれません。そのとき、テキスト方向にドリブンさせるにはこのパラメータを指定します。
animation-timeline:scroll(y);
yは文字どおり、ブロックやインラインの方向に関わらず、垂直方向のスクロールに対してアニメーションさせます。
我々が目にするwebサイト、ほぼほぼ左から右へのテキスト、上から下へのスクロールです。なので、ここまでの3つのパラメータ値については深く考える必要はありません。初期値のblockにしておけば良いだけのことです。
理解しておきたいのは次のxというパラメータ値です。
animation-timeline:scroll(x);
xは、アニメーションを水平方向のスクロールに対応させます。
x方向のスクロールドリブン、実装する場合にはその部分だけスクロールコンテナを構築することをおすすめします。
viewportを水平方向のスクロールコンテナとして使用するとしたら、<body>直下の要素を水平方向にoverflowさせる必要があります。
<body>直下の要素がオーバーフローするということは、<body>全体がオーバーフローすることを意味します。すると、スクロールドリブン以外のフツーのコンテンツ部分(スクロールさせたくない)においても、水平スクロールが可能になってしまいます。
これはユーザにとってあまりよろしくない。フツー部分においては横スクロールできるものの、ただの余白が出現するだけになります。
scrollerパラメータでつくったサンプルのように、内部に独立したスクロールコンテナを作成するようにしましょう。すると<body>自体の横幅を無意味にオーバーフローさせてしまうこともありません。
<div id = "box"> <div id = "nakami"> <div id = "sikaku"></div> </div> </div>
#box { width:60%; aspect-ratio:1/1; position:absolute; top:10%; left:20%; border:solid 1px dodgerblue; overflow:scroll; } #nakami { width:300%; height:100%; } #sikaku { height:60%; aspect-ratio:1/1; position:sticky; top:20%; left:20%; background-color:forestgreen; border-bottom:solid 5px crimson; animation:sikaku; 「「1animation-timeline:scroll(x);」」 } @keyframes sikaku { to {transform:rotate(0.5turn);} }
先ほどのサンプルを少し変更しました。
#boxの内側要素である#nakamiを水平方向にオーバーフローさせ、#sikakuのscrollパラメータを「x」としています。
内部のスクロールコンテナの横スクロールに対して、#sikakuがアニメーションするようになりました。
scrollerとaxis、2つのパラメータは同時にに指定することができます。
animation-timeline:scroll(nearest x);
半角スペースで区切って記述しましょう。2つのパラメータの記述順序は逆になっても問題ありません。
view( )関数は、画面に表示され始めを進捗0%、画面から完全に出たときを進捗100%としたアニメーションを発生させます。
おなじみ正方形が半回転するアニメーションでのイメージです。スクロールコンテナに要素が入った瞬間にアニメーションがスタートし、コンテナから完全に出たところでアニメーションを完了します。
要素の移動はスクロールによるものですが、view( )関数はあくまで「表示領域に入っているかどうか」という表示判定によってアニメーションの進捗を決定します。
そのため要素自体の大きさによっても、アニメーション開始〜完了までのスクロール量が変わってきます。
viewportのスクロールコンテナを使い、実装してみましょう。
<body style = "height:300vh;"> <div id = "sikaku"></div> </body>
#sikaku { width:40%; aspect-ratio:1/1; background-color:forestgreen; border-bottom:solid 5px crimson; position:absolute; top:130%; left:30%; }
htmlやcss、基本的にはscroll( )のときと同じものです。
こんどは#sikakuを画面から入ったり出たりさせる必要があるので、positionをfixedではなく、absoluteとしています。
@keyframes sikaku { to {transform:rotate(0.5turn);} } #sikaku { 「「3/* 〜略〜 */」」 animation:sikaku 「「1linear」」; animation-timeline:「「1view()」」; }
keyframeを紐付け、animation-timelineをview( )としました。
また今回はanimation-timing-functionを「linear」としています。要素が画面を通過する間、平均的な変化が欲しかったためです。
スクロールして、要素が画面に表示され始めるとアニメーションが開始。画面から完全に出たときに進捗100%となり、完了します。
view( )の実装には成功しましたが、このパラメータなしの「基本セット」だけでは、不満が出てくる部分もあります。
要素がスクロールコンテナにわずかでも表示されたら、すでにアニメーションは開始されている状態です。そして画面から完全に出たときにはじめて、アニメーション進捗が100%となります。
ユーザは、アニメーションの完全な開始前/完了時の状態を見ることができないということになります。
⬆︎はテキストが横から流れてくるアニメーションのイメージです。
先頭の文字はスクロールコンテナ内にほとんど表示されないため、ユーザはテキスト内容を満足に認識することができません。
これは<body>内部にスクロールコンテナを作った場合に起こりがちです。
コンテナ内部の高さ(幅)が不十分で、いっぱいにスクロールさせても、アニメーション要素がコンテナから完全には出て行かないとき。
下までスクロールし切っても、アニメーションが完了状態になりません。
view( )関数のタイムライン(進行具合の目安)はスクロール率ではなく「表示されているかどうかの判定」なので、コンテナから完全に出ないと、アニメーションは進捗100%にはなりません。
view( )関数を実用的なものにするには、次に紹介していくパラメータの利用が必須です。
view( )のパラメータは「axis」と「inset」の2つが用意されています。
animation-timeline:view(block/inline/x/y);
このうちaxisについてはscroll( )関数のものと同じです。
もう1つの「inset」がview( )関数を実用的にする、キモとなります。
animation-timeline:view(end start/end&start)
view( )関数では、要素がスクロールコンテナ内に表示され始めたらアニメーションを開始し、コンテナから完全に出たとき、アニメーションを完了させるものでした。
縦スクロールで考えたとき、スクロールコンテナの上端から下端の範囲が「表示判定エリア」である、と言えます。この表示判定エリアのことを「inset」と呼びます。
view( )関数の基本状態では、insetの終点と始点がスクロールコンテナの上端/下端と一致したものでした。それが原因で、先のセクションにあった「基本セット問題」を招いていたのです。
Insetパラメータは、スクロールコンテナからのendとstartの位置を指定することで、insetの範囲をコントロールすることが可能です。
animaton-timeline:view(20% 40%);
としたとき、
1つ目の値はendの位置です。「20%」とあるのでスクロールコンテナの上端から20%の逃げを取ります。
2つ目は、startの位置。スクロールコンテナ下端から40%の逃げを取ります。
animation-timeline:view(20%);
値が1つだけだと、start/end共通の逃げ値として扱われます。
またここでは相対値である%を使っていますが、pxなどの絶対値指定も可能です。
前のセクションで作った基本セット⬆︎に対して、insetパラメータを使ってみます。
#sikaku { 「「3/* 〜略〜 */」」 animation:sikaku linear; animation-timeline:view(20%); }
スクロールコンテナに対し、inset幅が小さくなりました。
viewの動きに反映されます。要素がstartに入りはじめたらアニメーション開始、endラインから完全に出たところでアニメーション完了となります。
とりあへず、アニメーション開始前/完了後の状態が見えるようになりました。でも開始/終了の瞬間、要素がスクロールコンテナからはみ出してしまっています。
〜いっそのこと、startとendを逆転させてしまいましょう。
animation-timeline:view(60% 60%);
startとend、逃げ値の合計がスクロールコンテナの大きさを超えると、コンテナ内でinsetとそれ以外の領域が逆転します。
逆転insetを試してみます。
#sikaku { 「「3/* 〜略〜 */」」 animation:sikaku linear; animation-timeline:view(60% 60%); }
逆転していても、startとendの働きは変わりません。アニメーションが始まるのはstartラインから、完了するのはendラインを越した瞬間です。
逆転させたおかげでアニメーション開始/完了時の状態がはっきり確認できるようになりました。
〜が、どこか違和感。
アニメーション要素のborder-bottomの紅色に注目しながら、スクロールさせてみてください。
キーフレームは、要素が180°回転する、というものでした。endラインのところでアニメーションが完了したとき、角度0°に戻ってしまっています。
完了時の状態を維持するために、animation-fill-modeを使いましょう。
#sikaku { 「「3/* 〜略〜 */」」 animation:sikaku linear 「「1forwards」」; animation-timeline:view(60% 60%); }
animation-fill-modeを「forwards(完了時の状態を保つ)」に指定しました。
endラインを越えても、終了時の180°回転した状態が保たれています。
〽️ アニメーション終了時の状態を維持したり。
こちらは前に作った、コンテンツ量不足で要素がスクロールコンテナから完全に出なかったものです。
insetを調整しましょう。
#sikaku_b { 「「3/* 〜略〜 */」」 animation:sikaku linear forwards; animation-timeline:view(「「180% 10%」」); }
insetを「end: 90% start: 10%」としたところ、コンテナ内でちゃんとアニメーションを完了させることができました。
view( )関数を使う際は、insetプロパティを使って見た目の良いアニメーションに仕上げていくようにしましょう。
長〜い記事となりました。最後まで読んでくださって、ありがとうございます。
今回紹介したscroll( )/view( )関数、そしてパラメータを活用することで、自在なスクロールアニメーションを実装することができます。CSSだけで。
Chrome・Edge以外のブラウザが対応してくれる日が待ち遠しいですね。
いやでもYoutubeで、海外のscroll-drivenに関する動画を観てたら「ドライヴィン」と発音している。やっぱり「ドライヴィン」でいいのでは?
ではまたー🎵
ベクターグラフィック、web、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。