CSS / animation

スクロールをトリガーにしたアニメーションを総当たりしてモノにする

─ Guide to page sections ─

スクロールをトリガーにしたアニメーションは scroll-driven animations が便利だ。ただ、この scroll-driven animations を完全制覇するには、animation-range 周辺をマスターしなければならない。

animation-range をマスター
トリガーにする要素をどれに充て込むかがまず先決である。

First

mp4 と Canvas をレイアウトする

さて、前回ページの「mp4 ファイルをコマ送りする」のレイアウトが消化しきれていないので、ここで吟味してブラッシュアップしてみたいと思う。

ここで取り残した問題は、position: fixed; と canvas を使った mp4 ファイルをどうやってレスポンシブに配置するか、である。

下図は PC(landscape) と モバイル(portrait) のスクリーンショットであるが、こんな具合のレイアウトを行いたいと考える。

PC(landscape) と モバイル(portrait) のスクリーンショット
PC(landscape) と モバイル(portrait) のスクリーンショット

video の動画を canvas を使って表示する

html の構文は以下のようになる。

<div class="canvas-wrapper">
  <video muted playsinline preload="preload" autobuffer="autobuffer">
    <source type="video/mp4" src="images/full-2.mp4" />
  </video>
  <canvas id="canvas"></canvas>
</div>

さて、レスポンシブ対応のレイアウトを組む準備として、以下(図)を解決しなければならない。ふたつの要素(画像とテキスト)を個別にレイヤーを組むように交差させてレイアウトを完成させる。通常のレイアウトとは異なるものだ。

position: fixed;

②の画像が納まる要素には、position: fixed; を定義し、top、bottom、left、right というプロパティとセットで使う。他の要素から干渉を受けずに、ぽっかりと浮かぶレイアウトを配置するので、スクロールしても所定の位置に固定される。②の左側に余白を設けたのが①である。

margin-left: auto;

テキストが納まる③の要素は、margin-left: auto; を設定して右に配置する。②へ寄せる配置を執るために、③の要素に①と同じ値の余白を設定したのが④である。

②の画像とテキストが納まる③との間に存在する余白の M は「100vw-(1+2)-(3+4)」の計算式で求める。実際には、「M= 100vw-(12.5vw + 20vw)-(50vw + 12.5vw)」として M を 5vw とした。

position: fixed; と margin-right: auto; を使ったレイアウト
position: fixed; と margin-left: auto; を使ったレイアウト

さらに、モバイルのレイアウトをブラッシュアップさせる

ここまで解決すると、モバイルの Portrait では画像が小さ過ぎると感じる。これを解消するために、画像を大きめに配置する Sticky Header 風のレイアウトへ変更してみよう。画像とテキストが重なるので、画像の方を「z-index:」で上方の順番に指定する。これで完成だ。下図をタップして完成ページをご覧いただきたい。

position: fixed; と margin-right: auto; を使ったレイアウト
モバイルにおいて、sticky 風にレイアウトした例(右の図)

Second

video タグについて

MP4 は、動画のラッパーとして機能するデジタルコンテナファイルを指す。 MPEG-4 規格の一部として策定された動画や音声などを記録するためのデジタルコンテナファイルであり、映像や音声の格納によく用いられ、他にも字幕や静止画なども格納できる。

<video> は html の要素で、動画ファイルの再生には video 要素を利用する。閲覧者は HTML5 対応のブラウザで閲覧すれば動画やオーディオを再生できる。

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

<video> タグの基本的な使い方でこのように表示され、その構文は以下になる。

<video autoplay loop muted playsinline style=" max-width: 100%;">
  <source type="video/mp4" src="images/sample.mp4">
</video>

video タグの属性

video ファイルの圧縮、編集

video の圧縮、編集についてのオンラインツールは、ネット上にいくつも存在する。セキュリティソフトの警告が出るものも少なからずあるが、そんな中で今回のオンラインで利用したのは、VEED.IO(編集が簡単)を使って静止画を数枚アップして動画を作成。次にオンラインで利用できるMP4COMPRESSを使ってファイルを圧縮(1.78M → 724KB)した。半分以下に圧縮したにもかかわらず、解像度の変化を感じない。


Third

スクロールすると連動して機能する

ここ暫くスクロールをトリガーにして機能させるものに対して、総当たりしている。その中で大いに興味を引いたのは、(※注釈)にあるような特殊な仕掛けである。その方法論についてはたっぷり時間をかけて吟味しなければならない。

in-view.js

これまでの多くは、スクロールアニメーションを構築する際、JavaScriptの scroll イベントを使用して実装してきた。例えば「in-view.js」といったスクロールして画面内に入ると発火させるプラグインを使ったものだった。

scroll-driven animations

その後、scroll-driven animations が現れた。その新しい APIは、「Scroll-driven Animations」と「animation-timeline」である。手軽に CSS だけを使って簡単にスクロール駆動のアニメーションを構築できる優れものである。同様の機能を求めるならば、間違いなくこれが一番楽である。

例えば、特定した要素がビューポートに達したテキストをハイライト表示するもの

scroll-driven animationsまた、スクロールに併せてフロント画像の円形画像の中身がコマ送りされて画面上部の吸着した細いヘッダーに移動して納まるギミック(※注釈)の備わったページ

Intersection Observer API

そういう経緯の中で、同様なことが行える Intersection Observer API に再注目する必要性を感じた。

Intersection Observer APIIntersection Observer API を利用したスクロールスパイ(ビューポートに要素が表示されている位置に対応するリンクをハイライトして示す機能)は、利用のしがってが良い。

Intersection Observer APIそして、今回はトリガーの位置とアニメーションのタイミングを見てほしいと思って制作したのが、要素の hr を監視して、hr がビューポートに入るとCSS クラスを追加する Intersection Observer(以下はそのJavaScript)仕様のもので更にその本質を知ることになる。

  <script>
"use strict";
const hr = document.querySelector('hr'); 
//要素 hr を取得する
const sticky = document.querySelector('.sticky-header'); 
//class が sticky-header を取得する
const ob = new IntersectionObserver(obCallback);
function obCallback(payload) {
    if (payload[0].boundingClientRect.y < 0) { 
//boundingClientRect.y は、オブジェクトの y 座標を取得する
        sticky.classList.add('visible'); 
//(payload[0].boundingClientRect.y が true の場合、CSS クラスが追加され
    }
    else {
        sticky.classList.remove('visible'); 
//(payload[0].boundingClientRect.y が false の場合、CSS クラス(visible)が削除される
    }
}
ob.observe(hr); //要素 hr の監視を開始する
  </script>

JavaScriptの scroll イベントを使用するもの、scroll-driven animations、Intersection Observer API を利用したもの全てに言えることが、発火のタイミングを如何にコントロールできるかということである。

スクロールしていってある地点に差し掛かるとアニメーションを発火させるポイントは、非表示(アニメーション待機)→表示(アニメーション発火) であり、ページが起動した直後は非表示(アニメーション待機)にしたい、というものであり、ページ起動直後にアニメーションが発火してしまうと台無しである。そういうことを踏まえてある地点を簡単に設定できる、というのが大事である。

ここまで挙げた 3 種類は、スクロールのトリガーの仕掛けこそ違うが、実際には同様のアニメーションを起動することができる。Intersection Observer API の場合は次のセクションでさらに掘り下げて紹介しよう。


Fourth

複数のアニメーションを実行する

Intersection Observer

Multiple
 Animations 

スクロールすると 指定した領域が発火して複数のアニメーションが走る。これを Intersection Observerでやる。下記の参考ページを元にIntersection Observer API を利用したアニメーションのサンプルページを作ってみた。

API(Application Programming Interface)とは、ソフトウェアやプログラム、Webサービスをつなぐインターフェースのことだ。ソフトウェアからOSの機能を利用するための仕様またはインターフェースの総称で、アプリケーションの開発を容易にするためのソフトウェア資源のことを指す。自分で 1 からプログラムを組む必要がなく、ソフトウェア開発の効率化がはかれる。プログラム開発側にとって、開発時間を短縮することができるのは大きなメリットである。

Intersection Observer API は主要なブラウザでサポートされている。インターフェースを繋ぐ手段は JavaScript で記述しなければならない。要素間の交差の有無を判定(ここは Intersection Observer API が担当)し、表示用のセレクタ(例えば" .is-visible")を対象要素に挿入することで画像とテキストをアニメーションで表示させるというものだ。この表示用の部分は「in-view.js」の手法と同様だ。

参考ページ:スクロールすると左右交互にアニメーションで要素がフェードインするページのサンプル

Intersection Observer API には今後を担う簡潔な機能に素晴らしさを感じる。従来のscrollを使った手法と異なり、変動する交差ポイントに自動的に反応する。スクロールに関するイベントではないので、スクロールするたびに呼ばれることがなくパフォーマンス面でも軽いのだ。

View Progress TimelineIntersection Observer API を利用したアニメーション


Fifth

animation-range:

scroll-driven animations以前「inview.js」を動かしたデモページ の中で 「text-stroke を使ったアニメーションがある。このアニメーションを使って Scroll-driven Animations で animation-range の挙動を検証してみよう。

Scroll-driven Animations に於いて、スクロールして特定の地点に差し掛かるとアニメーションが作動する。animation-range は CSS の一括指定プロパティで、タイムラインに沿ったアニメーションの適用範囲の先頭と末尾を設定する。 つまり、タイムラインのどこでアニメーションが始まり、どこで終わるかを設定するために使用する。

以下のように animation の一括指定を宣言した後に animation-range を宣言しないと、その効果を得ることができない。

animation: text-stroke 4s linear 1;
animation-timeline: view();
animation-range-end: cover 80%; 

以下に数値を変更して各々の挙動を確認した。

animation-range: entry 100% cover 60%
animation-range: entry 100% cover 60%

以下のページは、段落のテキストをマーキングでアニメーションした。レスポンシブに於いて、アニメーションが発動する範囲を、animation-range の設定の調整をしたの例である。各々画面の高さが異なるので、例えば高いものは段落に於いて行数が多くテキストが多く詰め込まれる。スクロールポートに既に要素がみっつあるとすると、みっつ全てのアニメーションが既に発動してしまう。作動する前のポジションに隠してあげなければならない。

animation-range: entry 100% cover 60%
animation-range: entry 100% cover 60%

あとがき:アットランダムに animation-range を試していったが、こうだという法則性が見いだせないままでいる。 animation-range は scroll-driven animations をコントロールする上で重要な部分である。もやもやをスッキリさせるべく次回を展開したい。