サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

CSS3: 3次元空間に立方体をつくって回す ー transformプロパティ

ID: FN1404001 Technique: CSS3 and JavaScript Library: Prefix free

CSS3のtransformプロパティは、要素に回転・伸縮・傾斜・移動などの座標変換を加えます。さらに、transform-styleプロパティも合わせて用いれば、要素が3次元空間で変換できます。記事「Creating an animated 3D CSS cube using 3D transforms」のサンプルを参考に、CSSで3次元空間に立方体をつくって回してみます(サンプル001)。

サンプル001■CSSで3次元空間に立方体をつくって回す


01 3次元空間の座標変換で使うCSSプロパティ

CSSのtransformプロパティは、要素に座標変換を加えます。具体的には、要素の回転や伸縮、傾斜、平行移動がプロパティで定められます。transformプロパティの値には、変換関数を与えます。次表001に掲げたのは、2次元平面の座標変換を行う関数です[*1]

表001■transformプロパティに与えられる2次元の変換関数と引数値
座標変換 変換関数 引数値 単位
回転 rotate(angle) 回転度数 deg
伸縮 scale(scaleX, scaleY)
scaleX(scaleX)
scaleY(scaleY)
水平・垂直伸縮比率 なし
傾斜 skew(scaleX, scaleY)
skewX(scaleX)
skewY(scaleY)
水平・垂直傾斜度数 deg
平行移動 translate(translateX, translateY)
translateX(translateX)
translateY(translateY)
水平・垂直移動座標 px

さらに、transform-styleプロパティに値としてpreserve-3dを定めると、z軸を加えた3次元空間で座標変換ができます(デフォルト値は2次元平面のflat)。すると、3次元空間の変換関数が使えるようになります。本稿のサンプルで用いる関数は、rotateX()rotateY()およびtranslateZ()です。

rotateX(x軸の回転度数)
rotateY(y軸の回転度数)
translateZ(z軸の移動座標)

要素を3次元空間に置いたように見せるには、遠近感を与えなければなりません。遠近法では、奥行き(z座標)が遠いものほど小さく、また距離も縮めて表現します。そして、かぎりなく遠ざかるにつれ、ある1点に向かって小さくなり、やがて見えなくなります。この点を「消失点」と呼びます(図001)。

図001■遠ざかるオブジェクトは消失点に向けて小さくなる
図001

CSSはperspectiveプロパティに、z座標の原点(0)から視点までの焦点距離で遠近法を定めます[*2]。ふたつのオブジェクトの奥行きの距離は、焦点距離が短くなるほど、離れて見えるようになります。逆に、焦点距離が長いと、互いに近づいて見えます(図002)。

図002■焦点距離による表現の違い
図002左
短い焦点距離
図002右
長い焦点距離

[*1] matrix()関数で変換行列を与えることもできます。

[*2] 消失点の位置は、perspective-originプロパティで決められます。デフォルトでは要素の真ん中(50% 50%)です。なお、perspectiveプロパティによる遠近感の違いについては、MDN「CSS transforms の利用」の「遠近感の準備」をご参照ください。


02 CSSプロパティのベンダー接頭辞と「Prefix free」

前項でご紹介したCSS3の3次元空間における座標変換の機能は、本稿執筆時ではまだ開発中で、ブラウザによってベンダー接頭辞を加えなければなりません。たとえば、WebKit系とMozilla系のふたつで動かすなら、つぎのようにCSSを書くことになります(CSS Lecture「CSS3 変形処理を行う transform プロパティ」参照)。

#experiment {
  -webkit-perspective: 400px;
  -moz-perspective: 400px;
}
#cube {
  -webkit-transform-style: preserve-3d;
  -moz-transform-style: preserve-3d;
}
.face {
  -webkit-transform: rotateX(90deg) translateZ(100px);
  -moz-transform: rotateX(90deg) translateZ(100px);
}

対応するブラウザをさらに拡げれば、ベンダー接頭辞は増え、同じことをその数だけ書かなければなりません(表002)。修正が入ったときの手間も増します(ヅラッシュ!「JavaScript で CSS Transforms するためのウェブブラウザ別 transform と transform-origin 一覧」参照)。

表002■CSSプロパティのベンダー接頭辞とDOMのStyleプロパティ
CSSプロパティ DOM Styleプロパティ 準拠
transform transform W3C Specification
-webkit-transform webkitTransform WebKit/Safari/Chrome
-moz-transform MozTransform Mozilla Firefox/Gecko
-ms-transform msTransform Microsoft Internet Explorer
-o-transform OTransform Opera

そこで、Javascriptライブラリ「Prefix free」を使うと、CSSからベンダー接頭辞が省けます(図003)。ベンダー接頭辞は、Prefix freeが自動的につけてくれるのです(Webクリエイターネット「CSS3のベンダープレフィックス」参照)。

図003■Prefix freeサイト
図003

しかも使い方は、ダウンロードしたPrefix freeのJavaScript(JS)ファイル(prefixfree.min.js)をscript要素に読込むだけです。将来の仕様でベンダー接頭辞が要らなくなったときも、script要素を除けば済みます。

<script src="lib/prefixfree.min.js"></script>

script要素にPrefix freeのJSファイルを読込めば、前掲のスタイルシートの例も、つぎのようにベンダー接頭辞は省くことができます。なお、抜書きした行番号は、後に掲げるサンプルのコード002にもとづきます。

  1. <script src="lib/prefixfree.min.js"></script>
  2. <style>
  1. #experiment {
  2.   perspective: 400px;
  3. }
  4. #cube {
  1.   transform-style: preserve-3d;
  2. }
  1. #cube .bottom {
  2.   transform: rotateX(-90deg) translateZ(100px);
  3. }
  4. </style>

03 立方体の底面をつくる

それでは、試しにCSSで立方体の底面をひとつつくってみましょう。body要素に、つぎのようにdiv要素を入れ子にします。3次元空間を定める親の要素(id属性"experiment")に、後から6面が加わる立方体の要素(id属性"cube")を加えます(第1〜2行目)。さらに、その子の底面にはふたつのクラスを定めました。ひとつ("face")が6面共通で、もうひとつ("bottom")は面ごとに異なります(第19行目)。面の要素には、img要素でPNG画像("images/pen.png")を含めました(第20行目)。なお、抜書きした行番号は、後に掲げる6面が加わったコード001にもとづきます。

  1. <div id="experiment">
  2.   <div id="cube">
  1.     <div class="face bottom">
  2.       <img src="images/pen.png">
  3.     </div>
  4.   </div>
  5. </div>

3次元空間と立方体、および底面のCSSは以下のように定めます(行番号は後掲コード002にもとづきます)。まず、3次元空間の要素(idセレクタ#experiment)には、perspectiveプロパティで焦点距離(400px)を与えました(第6〜8行目)。つぎに、立方体の要素(idセレクタ#cube)は、transform-styleプロパティをpreserve-3dとしています(第15行目)。そして、6面のクラス(クラスセレクタ.face)には共通のプロパティを定めたうえで(第17〜27行目)、面ごとのクラスに3次元空間の座標変換を加えます。

底面の要素(クラス.bottomセレクタ)は、x軸で90度回して、下方向に一辺の半分の長さ(100px)移動します(第43〜45行目)。このとき気をつけなければならないのは、要素とともにその座標軸も回転することです。そのため、下向きは要素から見れば正面なので、z座標を動かすことになります(図004)。これで、立方体の底面ができあがりました。

図004■面をx軸で90度回転して正面のz軸方向に移動する
図004

  1. <script src="lib/prefixfree.min.js"></script>
  2. <style>
  3. body {
  4.   font: 18px sans-serif;
  5. }
  6. #experiment {
  7.   perspective: 400px;
  8. }
  9. #cube {
  10.   position: relative;
  11.   margin: 50px auto 0;
  12.   height: 200px;
  13.   width: 200px;
  1.   transform-style: preserve-3d;
  2. }
  3. .face {
  4.   position: absolute;
  5.   height: 180px;
  6.   width: 180px;
  7.   padding: 10px;
  8.   background-color: rgba(25, 25, 75, 0.5);
  9.   line-height: 1em;
  10.   color: white;
  11.   border: 1px solid dimgray;
  12.   border-radius: 3px;
  13. }
  1. #cube .bottom {
  2.   transform: rotateX(-90deg) translateZ(100px);
  3. }
  4. </style>

04 立方体に6面を加える

前項の考え方で6面の要素とCSSを定めれば、立方体ができあがります。body要素には、つぎのコード001のように残り5面のdiv要素を加えます。それらの要素には、6面共通のクラス("face")と併せて面ごとのクラスが定められています(第3〜21行目)。

コード001■6面共通のクラスと面ごとのクラスを定めたdiv要素
  1. <div id="experiment">
  2.   <div id="cube">
  3.     <div class="face top">
  4.       <a href="http://www.fumiononaka.com" target="_blank"><img src="images/FumioNonakaLogo.png" width="180" height="36"></a>
  5.     </div>
  6.     <div class="face front">
  7.       To rotate the cube, press the arrow keys:<br>
  8.       Up, down, left, right
  9.     </div>
  10.     <div class="face right">
  11.       CSSの<em>transform</em>プロパティを用いると、要素を移動、回転、伸縮、傾斜させることができます。
  12.     </div>
  13.     <div class="face back">
  14.       New forms of navigation are fun.
  15.     </div>
  16.     <div class="face left">
  17.       Rotating 3D cube
  18.     </div>
  19.     <div class="face bottom">
  20.       <img src="images/pen.png">
  21.     </div>
  22.   </div>
  23. </div>

立方体の6面を定めるCSSは、以下のコード002のとおりです。5面にそれぞれのクラスを加えて、立方体の外に向けてx軸やy軸で回転したうえで、それぞれの面を正面つまりz軸方向に一辺の半分の長さ(100px)動かしています(第28〜45行目)。これで立方体の6面ができ上がります(図005)。

図005■CSSの座標変換で立方体の6面ができ上がった
図005

コード002■CSSによる3次元空間における6面の回転と移動で立方体をつくる
  1. <script src="lib/prefixfree.min.js"></script>
  2. <style>
  3. body {
  4.   font: 18px sans-serif;
  5. }
  6. #experiment {
  7.   perspective: 400px;
  8. }
  9. #cube {
  10.   position: relative;
  11.   margin: 50px auto 0;
  12.   height: 200px;
  13.   width: 200px;
  14.   transition: transform 2s linear;
  15.   transform-style: preserve-3d;
  16. }
  17. .face {
  18.   position: absolute;
  19.   height: 180px;
  20.   width: 180px;
  21.   padding: 10px;
  22.   background-color: rgba(25, 25, 75, 0.5);
  23.   line-height: 1em;
  24.   color: white;
  25.   border: 1px solid dimgray;
  26.   border-radius: 3px;
  27. }
  28. #cube .top {
  29.   transform: rotateX(90deg) translateZ(100px);
  30. }
  31. #cube .front {
  32.   transform: translateZ(100px);
  33. }
  34. #cube .right {
  35.   transform: rotateY(90deg) translateZ(100px);
  36. }
  37. #cube .back {
  38.   transform: rotateY(180deg) translateZ(100px);
  39. }
  40. #cube .left {
  41.   transform: rotateY(-90deg) translateZ(100px);
  42. }
  43. #cube .bottom {
  44.   transform: rotateX(-90deg) translateZ(100px);
  45. }
  46. </style>

05 JavaScriptで立方体を回す

仕上げに、キーボードの矢印キーで、立方体を上下左右に回しましょう。このインタラクションはJavaScriptコードで書きます。つまり、CSSのtransformプロパティを、スクリプトで定めることになります。そこで気をつけなければならないのは、JavaScriptに用いるDOM Styleプロパティもtransformプロパティと同じく、ブラウザによって異なるということです(前掲表002)。

たとえば、WebKit系とMozilla系のふたつなら、DOM StyleプロパティはそれぞれwebkitTransformMozTransformです。すると、JavaScriptコードには、つぎのようにふたつのプロパティを定めなければなりません(DOM Styleプロパティは「Prefix free」では変わりません)。ブラウザでどのDOM Styleプロパティが使えるかは、予め要素を調べてみればわかります。

var cubeStyle = document.getElementById("cube").style;
cubeStyle.webkitTransform = "rotateX(90deg);"
cubeStyle.MozTransform = "rotateX(90deg);"

前掲表002には、ベンダーによるDOM Styleプロパティは5つありました。これらのうちブラウザが対応するプロパティを定めるのが、以下に抜書きしたコードです(行番号は後掲コード003にもとづきます)。DOM Styleプロパティを調べるためのdiv要素("div")は仮につくります(第13行目)。そして、DOM Styleプロパティ名を予め配列(transforms)に入れておき、forループでそのプロパティが未定義でないかを確かめています(第15〜18行目)。

定義されているDOM Styleプロパティがあったら、その名前の文字列を変数(transform)に納めて、ループは抜けます(第18〜21行目)。このプロパティ名で参照したプロパティを扱えば定めはひとつで足り、ベンダーの数だけ書き並べずに済みます(Internet Explorer ブログ (日本語版)「ベンダー プレフィックスを使用したプログラミングのベスト プラクティス」参照)。

  1. var transform;
  2. var cubeStyle;
  1. function initialize() {
  2.   var transforms = [
        "transform",
        "WebkitTransform",
        "MozTransform",
        "OTransform",
        "msTransform"   ];
  3.   var cubeElement = document.getElementById("cube");
  4.   var divStyle = document.createElement("div").style;
  5.   var count = transforms.length;
  6.   for(var i = 0; i < count; i++) {
  7.     var transformType = transforms[i];
  8.     var divTransform = divStyle[transformType];
  9.     if(typeof divTransform != "undefined") {
  10.       transform = transformType;
  11.       break;
  12.     }
  13.   }
  14.   cubeStyle = cubeElement.style;
  15. }

キーボードのキー操作は、window.documentonkeydownイベントのハンドラとして関数を定めます(第25行目)。押されたキーのkeyCodeプロパティの値に応じて、立方体の要素をx軸またはy軸で±90度回します。それらxy軸それぞれの回転角は、入れ子の配列(angles)のキーコードのインデックスに納めました(第5〜9行目)。

ハンドラの関数は押されたキーのkeyCodeプロパティ値のインデックスから配列エレメント(angleXY)を取出します(第26行目)。そして、エレメントに入れ子配列があれば、そのxy軸それぞれの角度を現行角度の変数(angleXとangleY)に加えたうえで、ベンダーのDOM Style名で参照したプロパティに回転の変換関数を与えています(第27〜31行目)[*3]

  1. var angleX = 0;
  2. var angleY = 0;
  3. var angles = [];
  4. angles[37] = [   0, -90];   // left
  5. angles[38] = [ 90,    0];   // up
  6. angles[39] = [   0,  90];   // right
  7. angles[40] = [-90,    0];   // down
  1. document.onkeydown = function (eventObject) {
  2.   var angleXY = angles[eventObject.keyCode];
  3.   if (angleXY) {
  4.     angleX += angleXY[0];
  5.     angleY += angleXY[1];
  6.     cubeStyle[transform] =
          "rotateX(" + angleX + "deg) " +
          "rotateY(" + angleY + "deg)";
  7.   }
  8. };

これで、キーボードの矢印キーで立方体が上下左右に回せます(図006)。書き上がったJavaScript全体は、以下のコード003のとおりです。前掲サンプル001も、基本的にこのコードにもとづきます。

図006■キーボードの矢印キーで立方体が上下左右に回る
図006

コード003■立方体をキーボードの矢印キーで上下左右に回す
  1. var transform;
  2. var cubeStyle;
  3. var angleX = 0;
  4. var angleY = 0;
  5. var angles = [];
  6. angles[37] = [   0, -90];   // left
  7. angles[38] = [ 90,    0];   // up
  8. angles[39] = [   0,  90];   // right
  9. angles[40] = [-90,    0];   // down
  10. function initialize() {
  11.   var transforms = [
        "transform",
        "WebkitTransform",
        "MozTransform",
        "OTransform",
        "msTransform"
      ];
  12.   var cubeElement = document.getElementById("cube");
  13.   var divStyle = document.createElement("div").style;
  14.   var count = transforms.length;
  15.   for(var i = 0; i < count; i++) {
  16.     var transformType = transforms[i];
  17.     var divTransform = divStyle[transformType];
  18.     if(typeof divTransform != "undefined") {
  19.       transform = transformType;
  20.       break;
  21.     }
  22.   }
  23.   cubeStyle = cubeElement.style;
  24. }
  25. document.onkeydown = function (eventObject) {
  26.   var angleXY = angles[eventObject.keyCode];
  27.   if (angleXY) {
  28.     angleX += angleXY[0];
  29.     angleY += angleXY[1];
  30.     cubeStyle[transform] =
          "rotateX(" + angleX + "deg) " +
          "rotateY(" + angleY + "deg)";
  31.   }
  32. };

[*3] 本稿が参考にした記事「Creating an animated 3D CSS cube using 3D transforms」では、押したキーのキーコードによる切り分けはswitch文を用いています。前掲コード003のイベントハンドラの関数(第25行目)を書替えるならつぎのとおりです。

function(eventObject) {
  switch(eventObject.keyCode) {
    case 37:
      angleY -= 90;
      break;
    case 38:
      angleX += 90;
      break;
    case 39:
      angleY += 90;
      break;
    case 40:
      angleX -= 90;
      break;
  };
  cubeStyle[transform] =
    "rotateX(" + angleX + "deg) " +
    "rotateY(" + angleY + "deg)";
};

キーコードをswitch文で判定することは、とくに問題ありません。ただ、このJavaScriptコードでは矢印キー以外を押しても、要素のtransformプロパティに値の変わらない変換関数が与えられます。それは、無駄な処理といえるでしょう。



作成者: 野中文雄
作成日: 2014年4月11日


Copyright © 2001-2014 Fumio Nonaka.  All rights reserved.