Panda Noir

JavaScript の限界を究めるブログでした。最近は幅広めに書いてます。

もう分からないとは言わせない! 体系的に学ぶCSS transform 3D入門

(この記事はQiitaで僕が書いたものを移行した記事です。記事中のコメントはQiitaの該当記事を参照ください)

まえがき: 記事中の画像について

記事中の画像は、青が変形前、赤、緑が青を変形した後の要素となっています。参考にしてください。

基本編

transform関数とは?

transformプロパティの値として使われる関数です。rotate系、translate系、skew系、scale系、matrix、matrix3d、perspectiveがあります。

「系」と記したものには「rotateX」、「translateY」のように軸ごとに関数があります。面倒なのでまとめて「系」と記しました。記事中で出てくるrotateなどは、ほぼrotate系を指しています。

座標軸の注意点

座標軸について数学と異なる点がいくつかあるのでまとめました。

座標軸の正の方向

ここでいう座標軸とは、translate・rotateで変形する際に使われる軸のことです。どちらも同じ軸を使って変形するので、ここでまとめて説明しておきます。

軸にはx軸・y軸・z軸の3種類あります。

x軸、y軸はそれぞれ スクリーン横方向、縦方向 です。数学の座標そのままですね。z軸は スクリーンを貫く方向 …といえば伝わるでしょうか?

正の方向は、x軸は右方向、y軸は下方向、z軸はスクリーンから自分に向かってくる向きです。

display.png

(座標軸と正の方向の図)

座標軸の回転方向

回転方向は「正の方向から負の方向へ見た時に時計回り」です。分かりづらいと思うので下の画像をご覧ください。

axis.png

(軸の回転方向の図)

3軸は通常、要素の中央で交わります。これはCSSのtransform-originプロパティを使うことで変更可能です。 transform-originのサンプルです。 transform-origin

座標軸に関する注意点

x軸・y軸・z軸は 要素ごとに設定されています。どういうことかと言うと、要素を回転させると、座標軸も共に回転します。

transform関数を複数指定するときの挙動について

1. 指定順に適用される

transformでは rotate + translate のようにtransform関数を複数同時に適用できます。ただし、 記述順に変形されます。

さきほど軸は要素を回転させるとともに回転すると書きました。つまり

  • translateZ を行った後 rotateX をする
  • rotateX をしたあとに translateZ をする

この2つは結果が異なります。複数指定するときは、このことに気をつけましょう。

実際に順序が違うサンプルを用意しました。

order.png

(transformの記述順による変形の違いの図)

赤はrotateが先、緑はtranslateが先です。それ以外の条件は同じにしてあります。

上の画像のコードは表記順による違いからどうぞ。

2. 後ろに書いたtransform関数は「上書き」ではなく「追加」

例えば以下の例でtranslate1とtranslate2は同じではありません。

.translate1 {
  transform: translateX(100px);
}
.translate2 {
  transform: translateX(100px) translateX(100px);
}

translate2はtranslate1からさらに translateX(100px) しています。

CSSだと後ろに書いたほうが優先されるパターンが多いので勘違いしがちです。

検証コード

transformはブラウザによって表示結果がかなり違う

まず、transformならではの問題から話します。

3D周りはやはり実装が難しいのか、 モダンブラウザ間でも 表示が全く違うことがよくあります。バグも多いです。

なんと、同じWebKit系列のSafariとChromeでさえも異なることが多々あります。transformを使うときは普段以上に複数のブラウザで動作確認することが大切です。「一つのモダンブラウザで動いたから終わり」では大変危険です。

browser.png

(ブラウザ間で表示が異なるの図 上: Chrome、下: Safari)

ちなみに上の画像、たぶんSafariはバグです。僕の予想どおりの挙動だったのはChromeだったので。上の画像の再現コードはこちら

ベンダープレフィクス

ここ半年ほどの情報が全く出回ってなかったので、ベンダープレフィクスの必要性についてはわかりません。おそらく、ほぼ全てのモダンブラウザで(バグこそあれど)transformはサポートされています。

しかし、transform-styleなど一部のプロパティにはまだベンダープレフィクスをつけた方がいいかもしれません。

transform関数たち

基礎情報については一通り書いたので、今度はtransform関数について見ていきます。

rotate()から始める

前振りも終わりいよいよ入門編です。まずは rotate() からはじめます。

(注意: 基本編でも書きましたが、rotateX()rotateY()rotateZ()をまとめてrotate()と呼んでいるだけなのでrotate()自体は存在しません)

rotate() はその名の通り、回転させるだけの関数です。rotateX()rotateY()rotateZ() はそれぞれx軸、y軸、z軸周りに回転させます。

rotate() のサンプルです。青を変形させたものが赤になります。透過しているので重なった部分が紫になってます。

rotate.png

(rotate()のサンプル画像)

rotate3D()

rotateX()rotateY()はX軸、Y軸周りでしか回転させられませんでした。rotate3D(x, y, z, deg) では回転軸を自由に設定できます。 (x, y, z)で定められた方向ベクトルを回転軸にします。

まだベクトルを習ってない人は、原点と(x, y, z)を通る直線を軸にする、と考えてください。

回転方向は上の軸の画像と同じ方向です。

原点を変更するにはtransform-originというプロパティを使います。

ちなみに、rotate()は全てrotate3D()で書き換え可能です。

translate()

translate()指定した量だけ要素を移動させます 。3D方向にも移動させることができます。

回転と移動を同時に行う場合は まず移動させてから回転させる ようにすると書きやすいです。なぜかというと、先に回転させると移動軸も一緒に回ってしまうので考えるのが難しくなってしまいます。

3Dを扱うときはperspectiveプロパティが必要

Z軸方向に移動させるときは奥行きを設定する perspective というCSSプロパティを設定する必要があります。指定しない場合は奥行き0なので移動させても意味がありません。perspectivetranslate() する要素の親要素に設定してください。

perspective プロパティ の代わりに transform: perspective() でも可能です。この場合は translate() する要素に直接設定してください。

skew()

まずサンプル skew

これが一番の曲者です。回転でも移動でもない「傾斜変形」です。言葉で説明する前にまず画像を見てもらいます。

skew.png

(skew()のサンプル画像)

これが skewX() で変形した要素です。ちなみに skewX(80deg) をするとこうなります。ちょっと面白い。

skew2.png

(skew(80deg)のサンプル画像)

skewX() がどういう変形を加えているか説明します。まず要素を1pxの幅に切ります。次にその要素を回転させます。最後に、高さが元の要素と同じになるよう引き伸ばします。

skewY() でも高さ1pxに要素を切り回転させ拡大するという同様の変形をしています。

…うまい説明ができなくてすいません。

scale()

scale() は拡大縮小をします。以上です。…しいて言うなら、数値は%ではなく何倍かで指定します。1倍なら拡大縮小をしません。これはほんとに言うことないですね。そのまんまです。

matrix()

matrix() は行列を用いて変形します。 僕もいまいち理解できてません。 コメントで大変わかりやすいスライドを教えていただきました。高校で行列を習ってない僕でも理解できました。一読をお勧めします。

3Dの場合は(x, y, z, 1)という行列に4x4の行列( matrix3d() の引数として定義される行列)を掛けた結果を用いて変形します。数学では回転、正射影、反転、拡大縮小なら(x, y, z)のみで良いのですが、線形変形で平行移動をするにはどうしても4次元目が必要となります。

ただ、ここで気をつけていただきたいのが、この行列は数学と逆順で乗算を行っていることです。

 数学の場合 \left(\begin{array}{}x^\prime \\y^\prime\\ z^\prime\\1\end{array}\right) = M\cdot \left(\begin{array}{}x \\y\\ z\\1\end{array}\right)\\
CSS Transform \left(\begin{array}{}x^\prime \quad y^\prime \quad z^\prime \quad 1\end{array}\right) = \left(\begin{array}{}x \quad y \quad z \quad 1\end{array}\right) \cdot M^T

このため、Mは数学の行列に対して転置する必要があります。また、Mをつなげた場合の挙動も異なります。

 {
数学の場合 \left(\begin{array}{}x^\prime \\ y^\prime\\ z^\prime\\ 1\end{array}\right) = M_3\cdot M_2\cdot M_1\cdot \left(\begin{array}{}x \\ y\\ z\\ 1\end{array}\right)\\
CSS Transform \left(\begin{array}{}x^\prime \quad y^\prime \quad z^\prime \quad 1\end{array}\right) = \left(\begin{array}{}x \quad y \quad z \quad 1\end{array}\right) \cdot M_1^T \cdot M_2^T \cdot M_3^T}

ちなみに、rotate()scale()skew()translate() らは全て matrix() の糖衣構文です。ただし、 matrix() に書き直すと冗長になり、可読性も落ちます。matrix() なしで4つだけでもほとんどのことはできるので、 matrix() は基本使わない方針でいるのが幸せだと思います。

transform-styleのpreserve-3dで平面から脱却する

これに関しては、とほほさんの説明が逸品です。参照先でサンプルもあるので御覧ください。

CSS - transform-style

サンプル作りました。preserve-3d

このプロパティは「3D変換した映像を平面に写すか、立体的に表示するか」設定するプロパティです。つまり、親要素をテレビ画面とすると、普通のテレビのように平面として写すか、3Dテレビのようにはみ出すかを設定できます。

transform-styleプロパティはデフォルトではflatが設定されています。つまり普通のテレビと同じです。

preserve-3dを設定すると子要素が親要素の平面から飛び出して描画されます。

ちなみにIEは12以上で対応してます。つまり11以下では使えません。

応用編: サイコロを作る

以上を踏まえて最後にサイコロを作ってみたいと思います。

6面をdivでつくり、それをtransformで回転+移動させることでできそうです。

完成品がこちら

サイコロ

CSSだけで立体が作れる、結構すごいですよね!