かつて、鉄道や飛行機の案内板として活躍したパタパタ、あるいはソラリ―こと反転フラップ式案内掲示板をイメージをCSSアニメーションとJSで作ってみた。
なお、私が公開しているお気に入りの場所をマッピングした地図を公開するサービスToboggar でパタパタ型のUIを採用した。
サンプル
Toboggar - 名古屋で食べるべきハンバーガー Top 10
ここで使用したソースを簡素化したものが今回の記事である。
完成形
まずは、出来上がりを Codepen で確認。(画面が表示されていないときは、Result をクリック)
左下の Start ボタンを押すとフラップが回る。
また、Slow ボタンでゆっくり回るので、動きをじっくり確認できる。
See the Pen Split-flap display animation by hiroatsu (@scqspcfg-the-solid) on CodePen.
考え方
仕組みとしては単純で、CSSアニメーションのtransformを使用し、X方向(rotateX)に180度Divを回転させているだけである。
ただし、これだけでは反転フラップにならない。
反転フラップは上限半分に分割されたフラップの半分だけが回転する仕組みとなっているので、その部分で一工夫必要となる。
そこで、回転するdivを隠すための上下それぞれ半分のサイズのフラップを用意し、z-indexでサンドイッチする。重ねる順序は上から、
- 下半分
- 全体
- 上半分
となる。
さらに、上半分は次に表示する内容を表示しておく。
この状態で、まず90度回転すると、下半分は固定されたままフラップが回り、さらに上から次のフラップが顔をだす形になる。
このままさらに90度回転するとdivが裏返るだけなので、90度になった瞬間、次に表示した内容に書き換え、さらにz-indexを入れ替える。重ねる順序は上から、
- 上半分
- 全体
- 下半分
となる。
この状態で90度回転すると、今度は上半分が固定された状態で下半分が回転し、次のフラップに入れ替わっていく。
CSS
rotation
transform: 'rotateX(-90deg)'
回転はCSSのtrasform属性の rotateX関数で行う。roteteには、rotateX、rotateY、rotateZ があるが、rotateXは、水平軸の周りを縦回転する関数となる。
引数は角度だが、正の数は置く方向、負の数は手前方向の回転を表す。
今回は回転はスクリプトで指定するので、CSSファイル上では指定しない。
clip-path
clip-path: polygon(0 0, 100% 0, 100% 50%, 0 50%);
前述の通り回転させるフラップの上半分と下半分のコピーが必要となる。
コピー自体は .innerHTML を書き写せばよいが、"半分"に切り取らなくてはならい。
これは、CSSの clip-path プロパティを利用する。clip-path はDOM要素を任意の形状に切り取るプロパティである。
polygon 関数は形状を決める関数で、任意の数の引数をとる。それぞれの引数は X座標とY座標となる。0 0
は左上、100% 0
が右上、100% 50%
右端の上下真ん中、0 50%
が左端の上下真ん中を表すので、結果として上半分をクリップすることになる。
Javascript
Javascriptはシンプルだ。
turnFlap(nextMsg, duration)
がフラップを回転させる本体となる。
前半90度回転準備
// 全体、上半分、下半分のフラップを取得
const flap = document.querySelector('#flap');
const flapT = document.querySelector('.flap.top');
const flapB = document.querySelector('.flap.bottom');
// 表示するメッセージを上半分に設定
flapT.innerHTML = nextMsg;
// 前半z-indexなど調整
flap.style.zIndex = 11;
flapB.style.zIndex = 12;
flapT.style.zIndex = 10;
flapT.style.display = 'block';
flapB.style.display = 'block';
最初は、DOMを取得する。flap
が全体、flapT
が上半分、flapB
が下半分となる。
後ろに回る上半分は、.innerHTML
プロパティを使用して次の文字に書き換える。
下半分、全体、上半分の順になるようにz-indexを設定する。また、上半分と下半分は初期状態では非表示となっているので、.display
にblockを設定し表示する。
前半90度回転
let animation = flap.animate([
{ transform: 'rotateX(-90deg)' }
],
{
duration: duration
});
await animation.finished;
DOM flap
の animate メソッドにアニメーションの指示を行う。
transform: 'rotateX(-90deg)'
で全体のflapをX方向(縦方向)手前に90度回転する。
duration: duration
は、回転させる時間の指定。引数のduration(ミリ秒)を指定する。
アニメーションは非同期で実行されるので、await animation.finished;
で回転が終わるのを待つ。
後半90度回転準備
// 一旦、フラップを後ろに90度倒す
flap.style.transform = 'rotateX(90deg)';
// フラップの中身を書き換える
flap.innerHTML = nextMsg;
// z-indexを再調整
flap.style.zIndex = 11;
flapB.style.zIndex = 10;
flapT.style.zIndex = 12;
90度回転した状態でフラップは垂直となるので実質的に画面上から消える。
この間に、まずフラップを画面奥側に90度回転させておく。(フラップは180度回転したことになるが画面上垂直であるのは変わらないので、見た目は変わらない)
さらに、フラップの内容を書き換え、zIndexを上半分、全体、下半分の順に再設定する。
後半90度回転
animation = flap.animate([
{ transform: 'rotateX(0deg)' }
],
{
duration: duration,
fill: 'forwards'
});
await animation.finished;
animation.commitStyles();
animation.cancel();
またフラップを90度回転させる。今度は垂直状態(-90度)から手前に倒すようにして水平(0度)へ戻す動きを指定する。
animation.commitStyles();
で変更されたcssスタイルを固定し、animation.cancel()
アニメーションを終了させる。
後処理
// 次の回転のために下半分を書き換えておく
flapB.innerHTML = flap.innerHTML;
// 上半分、下半分は非表示
flapB.style.display = 'none';
flapT.style.display = 'none';
後処理は、次の回転のために下半分を書き換えたうえで、上半部と下半分のDivを非表示としておく。
最終完成版のHTML、CSS、Javascriptは、CODEPENのソースを参照してください。
今回はシンプルな文字の表示だが、フラップはdivなので、その中に画像なり表なりを突っ込めば、何でもパタパタと回転させることができる。