CSS / animation

scroll-driven animations を CSS で対応する 2

─ Guide to page sections ─

スクロールをトリガーにしたアニメーションには一枚のスプライト画像をコマ送りするもの、複数の連続する画像や、mp4 ファイルを使ったものなどがある。

プロンプター完成版
スクロールするとイメージがアニメーションする

First

フルスクリーンサイズの画像を使う

画像のサイズがフルスクリーンサイズとなれば、当然 avif のフォーマットの出番だ。スクロールコンテナ(3 枚の画像)にスナップ点(scroll-snap-type)を設定したページを作ってみよう

「scroll-snap-type: y mandatory;」を設定してある。

スナップを設定したフルスクリーンサイズの画像
スナップを設定したフルスクリーンサイズの画像

<picture>

picture タグは CSS を使わずに HTML だけで画面幅や閲覧する端末に応じた画像を表示させる要素である。

source 要素は、表示候補となるメディアリソース(画像、動画、音楽、など)を定義する要素である。srcset は、HTML5 で追加された属性で、複数のイメージソース(ここでは avif)を指定するために使用する。具体的には以下のように使う。

尚、ブラウザが avif に未対応のときは、 img を表示させる。

<picture>
  <source srcset="images/001.avif" type="image/avif" width="100%" alt="ウーマン 1">
  <img src="images/001.jpg" alt="ウーマン 1">
</picture>

CSS Scroll Snap を使う

閲覧者の操作性を大きく左右するのがスナップである。閲覧者にとって、スクロールやスワイプした動作の中でピタッと止まってくれるこの安心感は好ましい印象を与える。

スクロールスナップ仕様の主要なプロパティは、scroll-snap-type と scroll-snap-align だ。

.container {
  scroll-snap-type: y mandatory;
}
.child {
  scroll-snap-align: start;
}

scroll-snap-type には、mandatory: 強制 と proximity: 近接 がある。親要素の CSS に設定し、x は横方向のスクロール、y は縦方向のスクロールを制御する。

scroll-snap-align: start; は、スクロール コンテナのスナップポート内での、このボックスのスクロールスナップ領域の開始位置が、この軸のスナップ位置である。

基本的な scroll-snap-type と scroll-snap-align を使った例を作ってみる

background-image と image-set()

ここでの画像は background-image で配置、image-set(最も適切な CSS 画像をブラウザーに選択させる方法) を使って avif、webp、jpg の画像を並べて閲覧者のブラウザが対応しているファイル形式に基づいて画像選択を提供した。つまり以下のように、記述した。

.img-bg-01 {
  background-image: url("./images/001.jpg"); /* Fallback */
  background-image: image-set(
    url("./images/001.avif") type("image/avif"),
    url("./images/001.webp") type("image/webp"),
    url("./images/001.jpg") type("image/jpg"));
  background-size: cover;
  background-position: left top;
}

scroll-snap-stop: always;

すべてのポイントにスクロールを可能な限りスナップさせるには、alwaysを定義する。CSS のプロパティである「scroll-snap-stop」に「always」を設定すると、スクロールコンテナーが可能なスナップ位置を「通り過ぎる」ことを許さない状態になり、確実にスナップする。

使い方は、子要素に「scroll-snap-stop: always」を設定する。この記述により、ユーザーがスクロールを続ける前にスクロールコンテナが強制的にその要素で停止する。


Second

スクロールするとテキストがハイライト

scroll-driven animations を実行するとき、実に多くの CSS のプロパティが登場する。効率の良い書き方は、CSS 変数に代表されるが、それに加えて新しいプロパティを随時使っていくことになる。だからソースを見たときは混乱するかもしれないが、骨組みだけは押さえていくようにしよう。

まずは、スクロールするとテキストがハイライトするサンプルを見てみよう

もっと解りやすくするために機能縮小版のサンプルを作ってみた

View Progress Timeline
Scroll Progress Timeline を使ったアニメーション

このセクションで使用するプロパティなどの解説を以下に提示する。

:has() :is() :where()

さて、新しく便利な CSS セレクタが登場した。以下にまとめる。

  • :has()…特定の要素がセレクタ内に存在するかどうかを判別する。以下は、<figure> が <figcaption> を持っていれば、背景、パディング、画像の角丸のスタイルを指定する。
    
    figure:has(figcaption) {
        padding: 1rem;
        background-color: #f7f7f7;
        box-shadow: 0 3px 10px 0 rgba(#000, 0.1);
        border-radius: 3px;
    }
  • :is()…指定した要素に一致する場合にCSSを適用する。
    
    :is(.example1, .example2, .example3) a{
      color: orange;
    }
    /* 同義 */
    .example1 a, .example2 a, .example3 a{
      color: orange;
    }
  • :where()…特定の要素をまとめて選択し、一括でスタイルを指定することができるセレクタである。

Third

スクロールすると画像がコマ送りする

ここでの要点は、スプライト画像(複数の画像を1枚にまとめた画像のこと)だ。画像は連続したフレームを横に陳列させる。フレームの数は 24 フレームにしてみた。

具体的にはこういう画像である。

画像を編集するには、フリー版 Any Video Converter を採用した。 「動画ダウンロード」、「動画カット」、「GIFメーカー」の機能を使って、最終的に Photoshop でスプライト画像を完成させた。

次の画像をタップ(クリック)すると、スクロールすると画像がコマ送りで動いているように見える。

View Progress Timeline
Scroll Progress Timeline を使ったアニメーション

stop-motion animation

CSSのプロパティである animation-timing-function の値にstepsがある。stepsは、コマ撮りのアニメーション(stop-motion animation)のような動きをさせることができる。上で作ったスプライト画像を使うと簡単にアニメーションが出来上がる。

mp4 から画像を切り出す

ここで行ったのは、動画ファイルの mp4 を表示して、静止画をシーン別に約100枚切り出した。この jpg 画像を avif 画像に圧縮して、スクロールするとアニメーションとして動くようにする。

スプライト画像と比べると、修正などで手直しを入れるときは一枚毎に処理を行う方が安易であるようだ。画像数が多いので、Squoosh を使う場合は、エラー表示に気を付けること。

mp4 から静止画を切り出す
mp4 から静止画を切り出す

Fourth

mp4 ファイルをコマ送りする

スクロールすると mp4 ファイルを読み込んでコマ送りするものがある。動画ファイルを差し替えていくつか試したが、上手く動作しない場合も多かった。

フルスクリーンの背景画像をスクロールに連動させる mp4

上手くいった例である。背景にフルスクリーンで mp4 画像を表示して、スクロールする都度画像がコマ送りされるサンプルを見てみよう。

mp4 ファイルをコマ送りする
mp4 ファイルをコマ送りする

モバイルのポートレイト(portrait)ではフルスクリーンにして配置を調整した例である。

mp4 を読み込んで画像をコマ送りできる、ということが解ったので、「position: fixed;」を使って画像の位置を固定して、スクロールすると画像がコマ送りされサイドのテキストがスクロールする演出を作ってみる。

似ているタイプでこういうのもある、画像を切り替えるものだが、作り方はまるで違う

スクロールコンテンツの作り方とその挙動を確認する

scroll driven(スクロール駆動)でアニメーションさせる前提として、スクロールを発生させるエリアやニメーションする要素を、如何なる配置で行うか。

スクロールコンテナの中に納まるアニメーションの配置を各々指定して、挙動の確認をしておきたい。


Fifth

フルスクリーンの画像が縮小して固定のヘッダーになる

scroll-driven animationsBuilt by Bramus による Scroll-driven Animations の DEMO の中で 「Cover Card to Fixed Header(カバーカードを固定ヘッダーに固定)」というのがある。

ここで使われているテクニックは、スクロールするとフルスクリーンを覆う画像が Y 方向へ縮みヘッダーとしてトップへ張り付くというもの(※下図参照のこと)で、 Scroll-driven Animations ならではの簡単な記述で実現する。

ここでのセクションでは、この「Cover Card to Fixed Header」を精査してみよう。

フルスクリーンの画像が縮小して固定のヘッダーになる
Cover Card to Fixed Header

html は <body> 直下に配置し、css は以下のように記述する。

<div id="sticky-parallax-header">Full-Height Cover Card to Fixed Header</div>
<div id="content">
  <h1 style="color: #66669a;">Steely Dan</h1>
  <p>some sentences …</p>
</div>

以下 CSS において、(1)、(2)、(3)、(4) が scroll-driven animations を実装する基本構成である。

* {
  box-sizing: border-box;
}
html, body {
  margin: 0;
  padding: 0;
  background: #fff;
  font-family: 'Noto Sans JP', sans-serif;
  font-weight: 400;
  color: #333;
}
#sticky-parallax-header {
  color: #fff;
  height: 100vh;
  width: 100%;
  background-image: url("images/steely_dan.avif");
  background-size: cover;
  background-position: 50% 50%;
  background-blend-mode: soft-light;
  display: grid;
  place-items: center;
  text-align: center;
  font-size: calc(3vw + 1em);
  font-weight: 100;
}
#content {
  padding-top: 1em;
  margin: 0 auto 0; 
  width: 600px;     /* PC では 600px */
  max-width: 90%;   /* mobile では90% */
}

/* -------- @supports() -------- */
@supports(animation-range: 0vh 90vh) {
  @keyframes sticky-parallax-header-move-and-size {  /* (1) */
    from {
      background-position: 50% 0;
    }
    to {
      background-position: 50% 100%;
      background-color: #0b1584;
      height: 10vh;
      font-size: 2em;
    }
  }
  #sticky-parallax-header {
    position: fixed;
    top: 0;
    animation: sticky-parallax-header-move-and-size linear forwards; /* (2) */
    animation-timeline: scroll(); /* (3) */
    animation-range: 0vh 90vh; /* -- @supports() (4)-- */
  }
  body {
    padding-top: 100vh;
  }
}

(番外) CSSの新しい単位(lvh, svh, dvh)がすべてのブラウザでサポート済み

iOS Safariではスクロールを始めるとURLバーが小さくなり画面のサイズが変わるという問題が存在したが、「dvh」が解決する。古いブラウザへは以下のようにフォールバックとセットで使用する解決策がある。

<div style="height: 100vh; height: 100dvh"></div>

(番外) 右寄せ

margin-left: auto; 
margin-right: 0px;
アナザー・レスポンシブの方法

メディアクエリ(Media Queries)は使わない。width プロパティには、width、max-width、min-width と 3 つの種類がある。max-width は width を上書きするが、 min-width は max-width を上書きする。width: と max-width: を使ってレスポンシブを実装する。

width プロパティのレスポンシブ
width プロパティのレスポンシブ
p {
  width: 500px;   /* pc では 500px */
  max-width: 90%; /* mobile では 90% */
}