こんにちは、「ふ」です。
webデザインのサンプル、今回もリクエストいただいた内容を紹介します。
作成していくのはこちら⬆︎。
自動スクロールするスライドショーにて、中央に入ってきた画像を少し拡大させて表示する、というものを実装していきます。
要素の状態変化としては今回は「拡大縮小」を施しますが、他のアニメーションを取り入れることもできます。応用して楽しいスライドショー作りに役立ててください。
スライドショーを作っていくにあたり、画像が切り替わる「一回あたりの変化」を整理しておきましょう。⬆︎の4つの画像を使って考えます。
はじめの状態です。
4つの画像を横並びにし、可視範囲を限定しています。
真ん中の画像はすこし大きく表示し、左右の画像は可視範囲に対してはみ出した状態です。
可視範囲のoverflow制限を解除したのがこちら。
2番目の画像要素が拡大表示され、そして画像エリア全体は画像1/2個ぶん、左にずれています。
そして、最初の状態から画像切り替えを1回行うと⬆︎のようになります。
切り替え前と後に必要な動きは、
・画像エリア全体が画像1つぶん横移動
・3つ目の要素が真ん中にくるため、拡大
・2つ目の要素が真ん中ではなくなるため、元のサイズに戻す。
の3つですね。
これら3つのアニメーションを同時に行います。スライドショーなので、通常は静止した状態。そこからsetIntervalで一定時間ごとにアニメーションを発火させ、画像を切り替えます。
3つのアニメーションが必要なことがわかりました。この3つの動きを、画像要素1つ1つに細かいキーフレームを仕込んでいけば、確かに思ったものは実装できます。
ただそれはかなり面倒な作業であり、もし画像要素を追加した場合にはその都度キーフレームを加工し直す作業が発生します。つまり、非効率。
in other words, 非効率。
横移動の動きに関しては画像をグループ化し、ひとまとめにアニメーションさせることにしましょう。
そして拡大/縮小の動きですが、1回目の切り替えでは2番目と3番目の要素、2回目の切り替えでは3番目と4番目の要素....というように、常にアニメーションさせる対象が入れ替わってしまいます。
そのつど指定先を変更させるのも、とても面倒。
それを解消するのが、「画像要素のキャタピラ」です。
1回のアニメーションが終了したタイミング⬆︎で、
先頭の要素を最後尾に並び変えてしまいます。
可視範囲の外側で行っているため、ユーザからは見えません。
毎回のアニメーション完了後にこの「キャタピラ処理」を行うことで、
・縮小は常に2番目の要素
・拡大は常に3番目の要素
と、アニメーションの対象を固定することができます。また静止時にも、
・CSSで2番目の要素を拡大表示
と設定しておけばOK。
さらにさらに。キャタピラ処理をおこなうことで常に先頭要素が背後に回り込むことになり、その結果、スライドショーをエンドレスに稼働させることができます。
いいことづくめですね ♪
作戦が決まったところで、要素を配置していきます。
コードを書き始める前に、画像要素と余白のサイズを考えておきましょう。
可視範囲に対して、
「真ん中の画像」
「左右の画像の見え隠れ」
「余白」
の合計がきっちり100%となるように設計しておきます。ここでは画像のwidthが30%、画像1つあたりのpaddingを10%としました。
なお真ん中画像は拡大表示させるのですが、その際可視範囲からはみ出ないように上下のpaddingも十分に取っておくようにしてください。
「「3<!--可視範囲-->」」 <div id = "area"> 「「3<!--画像全体を横移動するためのグループ-->」」 <div id = "images"> <img src = "a.svg"> <img src = "b.svg"> <img src = "c.svg"> <img src = "d.svg"> </div> </div>
HTMLは2段階の入れ子構造にします。
・1番外側が、可視範囲の「#area」。
・その内側に画像を収納する「#images」、これは画像群をまとめて横移動させるためのグループ化処理です。
・さらに孫要素として、実際の画像を入れておきます。
#area { width:40%; margin:0 auto; overflow:hidden; } #images { display:flex; } #images img { width:30%; padding:10%; }
可視範囲「#area」のoverflowを「hidden」に指定ましょう。枠線はお好みで。
また画像は設計どおりのサイズと余白に指定します。
この時点で、画像はまだフツーに左端から並べられている状態です。
画像を収納している「#images」を少し左にずらして、Bの画像を中央に持っていきましょう。
横移動は、transform:translateXに相対値「%」で指定することにしました。
その場合、規定値のwidthを基準(100%)として移動距離が算出されます。画像要素をまとめた「#images」のwidthは特に指定を行っていないので、規定のwidthは親である「#area」と同じ大きさです(⬆︎のグレーの部分)。
こうしておけば、もし後から画像の数を増やしたとしても「移動距離の基準」は変わらないため、コードを修正の必要がありません。
#images { display:flex; 「「1transform:translateX(-25%);」」 }
現在は余白を含めた画像が2枚分すっぽりおさまっているので、マイナス方向に1/4動かせばOKですね。
Bの画像が中央にきました。
あとは真ん中画像なので、拡大しておきましょう。
#images img:nth-child(2) { transform:scale(1.3); }
拡大できました。
仕組みのところで述べた「キャタピラ処理」により、スライドショー静止時に中央にある画像は常に拡大表示されることになります。
これで要素の配置は完了です。
画像切り替えのアニメーションを実装していきましょう。必要なアニメーションは、
・画像エリア全体が画像1つぶん横移動
・3つ目の要素が真ん中にくるため、拡大
・2つ目の要素が真ん中ではなくなるため、元のサイズに戻す。
の3つでした。
「3種類のCSSアニメーションを使えばいいか....」と思った矢先、問題が発生します。
アニメーション終了後に例の「キャタピラ処理」をおこなうのですが、CSSアニメーションを3つ同時におこなったとき、ブラウザがanimationendイベントを3回、キャッチしてしまうのです。これではキャタピラも3回発生してしまい、うまくいきません。
〜そこで今回はJavaScriptのWeb Animations APIを使うことにしました。
キーフレームとタイミング関数を記述します。
「「3//タイミング関数(共通)」」「「1 ....@1@」」 const options = { duration:200, iterations:1 }; 「「3//横移動のキーフレーム」」「「1 ....@2@」」 const images_keyframe = [ {transform:"translateX(-25%)"}, {transform:"translateX(-75%)"} ]; 「「3//拡大のキーフレーム」」「「1 ....@3@」」 const expansion_keyframe = [ {transform:"scale(1)"}, {transform:"scale(1.3)"} ]; 「「3//縮小のキーフレーム」」「「1 ....@4@」」 const shrink_keyframe = [ {transform:"scale(1.3)"}, {transform:"scale(1)"} ];
@1@ アニメーションのタイミング関数です。3つのアニメーションは全て同時に始まり同時に終わるので、この関数は共通で利用します。
@2@ 「#images」を横移動させるキーフレーム。もともと画像を中央に寄せるために-25%移動させていたので、25%〜75%の動きとしました。
@3@@4@ 拡大と縮小のキーフレーム。画像が切り替わった際、真ん中にあった画像は等倍にもどり、右から中央に流れてきた画像は拡大されます。
「「3//3つのアニメーションを関数定義の中に」」「「1 ....@5@」」 function slide() { 「「3//横移動」」 let imgs_ani = images.animate(images_timing,images_option); 「「3//拡大」」 let expansion_ani = images.children[2].animate(expansion_timing,images_option); 「「3//縮小」」 let shrink_ani = images.children[1].animate(shrink_timing,images_option); 「「3//横移動終了後にキャタピラ処理を呼び出す」」「「1 ....@6@」」 imgs_ani.onfinish = caterpillar; } 「「3//画像の並び順をキャタピラ」」「「1 ....@7@」」 function caterpillar() { images.appendChild(images.children[0]); } 「「3//一定時間おきにアニメーションを実行」」「「1 ....@8@」」 setInterval(slide,2000);
@5@ 3つのアニメーションをまとめて1つの関数にしています。
Web Animations APIの終了イベントをキャッチするには、animate()メソッドを変数/定数に格納する必要があるのですが、格納した時点でアニメーションが発火してしまうのです。そのため、「関数定義」の中に記述するようにしました。こうしておけば、関数自体が呼び出されるまでアニメーションが実行されることはありません。
@6@ 横移動アニメーション終了イベントをキャッチして、このあと記述するキャタピラ関数を呼び出します。
@7@ ここが今回のキモとなるキャタピラ処理です。
appendChild()は、指定した要素を自身の子要素の末尾に追加します。自身の先頭要素をappendChildした場合、その要素が末尾に回り込みます。
@8@ 一定時間おきにアニメーションおよびキャタピラ処理を発火。今回は2000msごととしました。
これで「拡大スライドショー」が完成です。
お疲れ様でした!
最後までお読みくださり、ありがとうございました。
今回作成したスライドショーは拡大/縮小の変形でしたが、キーフレームやCSSの規定値を書き換えることで、いろんなアニメーションを演出することができます。
const expansion_timing = [ {transform:"scale(1) rotate(0deg)"}, {transform:"scale(1.3) rotate(1turn)"} ];
⬆︎は拡大アニメーションのキーフレームを少し書き換えたもの。
拡大に加えて、回転もさせてみました。
みなさんもいろんな演出のスライドショーを作ってみてくださいね。
ではまた〜 ♬
ベクターグラフィック、web、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。