CSS3アニメーションでつくるインターフェイス表現

第10回画像を3次元で水平に回すアニメーション

今回のお題は、5つの画像で五角柱を組み立てて、水平に回してみようサンプル1⁠。五角柱にマウスポインタを重ねれば、回転は止まる。ずっと回し続けるために、アニメーションにはanimationプロパティを使った。といっても、設定そのものはさほどむずかしくない。考え方さえわかれば、3次元のアニメーションは「こけおどし」にはもってこいといえる。

サンプル1 CSS3: Photos on a rotating polygon in 3D space

五角柱をつくる計算

今回の表現は、Photos on a rotating polygonを採り入れた。もちろん、コードはわかりやすいように大幅に書き替えてある。画像を回す前に、まず五角柱を組み立てなければならない。そのために使うのは、座標変換のtransformプロパティだ。関数rotateY()translateZ()で、5つの画像を順に垂直軸で回しては移動する。まずは、わかりやすいように、四角柱で考え方を確かめておこう。

四角柱は、4つの側面がそれぞれ隣の面と直角に接する(上面と底面はなしとする⁠⁠。配置を確かめるためには、真上から見たxz平面で考えるとよい図1⁠。z軸の下方向が、画面に向かって正面だ。ひとつめの面を、まずy軸で90度回すrotateY()関数⁠⁠。すると、面のxz軸も一緒に90度回転する。そこでつぎに、面のz軸方向にその幅の半分動かせばよいtranslateZ()関数⁠⁠。なお、座標変換では、複数の変換を加えた場合、一般にその順序によって結果は変わるので注意してほしい。

図1 面をrotateY()で回してtranslateZ()で動かす
図1 面をrotateY()で回してtranslateZ()で動かす

五角柱もそれぞれの面をy軸で回してrotateY()関数⁠⁠、z軸の向きに位置を変えるtranslateZ()関数)ことは同じだ図2⁠。ただし、回転角は72度(= 360 / 5)ずつになる。このとき、z軸方向にどれだけ動かせばよいか。四角柱のように、面の幅の半分と、たやすくは計算できない。

図2 五角柱も面をrotateY()で回してtranslateZ()で動かすのは同じ
図2 五角柱も面をrotateY()で回してtranslateZ()で動かすのは同じ

この場合、三角関数のtanを使う。tanはつぎのように定められている図3⁠。一般に、直角三角形の直角でない角(θ)と1辺がわかれば、他の辺は三角関数を用いた比例計算で導ける(斜辺が得られたときにはsinまたはcosを使う⁠⁠。

tanθ= 高さ / 底辺
図3 tanは直角三角形の高さ / 底辺
図3 tanは直角三角形の高さ / 底辺

さて、五角柱の場合は、角度θは36度(= 72 / 2)だ。HTMLドキュメントの<body>要素の記述は以下のコード1のとおりで、5つの画像img要素)の幅widthプロパティ)<img>要素に揃えて240ピクセルを与えたので、その半分は120ピクセルとなる。すると、各面をz軸方向に動かす長さtzは、つぎのように求められる。

tanθ = (width / 2) / tz
tz = (width / 2) / tanθ
= 120 / tan(36°)
コード1 5つの面の画像を定めた<body>要素の記述
<div class="container">
    <div class="spinner">
        <img class="face face-1" src="images/pen01.png" width="240" height="160" alt="面1">
        <img class="face face-2" src="images/pen02.png" width="240" height="160" alt="面2">
        <img class="face face-3" src="images/pen03.png" width="240" height="160" alt="面3">
        <img class="face face-4" src="images/pen04.png" width="240" height="160" alt="面4">
        <img class="face face-5" src="images/pen05.png" width="240" height="160" alt="面5">
    </div>
</div>

もっとも、tan(36°)をどうやって計算するか、不安を感じた読者もいるかもしれないが難しくはない。ブラウザのコンソールを使って、JavaScriptで求めればよい(Google Chromeなら[表示]/[開発/管理]/[JavaScriptコンソール]⁠⁠。JavaScriptでtanの値はMath.tan()メソッドにより得られる。ただし、引数に渡す角度は度数でなくラジアンを単位とする。180度がπラジアンなので、36度はπ / 5ラジアンだ。πの値はMath.PI定数で表される。コンソールにつぎのように入力したら、移動する長さは約165ピクセルだとわかった図4⁠。

120 / Math.tan(Math.PI / 5)
図4 コンソールにJavaScriptコードを書いて実行する
図4 コンソールにJavaScriptコードを書いて実行する

五角柱を組み立てる

それでは、5つの画像で五角柱を組み立てよう。前掲コード1に対して、3次元の操作やアニメーションを加える前に、つぎのようなCSSを定めた。

.container {
    margin: 1em auto;
    padding: 1em;

    width: 240px;
    height: 160px;
}

.spinner img {
    position: absolute;
    border: 1px solid #ccc;
    background: rgba(255, 255, 255, 0.8);
    box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2);
}

3次元の表現を加えるには、プロパティtransform-stylepreserve-3dを与え、perspectiveの値が定められていなければならない(第4回の要素にポインタを重ねたら水平軸で回す参照⁠⁠。そのうえで、つぎのように各面のクラス(face-1~face-5)にそれぞれの面の角度と移動の位置決めをした。なお、0度の回転はデフォルトなので省いて構わない。

.container {

    perspective: 1200px;

}
.spinner {
    transform-style: preserve-3d;
}

.face {
    padding: 0px 7px;
}
.face-1 {
    transform: /* rotateY(0deg) */ translateZ(175px);
}
.face-2 {
    transform: rotateY(72deg) translateZ(175px);
}
.face-3 {
    transform: rotateY(144deg) translateZ(175px);
}
.face-4 {
    transform: rotateY(216deg) translateZ(175px);
}
.face-5 {
    transform: rotateY(288deg) translateZ(175px);
}

面のz軸方向の位置は少し遠め(175ピクセル)にした。各面の両端に少し余白をとった方が、背後の面も透けて立体感が増すからだ。そのため、paddingプロパティに左右の余白を定めた[1]⁠。

tanθ = (width / 2) / tz
width = 2tztanθ

これで、5つの画像で五角柱ができ上がる図5⁠。ここまでのCSSの定めをコード2にまとめた。

図5 5つの画像からつくられた五角柱
図5 5つの画像からつくられた五角柱
コード2 5つの面で3次元の五角柱を組み立てる
.container {
    margin: 1em auto;
    padding: 1em;
    perspective: 1200px;
    width: 240px;
    height: 160px;
}
.spinner {
    transform-style: preserve-3d;
}
.spinner img {
    position: absolute;
    border: 1px solid #ccc;
    background: rgba(255, 255, 255, 0.8);
    box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2);
}
.face {
    padding: 0px 7px;
}
.face-1 {
    transform: translateZ(175px);
}
.face-2 {
    transform: rotateY(72deg) translateZ(175px);
}
.face-3 {
    transform: rotateY(144deg) translateZ(175px);
}
.face-4 {
    transform: rotateY(216deg) translateZ(175px);
}
.face-5 {
    transform: rotateY(288deg) translateZ(175px);
}

五角柱を水平に回す

いよいよアニメーションを定める。もっとも、このお題でややこしいのは、前項までに行った面の位置決め計算だ。プロパティanimationの使い方は、基本がtransitionと同じで、それに少し付け加えるだけといえる。つぎのように、時間とタイミング関数はtransitionと変わらない。繰り返し回数が与えられるのは、animationプロパティのよいところだ。繰り返し続けたいときは、値をinfiniteとする。一番の違いは、任意のアニメーション名を定めなければならないことである。

animation: 時間 タイミング関数 繰り返し回数 アニメーション名

今回は、以下のようにanimationプロパティを指定した。タイミング関数linearは、時間に対して値が線形で変わる。

.spinner {
    animation: 6s linear infinite spinner;

}

アニメーション名は@keyframes規則に与える。transitionプロパティは、始めと終わりのプロパティを定めると、その間をアニメーションした。@keyframes規則には、いくつでも経過点が加えられる。この経過点を「キーフレーム」と呼び、始めを0%、終わりを100%とするパーセンテージで与える。始めと終わりは、それぞれfromおよびtoというキーワードを用いてもよい。

@keyframes アニメーション名 {
    キーフレーム {
       /* プロパティの定め */
    }
}

もっとも今回は、決まった速さで回り続けるだけなので、つぎのように始めと終わりだけ定めれば足りる。さらにいえば、始めのプロパティはデフォルトの状態なので省いて構わない。

@keyframes spinner {
    /* from {
        transform: rotateY(0deg);
    } */
    to {
        transform: rotateY(-360deg);
    }
}

アニメーションのために加えたコードがあっけないほど簡単だったので、もうひとつ書き足すことにしよう。五角柱にマウスポインタを重ねたら、回転を止める。そのときに使うのが、animation-play-stateだ。つぎのように、値にpausedを与えるとアニメーションは一時停止する。デフォルト値は、アニメーションを実行するrunningになっている。書き上がったCSSの定めは、以下のコード3にまとめた。

.spinner:hover {
    animation-play-state: paused;
}
コード3 画像の五角柱を水平に回してマウスが重なったら止める
.container {
    margin: 1em auto;
    padding: 1em;
    perspective: 1200px;
    width: 240px;
    height: 160px;
}
.spinner {
    animation: 6s linear infinite spinner;
    transform-style: preserve-3d;
}
@keyframes spinner {
    to {
        transform: rotateY(-360deg);
    }
}
.spinner:hover {
    animation-play-state: paused;
}
.spinner img {
    position: absolute;
    border: 1px solid #ccc;
    background: rgba(255, 255, 255, 0.8);
    box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2);
}
.face {
    padding: 0px 7px;
}
.face-1 {
    transform: translateZ(175px);
}
.face-2 {
    transform: rotateY(72deg) translateZ(175px);
}
.face-3 {
    transform: rotateY(144deg) translateZ(175px);
}
.face-4 {
    transform: rotateY(216deg) translateZ(175px);
}
.face-5 {
    transform: rotateY(288deg) translateZ(175px);
}

おすすめ記事

記事・ニュース一覧