CSS Transforms についてのメモ

Category : CSS

既に色々なデモページが作られていたり、実用されていたりする CSS Transforms ですが、ちゃんと理解したかったので調べてみました。
まだそんなに自信はありませんが、以前よりは理解できた気がします。
というわけなので、自分が分かる範囲で説明してみたいと思います。

CSS Transforms は 2D と 3D とで別になっていますが、将来的には SVG Transforms と合わせて一つの仕様になるそうです
今はまだ別々なので、それぞれにリンクをしておきます。括弧内は現時点での最新の日付です。

新しくなった仕様が出ていましたので、リンクを追加しておきます。最新版へのリンクです。

記事内容に若干の不安があるので、他力本願で申し訳ないですが間違っていたら教えていただけると助かります。

CSS 2D Transforms

transform プロパティ

どのような変形を行うかを指定します。指定は Transform 関数で行います。
関数は空白区切りにより複数指定できます。
各関数は指定した順に適用されます。

初期値は none です。

translate(tx, ty)

translate() では、要素の移動を行います。
1つ目のパラメータで X 方向への移動距離、2つ目のパラメータで Y 方向への移動距離を指定します。
2つ目のパラメータ ty が省略された場合、ty0 として計算されます。
指定には <length> 形式、または <percentage> を使用します。

以下はサンプルです。
灰色の部分が変形適用前のもの、赤い部分が適用後のものとなります。

transform:translate() sample

.sample1 {
  transform: translate(0);
}

.sample2 {
  transform: translate(20px);
}

.sample3 {
  transform: translate(20px, 20px);
}

transform:translate() sample

translateX()translateY()

translate() を個別に指定する関数です。
translateX() は X 方向へ、translateY() は Y 方向への移動距離を指定します。

scale(sx, sy)

要素の拡大縮小を行います。
1つ目のパラメータで X 方向の比率を、2つ目のパラメータで Y 方向の比率を指定します。
2つ目のパラメータ sy が省略された場合、sy は1つ目のパラメータ sx と同じ値として計算されます。
また、指定には <number> 形式を使用します。

transform:scale() sample

.sample1 {
  transform: scale(0);
}

.sample2 {
  transform: scale(1.2);
}

.sample3 {
  transform: scale(1.2, 0.8);
}

transform:scale() sample

scaleX()scaleY()

scale() を個別に指定する関数です。
scaleX() で X 方向、scaleY() で Y 方向の比率を指定します。
仮に scaleX() しか指定されていない場合、残る scaleY() の値は 1 となります。これは逆の場合でも同じです。

rotate()

要素を回転させます。回転の方向は時計回りです。
回転の中心点は transform-origin (後述) プロパティで指定されたポイントとなります。
パラメータは1つだけで、<angle> 形式で指定します。

transform:rotate() sample

.sample1 {
  transform: rotate(0);
}

.sample2 {
  transform: rotate(15deg);
}

.sample3 {
  transform: rotate(-15deg);
}

transform:rotate() sample

skewX(), skewY()

要素を傾斜させます。
skewX() は X 軸に沿って指定された分の傾斜を与えます。同様に、skewY() は Y 軸に沿います。
パラメータは1つだけで、<angle> 形式で指定します。

transform:skewX() & transform:skewY() sample

.sample1 {
  transform: skewX(0);
}

.sample2 {
  transform: skewX(15deg);
}

.sample3 {
  transform: skewY(15deg);
}

transform:skewX() & transform:skewY() sample

matrix(a, b, c, d, e, f)

行列を使用して変形を指定します。これ1つで上記の指定をまとめて行うこともできます。
非常にややこしいので CSS しか書かない人が使う機会は少ないかもしれませんが、JavaScript で変形を操作するときには必要になってきたりします。CSS で rotate()scale() などを使って書いたとしても、JavaScript から transform の値を取得すると、この matrix() になるからです。

この関数には6つのパラメータがあり、変形なしの状態だと matrix(1, 0, 0, 1, 0, 0) となります。

使用される行列は 3×3 ですが、うち3つは固定です。

matrix() の行列

変形なしの状態の行列はこうなります。

行列の初期値

この行列が何をするのかですが、この行列を使って座標を算出します。
要素の4つの頂点(矩形なので)を、それぞれ (X, Y, 1) とし、これを行列に乗算して座標を出します。

2D Transform 行列の計算 1

これを計算します。

2D Transform 行列の計算 2

この x’y’ が変形後の座標になります。
これを各頂点ごとに行い、それぞれの結果に応じて要素の頂点の座標を移動することで変形が行われます。

たとえば、100×100 の要素を translate(10, 20) すると、次のようになります。
上から順に、左上・右上・左下・右下の頂点です。

2D Transform 行列の計算 3 座標の算出

次に、ここまで出てきた関数を行列に変換してみます。

translate() から matrix() へ

translate(tx, ty)matrix(1, 0, 0, 1, tx, ty) で表せます。
translateX(tx), translateY(ty) でも同様です。

translate() から matrix() へ

scale() から matrix() へ

scale(sx, sy)matrix(sx, 0, 0, sy, 0, 0) で表せます。
scaleX(sx), scaleY(sy) でも同様です。

scale() から matrix() へ

rotate() から matrix() へ

rotate(a)matrix(cos(a), sin(a), -sin(a), cos(a), 0, 0) で表せます。
a は角度で、度数法 (degree) ではなく弧度法 (radian) で指定します。
たとえば 90deg の場合、90 * π / 180 = 1.570796325 [rad] となります。

rotate() から matrix() へ

skewX(), skewY() から matrix() へ

skewX(a)matrix(1, 0, tan(a), 1, 0, 0) で表せます。
arotate() のときと同じです。

skewX() から matrix() へ

また、skewY(a)matrix(1, tan(a), 0, 1, 0, 0) で表せます。

skewY() から matrix() へ

複数指定されている場合

仮に、rotate(30deg) scale(2) translate(10px, 10px) といった指定があったとしましょう。
この場合には、次のようになります。
(四捨五入してます)

rotate(30deg) scale(2) translate(10px, 10px) から matrix() へ

matrix() でまとめて指定すると matrix(1.73205, 1, -1, 1.73205, 7.32051, 27.32051) となります。
計算方法はめんどくさいので行列の乗算の方法を調べてください。
計算するだけなら Web 2.0 scientific calculator などを使うと楽です。

実際に試して見ましょう。

transform:matrix() sample 1

.sample1 {
  transform: rotate(30deg) scale(2) translate(10px, 10px);
}

.sample2 {
  transform: matrix(1.73205, 1, -1, 1.73205, 7.32051, 27.32051);
}

transform:matrix() sample 1

これを重ねてみます。

transform:matrix() sample 2

transform:matrix() sample 2

transform-origin プロパティ

transform-origin では、変形の原点を指定できます。
指定には、<percentage><length>、キーワードが使えます。
キーワードは、水平方向の場合 left, center, right で、垂直方向の場合は top, center, bottom となります。

値が1つしかない場合、2つ目の値は center として扱われます。

値が2つの場合、その値がキーワードでなければ、1つ目の値は水平方向、2つ目の値は垂直方向のものとして扱われます。
<percentage> と <length> は、要素のボーダーボックスの左上隅からのオフセットを表します。

値が3つ、または4つの場合、<percentage> や <length> の前にキーワードが先行する必要があります。
たとえば、transform-origin: bottom 10px right 20px; は、垂直方向が要素の下端から 10px、水平方向が右端から 20px の位置になります。値が3つしかない場合、未指定のオフセットは 0 であるとみなされます。

正の値は、ボーダーボックスの端から内側へのオフセットを、負の値はボーダーボックスの端から外側へのオフセットを表します。

このプロパティの初期値は 50% 50% です。

次のサンプルは、赤い部分を transform: rotate(15deg); し、transform-origin の値を変更したものです。

transform-origin sample

.sample1 {
  transform-origin: 50% 50%;
}

.sample2 {
  transform-origin: 0 0;
}

.sample3 {
  transform-origin: 50% -20px;
}

transform-origin sample

2D Transforms については以上です。
CSS アニメーションとの絡み、DOM での扱いなどは仕様に載っていますので参照してください。

CSS 3D Transforms

Transforms を使うことで、要素を2次元または3次元へ移動・回転・スケール変更することができます。
Perspective transform は、奥行きのある表現を可能にします。

3次元空間では Z 軸が追加され、その正の値はウィンドウからユーザー側に向かって伸びており、負の値はユーザー側からウィンドウに向かってより奥に伸びています。
Z 軸を真横から見た場合、ユーザー側が正の値、その逆が負の値となります。

ちょっと斜め上から見たらこんな感じです。文字が書かれているほうが負になります。
(文字も回転しちゃってますが気にしないでください)

CSS 3D Transforms で使われる3つの軸

それと、ここから先で使うサンプルですが、変形の確認に使う基本の HTML と CSS はこんな感じになっています。

<div class="drawing">
  <h2>...</h2>
  <div class="box">
    <div class="base"></div>
    <div class="sample"></div>
  </div>
</div>
.box {
  position : relative;
  width    : 200px;
  transform: rotateX(-10deg) rotateY(-20deg);
}

.base,
.base + div {
  position: absolute;
  top     : 50px;
  left    : 50px;
  width   : 100px;
  height  : 100px;
}

また、正面からだと変形したことが分かりにくいものもありますので、斜め上から見ているような感じにしていますが、画面上のほうにあるボタンで切り替えられます。
軸も表示していますが、これも表示・非表示を切り替えられます。

transform プロパティ

2D Transforms と同様、どのような変形を行うかを指定します。指定は Transform 関数で行います。
関数は空白区切りにより複数指定できます。
また、各関数は指定した順に適用されます。

初期値は none です。

translate3d(tx, ty, tz)

3D での要素の移動を行います。
指定には <length> 形式、または <percentage> を使用しますが、Z 方向では <length> のみが使用できます。

以下はサンプルです。
灰色の部分が変形適用前のもの、赤い部分が適用後のものとなります。

transform:translate3d() sample

.sample1 {
  transform: translate3d(20px, 0, 0);
}

.sample2 {
  transform: translate3d(0, 20px, 0);
}

.sample3 {
  transform: translate3d(0, 0, 50px);
}

transform:translate3d() sample 1

transform:translate3d() sample 2

translateX(tx), translateY(ty), translateZ(tz)

移動を個別に指定します。
translateX(tx)translate3d(tx, 0, 0) と同じです。
同様に、translateY(ty)translate3d(0, ty, 0) と同じ、translateZ(tz)translate3d(0, 0, tz) と同じになります。

パラメータには <length> または <percentage> ですが、translateZ() の場合 <percentage> は使えません。

scale3d(sx, sy, sz)

3D での要素の拡大縮小を行います。指定には <number> 形式を使用します。

transform:scale3d() sample 1

.sample1 {
  transform: scale3d(1.5, 1, 1);
}

.sample2 {
  transform: scale3d(1, 1.5, 1);
}

.sample3 {
  transform: scale3d(1, 1, 1.5);
}

transform:scale3d() sample 1-1

transform:scale3d() sample 1-2

X 方向、Y 方向への比率が変化しているのが分かります。ですが、Z 軸については何も変化していないように見えます。Z 方向へ拡大するからといって、要素に厚みが出るわけではありません。
では、Z 方向へ拡大したものを Y 軸を中心に少し回転させてみましょう。
(回転については次の次のセクションからやります)

  1. 回転のみで、比率の変化はなし
  2. Z 方向への拡大
  3. X 方向と Z 方向への拡大

と、なっています。

transform:scale3d() sample 2

.sample1 {
  transform: scale3d(1, 1, 1) rotateY(45deg);
}

.sample2 {
  transform: scale3d(1, 1, 1.5) rotateY(45deg));
}

.sample3 {
  transform: scale3d(1.5, 1, 1.5) rotateY(45deg);
}

transform:scale3d() sample 2-1

transform:scale3d() sample 2-2

回転されたものが、Z 方向にも変化しているのが分かります。
scale3d(1, 1, sz) は Z 方向に変化のある要素の Z 方向の比率が変更される、ということです。

scaleX(sx), scaleY(sy), scaleZ(sz)

scale3d() の各パラメータを個別に指定する関数です。
scaleX(sx)scale3d(sx, 1, 1) と、scaleY(sy)scale3d(1, sy, 1) と、 scaleZ(sz)scale3d(1, 1, sz) と同じになります。

rotate3d(x, y, z, a)

要素を 3D 回転させます。
x, y, z で方向ベクトルを指定し、a で回転の角度を指定します。回転は時計回りに行われます。
回転は transform-origin で指定したポイントを基準に行われまれ、角度の指定は <angle> 形式で記述します。

またこの関数は、matrix3d(1+(1-cos(angle))*(x*x-1), -z*sin(angle)+(1-cos(angle))*x*y, y*sin(angle)+(1-cos(angle))*x*z, 0, z*sin(angle)+(1-cos(angle))*x*y, 1+(1-cos(angle))*(y*y-1), -x*sin(angle)+(1-cos(angle))*y*z, 0, -y*sin(angle)+(1-cos(angle))*x*z, x*sin(angle)+(1-cos(angle))*y*z, 1+(1-cos(angle))*(z*z-1), 0, 0, 0, 0, 1) と等しくなります。

rotate3d() の行列

transform:rotate3d() sample

.sample1 {
  transform: rotate3d(1, 0, 0, 45deg);
}

.sample2 {
  transform: rotate3d(0, 1, 0, 45deg);
}

.sample3 {
  transform: rotate3d(0, 0, 1, 45deg);
}

transform:rotate3d() sample 1

transform:rotate3d() sample 2

rotateX(a), rotateY(a), rotateZ(a)

各ベクトルごとに回転を指定します。パラメータは <angle> 形式で記述します。
rotate3d() でまとめて指定しようとするとややこしいので、CSS を書く人はこちらをメインに使うことになるかと思います。

rotateX(a) では、a に指定された角度だけ、X 軸を基準に要素を時計回りに回転させます。
rotateX(a)rotate3d(1, 0, 0, a) と同じ変形となります。

同様に rotateY(a)rotate3d(0, 1, 0, a)rotateZ(a)rotate3d(0, 0, 1, a) と同じ変形となります。
回転の基準となる軸もそれぞれ Y 軸・Z 軸となります。

回転の中心となるポイントは transform-origin で指定した座標になります。

transform:rotateX() rotateY() rotateZ() sample

.sample1 {
  transform: rotateX(45deg);
}

.sample2 {
  transform: rotateY(45deg);
}

.sample3 {
  transform: rotateZ(45deg);
}

transform:rotateX() rotateY() rotateZ() sample 1

transform:rotateX() rotateY() rotateZ() sample 2

skew(ax, ay)

要素を傾斜させます。
1つ目のパラメータで X 方向の、2つ目のパラメータで Y 方向の傾斜を指定します。
2つ目のパラメータが指定されていない場合、0 として扱われ、Y 方向の変形は行われません。 パラメータは <angle> 形式で記述します。

transform:skew() sample

.sample1 {
  transform: skew(30deg, 0);
}

.sample2 {
  transform: skew(0, 30deg);
}

.sample3 {
  transform: skew(30deg, 30deg);
}

transform:skew() sample 1

transform:skew() sample 2

skewX(a), skewY(a)

要素の傾斜を X 方向、Y 方向と個別に指定する関数です。
パラメータは <angle> 形式で記述します。

skewX(a)skew(a, 0)skewY(a)skew(0, a) と同じ変形となります。

perspective(d)

透視投影による指定をします。これを指定することにより、変形に遠近効果をつけることができます。遠くのものはより小さく、近くのものはより大きく、といった感じです。

仕組みとしては、視点のポイントから要素の4つの各頂点を線で結び、その線の z=0 の位置に描画されるものがブラウザで閲覧する変形後の要素となります。
たとえば Z の位置が小さいほど、線同士が作る角度は狭くなるので、より小さく描画されます。
この perspective()z=0 から視点ポイントまでの距離を指定します。

綺麗な図が描けなかったので真横からの図になりますが、次のような感じです。
CSS Transforms でいうところの translateZ() だけを指定した要素で、結ばれている線は実際には頂点ごとに、つまり4本あると思ってください。
「Z 方向に対し正(負)の方向に移動した要素」自体の大きさは同じです。

Perspective について 1

Perspective について 2

関数の指定については <length> 形式で記述するとなっていますが、px 以外で指定しているものを見たことがないです……。
数値の大小による見え方の違いですが、数値が小さいほど遠近感が強く、大きいほど遠近感が弱くなります。

指定する値は 0 より大きい値である必要があります。

また、Perspective が指定されていない場合、要素の各頂点から z=0 への線が z=0 に対して垂直になるように投影されます。

transform:perspective() sample

.sample1 {
  transform: translateZ(50px);
}

.sample2 {
  transform: perspective(200px) translateZ(50px);
}

.sample3 {
  transform: perspective(500px) translateZ(50px);
}

.sample4 {
  transform: rotateY(45deg)
}

.sample5 {
  transform: perspective(200px) rotateY(45deg)
}

.sample6 {
  transform: perspective(500px) rotateY(45deg)
}

transform:perspective() sample 1

transform:perspective() sample 2

matrix3d(a1, b1, c1, d1, a2, b2, c2, d2, a3, b3, c3, d3, a4, b4, c4, d4)

行列による指定を行います。この行列は座標変換を行うためのものです。
座標変換のやり方はは 2D のときと同じです。

この関数は16個のパラメータがあり、変形なしの状態だと matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) となります。

matrix3d() では 4×4 の次のような行列を使います。

matrix3d() の行列

変形なしだとこうなります。

matrix3d() の行列 初期値

ちなみに、matrix(a, b, c, d, e, f)matrix3d() で表すと、matrix3d(a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, e, f, 0, 1) となります。

matrix() を matrix3d() に

translate3d() から matrix3d()

translate3d(tx, ty, tz)matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1) で表せます。
translateX(tx)translate3d(tx, 0, 0) なので、同じように当てはめるといいです。これは translateY(ty), translateZ(tz) でも同様です。

translate3d() から matrix3d() へ

scale3d() から matrix3d()

scale3d(sx, sy, sz)matrix3d(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1) で表せます。
scaleX(sx)scale3d(sx, 1, 1) なので、同じように当てはめるといいです。これは scaleY(sy), scaleZ(sz) でも同様です。

scale3d() から matrix3d() へ

rotate3d() から matrix3d()

rotate3d(x, y, z, a) は、その関数の項目で書いたように matrix3d(1+(1-cos(angle))*(x*x-1), -z*sin(angle)+(1-cos(angle))*x*y, y*sin(angle)+(1-cos(angle))*x*z, 0, z*sin(angle)+(1-cos(angle))*x*y, 1+(1-cos(angle))*(y*y-1), -x*sin(angle)+(1-cos(angle))*y*z, 0, -y*sin(angle)+(1-cos(angle))*x*z, x*sin(angle)+(1-cos(angle))*y*z, 1+(1-cos(angle))*(z*z-1), 0, 0, 0, 0, 1) となります。

rotate3d() から matrix3d() へ

rotateX(), rotateY(), rotateZ() から matrix3d()

これらを計算するときには、角度 a を radian で計算します。
2D の matrix() でも触れましたが、degree aa * π / 180 で radian に変換できます。

rotateX()matrix3d(1, 0, 0, 0, 0, cos(a), sin(a), 0, 0, -sin(a), cos(a), 0, 0, 0, 0, 1) となります。

rotateX() から matrix3d() へ

rotateY()matrix3d(cos(a), 0, sin(a), 0, 0, 1, 0, 0, -sin(a), 0, cos(a), 0, 0, 0, 0, 1) となります。

rotateY() から matrix3d() へ

rotateZ()matrix3d(cos(a), sin(), 0, 0, -sin(a), cos(a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) となります。

rotateZ() から matrix3d() へ

skew() から matrix3d()

skew(ax, ay) は、matrix3d(1, tan(ay), 0, 0, tan(ax), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) となります。
skewX(ax), skewY(ay) で個別に指定するときも同様です。

skew() から matrix3d() へ

perspective() から matrix3d()

perspective(d) は、matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1/d, 0, 0, 0, 1) となります。

perspective() から matrix3d() へ

transform-origin プロパティ

変形の原点を指定します。原点の座標は、要素の左上 (0, 0, 0) からの距離となります。
初期値は 50% 50% 0 です。

指定は 2D Transforms のときと同様ですが、Z 方向場合にはパーセンテージとキーワードは使用できません。

次のサンプルは、赤い部分を transform: rotateY(45deg); し、transform-origin の値を変更したものです。

transform-origin sample

.sample1 {
  transform-origin: 50% 0 0;
}

.sample2 {
  transform-origin: 0 50% 0;
}

.sample3 {
  transform-origin: 0 0 50px;
}

transform-origin sample

transform-style プロパティ

このプロパティは、指定した子要素が 3D 上で平面として描画されるか、立体として表示されるかを指定します。

指定できる値は flat または preserve-3d で、初期値は flat です。
flat を指定すると、3D 空間上で平面として描画され、preserve-3d を指定すると、立体的に表示されます。

次のサンプルは、各サンプルの親要素に transform-style プロパティで flat, preserve-3d を指定したものです。

transform-style sample

transform-style sample

flat を指定した1つ目の例では Z 方向のものも平面になっているのが分かります。

perspective プロパティ

transform プロパティで使う perspective() 関数と同じ意味です。
違いは、perspective() 関数が要素そのものの深度を指定するのに対し、この perspective プロパティでは、指定した要素の子要素全体の深度を指定することにあります。

同様に、指定には <length> 形式を用い、0 より大きい値を指定しなければなりません。

perspective-origin プロパティ

perspective プロパティの視点ポイントの位置を指定します。値は2つあり、X 方向 Y 方向の座標を指定します。
値には transform-origin と同様に、<percentage> と <length>、キーワードが使えます。
キーワードは、水平方向の場合 left, center, right で、垂直方向の場合は top, center, bottom となります。
0 0 は要素の左上の座標です。

初期値は 50% 50% です。

2つ目の値が省略され、1つ目の指定がキーワードでなければ、もう1つの値は 50% として扱われます。
1つ目が X 方向のキーワードの場合は、Y 方向は 50% となり、Y 方向のキーワードの場合は X 方向が 50% として扱われます。

仮に、Z 軸の負の方向に移動した要素の親要素に perspective を指定し、perspective-origin の Y 方向の値を増やすと、次のような図になります。

perspective-origin について

以下のサンプルは、赤い色のボックスが transform: translateZ(-100px) されたもので、全ての親要素に、perspective: 500 が指定されており、さらに、親要素に perspective-origin がそれぞれ 50% 50%, 0 0, 100% 100% と指定されたものです。

perspective-origin sample

perspective-origin sample 1

正面から見ると次のようになります。

perspective-origin sample 2

backface-visibility プロパティ

backface-visibility プロパティは、要素の裏側を表示するかどうかを指定するプロパティです。

値には hiddenvisible が使用可能で、初期値は visible となっています。
hidden を指定すると、要素の表面が見えなくなった(たとえば rotateY(180deg) された)場合に、その要素は見えなくなります。
visible の場合は、その要素を裏側から透かして見るような感じになります。

transform: rotateY(180deg) した要素の backface-visibility プロパティに、それぞれ hidden, visible を指定すると次のようになります。

backface-visibility

backface-visibility sample

transform プロパティでの注意点

このプロパティに指定する各関数ですが、指定するときは順番に注意したほうがいいかと思います。
変形の最終的な座標は行列によって計算されるわけですが、仮に transform: 関数A 関数B という変形を行うとすると、関数Aの行列 * 関数Bの行列 となり、そこから座標が計算されるということです。

基本的に行列の乗算には交換法則が成り立ちませんので、行列 AB があるとき、A*BB*A は別物となります。

とりあえずは、transform プロパティに指定する関数は書いた順に適用される、と覚えておけばいいと思います。

3D での回転について

上の関数の書き順にも関係してくるのですが、変形のない状態の場合、見た目の上では X 軸は左から右へ、Y 軸は上から下へ、Z 軸は奥から手前へ、と数値が増えていきます。
ですが 3D Transforms において回転変形を行うと、その要素の各軸は回転に伴って移動します。たとえば、rotateY(180deg) すると、その要素は裏返したような状態になりますが、このとき X 軸は右から左へ、Z 軸は手前から奥へ、となります。
transform: rotate(180deg) したものを奥にあるように見せるには、rotate(180deg) translateZ(100px) とします。translateZ(100px) rotate(180deg) では手前側に移動した後に回転となるので、手前側にあるように見えます。
translateZ() を先に書くなら、translateZ(-100px) rotate(180deg) とします。

回転する立方体を作ってみる

練習を兼ねてアニメーションしながら回転する立方体を作ってみます。
まず、ベースとなる部分から。

実際にはプロパティごとに各ベンダープレフィクスが必要となりますが、記事内には書きません。
あまり関係ない部分も省略しています。

<div class="drawing">
  <div class="cube">
    <div class="panel front">1</div>
    <div class="panel back">2</div>
    <div class="panel left">3</div>
    <div class="panel right">4</div>
    <div class="panel top">5</div>
    <div class="panel bottom">6</div>
  </div>
</div>

.drawing {
  width : 200px;
}

.cube {
  position       : relative;
  width          : 200px;
  height         : 200px;
  transform-style: preserve-3d;
}

.panel {
  position   : absolute;
  border     : 1px solid #f00;
  width      : 200px;
  height     : 200px;
  box-sizing : border-box;
}

.front {
  background: rgba(255, 0, 0, 0.7);
}

.back {
  background: rgba(0, 0, 255, 0.7);
}

.left {
  background: rgba(255, 255, 0, 0.7);
}

.right {
  background: rgba(0, 255, 255, 0.7);
}

.top {
  background: rgba(0, 255, 0, 0.7);
}

.bottom {
  background: rgba(255, 0, 255, 0.7);
}

3D 変形させるのは div.panel の6つなので、div.cubetransform-style: preserve-3d を指定します。
この時点では次のようになります。

CSS Cube 1

回転する立方体を作る 1

次に、各パネルを回転させ、立方体になったとき、表が外側に来るようにします。

  • .front は最初から外を向いているので変更なし
  • .back は Y 軸を中心に180度
  • .left は Y 軸を中心に90度
  • .right は Y 軸を中心に-90度
  • .top は X 軸を中心に90度
  • .bottom は X 軸を中心に-90度
.front {
  background: rgba(255, 0, 0, 0.7);
}

.back {
  background: rgba(0, 0, 255, 0.7);
  transform : rotateY(180deg);
}

.left {
  background: rgba(255, 255, 0, 0.7);
  transform : rotateY(90deg);
}

.right {
  background: rgba(0, 255, 255, 0.7);
  transform : rotateY(-90deg);
}

.top {
  background: rgba(0, 255, 0, 0.7);
  transform : rotateX(90deg);
}

.bottom {
  background: rgba(255, 0, 255, 0.7);
  transform : rotateX(-90deg);
}

CSS Cube 2

回転する立方体を作る 2 各要素を回転させる

正面からだとあまり変化がないように見えますが、ちょっと斜め上から見ると次のようになります。

回転する立方体を作る 2 各要素を回転させる 斜め上からみたところ

それでは、立方体にするために各パネルを移動します。
前に説明したように、回転済みのものを移動するときには要素と共に回転した軸を考慮する必要があります。ここで、各パネルの表が外側を向くように回転させていますので、どのパネルも移動する方向は Z 軸の正の方向になります。

.front {
  background: rgba(255, 0, 0, 0.7);
  transform : translateZ(100px);
}

.back {
  background: rgba(0, 0, 255, 0.7);
  transform : rotateY(180deg) translateZ(100px);
}

.left {
  background: rgba(255, 255, 0, 0.7);
  transform : rotateY(90deg) translateZ(100px);
}

.right {
  background: rgba(0, 255, 255, 0.7);
  transform : rotateY(-90deg) translateZ(100px);
}

.top {
  background: rgba(0, 255, 0, 0.7);
  transform : rotateX(90deg) translateZ(100px);
}

.bottom {
  background: rgba(255, 0, 255, 0.7);
  transform : rotateX(-90deg) translateZ(100px);
}

CSS Cube 3

回転する立方体を作る 3 各要素を移動させる

回転する立方体を作る 3 各要素を移動させる 斜め上からみたところ

立方体ができました。
それでは、これをアニメーションで回転させます。アニメーションの指定については、以前書いた CSS アニメーションの基礎 を参考にしてもらえればと思います。

.cube {
  position       : relative;
  width          : 200px;
  height         : 200px;
  transform-style: preserve-3d;
  animation      : rotation 4s linear infinite;
}

...

@keyframes rotation {

  from {
    transform: rotateY(0);
  }

  to {
    transform: rotateY(360deg);
  }

}

CSS Cube 4

回転する立方体を作る 4 回転アニメーション

アニメーションができました。
次は遠近感を出すために Perspective を指定します。

.cube {
  position       : relative;
  width          : 200px;
  height         : 200px;
  transform-style: preserve-3d;
  perspective    : 500px;
  animation      : rotation 4s linear infinite;
}

CSS Cube 5

回転する立方体を作る 5 奥行きを出したいが上手くいかない

回転する立方体を作る 5 奥行きを出したいが上手くいかない

何か変ですね。ではなぜこうなったのか考えてみます。

パネルが立方体を作るための変形(回転と移動)をする際、親要素で指定された Perspective が加わります。それは全ての子要素から見て同じ位置であり、親要素の z=0 から視点までの距離です。
この親要素を回転させると、Z 軸も一緒に回転するわけで、それ伴って Perspective での視点も移動します。子要素はその回転する視点に合わせてを変形するので、このようなことになってしまったと考えられます。

ではどうすればいいのかというと、.cube が回転する際に Perspective の視点が一緒に動いてしまわないようにすればいいのです。
つまり、.cube の親、.drawingperspective プロパティによる指定をしてしまえば、その視点は動かないわけですから、遠近感のある立方体ができあがるはずです。

.drawing {
  width      : 200px;
  perspective: 500px;
}

.cube {
  position       : relative;
  width          : 200px;
  height         : 200px;
  transform-style: preserve-3d;
  animation      : rotation 4s linear infinite;
}

CSS Cube 6

回転する立方体を作る 6 奥行きを出す

回転する立方体を作る 6 奥行きを出す

期待通りの動きになりました。
また、transformperspective() 関数で指定する場合には、回転の前に書かなければなりません。
ここではキーフレームの指定の中にあります。

/* CSS Cube 7 (これは上手くいかない) */
@keyframes rotation {

  from {
    transform: rotateY(0) perspective(500px);
  }

  to {
    transform: rotateY(360deg) perspective(500px);
  }

}

/* CSS Cube 8 (期待通り) */
@keyframes rotation {

  from {
    transform: perspective(500px) rotateY(0);
  }

  to {
    transform: perspective(500px) rotateY(360deg);
  }

}

CSS Cube 7

CSS Cube 8

これで無事に回転する立方体が完成しました。
もしパネルの裏面を表示したくなければ、.panelbackface-visibility: hidden を指定してください。

CSS Cube

最後に

いつものことですが、この記事を鵜呑みにせず、自分でも仕様を確認したり、実際に試してブラウザの調査ツールなどで調べてみたりしてください。
仕様が変更されるかもしれませんし、そもそも記事に誤りがある可能性もあります。

最後まで読んでいただき、ありがとうございました。

Trackbacks & Pingbacks

Comments

matchaby

大変解りやすい図とサンプルで理解が深まりました。

他の記事も非常に参考になるものばかりでいつも利用させて頂いています。

これからもいい記事を楽しみにしています。

まとり

@matchaby さん
ありがとうございます。そう言っていただけると嬉しいです。
あまり更新の多いブログではありませんが、これからもよろしくお願いします。

onkeyi

大変わかりやすい図です。いきなりご質問ですが、
javascript ゲーム開発で、 ObjectA の上に ObjectBをのせて、ObjectA を rotation,scale,原点がかわった場合、 ObjectBに反映したいですが、
その場合、 transformA から transformBを取得する方法を教授いただけますか?

よろしくお願いいたします。

Leave a Reply