フーノページ



CSSアニメーション

CSS、スクロールに合わせてアニメーション、scroll-driven。










syntax。

#elm {
    animation:「「3キーフレーム」」;
    animation-timeline:scroll()/view();
}

contents。




スクロールに合わせたアニメーション。


こんにちは、「ふ」です。
今回はCSSで新しく追加されたanimation機能、scroll-drivenについて紹介していきます。

scroll-drivenとは?

scroll-drivenとは、この記事のtopにあった、スクロールに反応して要素をアニメーションさせる仕組みです。
こういったユーザのスクロール操作に合わせたアニメーションは、これまではJavascriptなしでは実装できませんでした。しかし今回登場したscroll-drivenを使うことによって、CSSだけで実装が可能になったんです。

対応ブラウザ。

2023.09時点。
scroll-drivenは、最新のChromeとEdgeしか対応できていません。この後のサンプルを観察するには、ver115以降のChrome/Edgeで当記事を開くことをおすすめします。

まだ主要ブラウザの対応が整っていないとはいえ、今のうちに使い方を知っておきましょう。多少の形式変更はあるかもですが、いずれSafariやFirefoxが対応してくる可能性は大です。

ちなみにdrivenは「ドリブン」と読むそうです。「ふ」はずっと心の中で「ドライヴィン」と呼んでいました。

animation-timelineプロパティ。


animation-timeline:scroll()
animation-timeline:view()

scroll-drivenはanimation-timelineプロパティを使って指定します。 その値は「scroll( )」「view( )」という2種類の関数が存在します。

scroll( )。

⬆︎スクロールしてみて下さい。
スクロールの割合に応じて、要素をアニメーションさせることができます。

view( )。

もうひとつはview( )です。
ユーザが画面をスクロールさせていくと、画面外にあった要素が表示され、そのあと画面外に消えていきます。
画面に表示され始めを進捗0%、画面から出ていった時を100%としてアニメーションさせるのが「view」の仕組みです。

⬆︎のコンテナをゆっ…くりとスクロールさせてみてください。
下から要素が入り始めると、アニメーションが開始されます。そして画面から消えるタイミングでアニメーションが完了しています。

timelineとは?

「timeline」という不慣れなワードが出てきました。
webアニメーションにおけるタイムラインとは「アニメーション進捗の基準となるもの」のことです。
通常のCSSアニメーションは、時間の経過とともに進捗していきます。 言い換えると、アニメーション進行の目安(タイムライン)となるのは「時間」ということになります。

そのタイムラインを時間ではなく「スクロール率」や「表示されている状態」に置き換えたものが、scroll-drivenの考え方です。

このあと具体的な指定方法を見ていきましょう。

scroll( )。


それでは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」が気になった方は⬇︎の記事をどうぞ。

CSS、要素の縦横比を固定〜aspect-ratio。

〽️ heightの相対値問題を解消。





scroll( )のパラメータ。


scroll( )の後ろに付いている( )。ここには「scroller」と「axis」という2つのパラメータを指定することができます。
パラメータを利用することで、スクロールアニメーションの細かな設定が可能になります。

scroller。


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°回転を利用します。 これを使って、パラメータ値の性質を観察しましょう。

・nearest(初期値)。

animation-timeline:scroll(nearest);

scrollerの初期値です。nearest(最も近い)、つまり直近のスクロールコンテナに反応してアニメーションします。

#sikaku要素直近のスクロールコンテナは#boxですね。scrollerパラメータを特に指定しない場合はすぐ外側にある#boxのスクロールに対し、アニメーションします。

・root。

animation-timeline:scroll(root);

rootは、documentのルートに対してドリブンします←要するに、<body>のスクロールのことですね。

#box要素ではなく、<body>のスクロールに対してアニメーションするようになります。

・self。

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で絶対配置にしているためです。




axis。


animation-timeline:scroll(block/inline/x/y);

axisパラメータは、アニメーションを「どの方向のスクロールに対応させるか」を指定するものです。

・block(初期値)。

animation-timeline:scroll(block);

axisパラメータの初期値で、ブロック要素の並び方向へのスクロールに対応させます。
通常のwebページであればブロック要素は上から下へと配置されていきます。縦方向のスクロールドリブンを実装する場合には、初期値のままで問題ありません。

・inline。

animation-timeline:scroll(inline);

インライン要素の方向へのスクロールに対応させます。
インライン方向というのは「テキストの向き」と考えて差し支えありません。

言語圏によっては、横書きでも右から左へのテキストであったり、日本語でも縦書きのサイトがあるかもしれません。そのとき、テキスト方向にドリブンさせるにはこのパラメータを指定します。

・y。

animation-timeline:scroll(y);

yは文字どおり、ブロックやインラインの方向に関わらず、垂直方向のスクロールに対してアニメーションさせます。

我々が目にするwebサイト、ほぼほぼ左から右へのテキスト、上から下へのスクロールです。なので、ここまでの3つのパラメータ値については深く考える必要はありません。初期値のblockにしておけば良いだけのことです。

理解しておきたいのは次のxというパラメータ値です。

・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( )。


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( )のパラメータ。


view( )のパラメータは「axis」と「inset」の2つが用意されています。

animation-timeline:view(block/inline/x/y);

このうちaxisについてはscroll( )関数のものと同じです。
もう1つの「inset」がview( )関数を実用的にする、キモとなります。




inset。


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を逆転させてしまいましょう。

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°回転した状態が保たれています。

CSSアニメーション09、animation-fill-mode。

〽️ アニメーション終了時の状態を維持したり。





こちらは前に作った、コンテンツ量不足で要素がスクロールコンテナから完全に出なかったものです。
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、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。