Nack Labで紹介している"ダイス"、およびTech Sampleの"3D 四面"、"3D 八面"はcss trahnsformを使用して3Dの動きを実現しています。
主にPaul R. Hayes氏のExperiment: 3D cube with touch gestures and click and dragを参考にしています。
ここでは直方体を横方に回転させる"3D 四面"を例に取り上げて説明します。
4平面を作るためにDIV
<div id="divFaces">
<div id="cube">
<div class="face one" id="one">One</div>
<div class="face two" id="two">Two</div>
<div class="face three" id="three">Three</div>
<div class="face four" id="four">Four</div>
</div>
</div>
上記DIV対応するCSS
全体を包むdivFacesの設定
#divFaces
{
-webkit-perspective: 420;
-webkit-perspective-origin: 50% 100px;
}
-webkit-perspective:
視点からの距離。数値が大きいほど望遠、小さいほど広角な効果となります。
-webkit-perspective-origin:
座標軸の中心の位置。この場合、横方向中央(50%)、縦は上から100pxとなります。
divFaces内の直方体の設定
#cube {
-webkit-transition: -webkit-transform 0.2s linear;
-webkit-transform-style: preserve-3d;
margin: -150px auto 0px auto;
height: 150px;
width: 200px;
}
-webkit-transform-style: preserve-3d;
3D座標変換を行うための設定。
-webkit-transition: -webkit-transform 0.2s linear;
アニメーションの設定。3D座標変換とは関係ない。
margin、height、widthは適宜設定する。
四面に共通の設定
.face {
position:absolute;
height: 100%;
width: 100%
padding:0px;
background-color: rgba(50, 50, 50, 0.5);
font-size: 27px;
line-height: 1em;
color: #fff;
border: 1px solid #555;
}
position:absolute;
同一平面上に配置するため。これがないと四面のDIVが縦方向に重ならずに並ぶ。
height: 100%;
width: 100%;
cubeのサイズと一致するように100%をセット。100%である必要はなく、pxなどの設定でもよい。
各面についての設定
#cube .one {
-webkit-transform: translateZ(100px);
}
第1面はZ軸方向に+100px移動。手前に近付いている。
#cube .two {
-webkit-transform: rotateY(90deg) translateZ(100px);
}
第2面はY軸で+90度回転(まわれ右)してから、Z軸で100px移動(右へ移動)。
#cube .three {
-webkit-transform: rotateY(180deg) translateZ(100px);
}
第3面はY軸で+180度回転(後向き)してから、Z軸で100px移動(遠ざかる)。
#cube .four {
-webkit-transform: rotateY(-90deg) translateZ(100px);
}
第4面はY軸で-90度回転(まれれ左)してから、Z軸で100px移動(左へ移動)。
直方体回転
次のようなループで直方体を連続回転させることができます。
//touch, mouseの移動量を yAngle にセットします。
var yAngle = 0;
//TransformでrotateYを行い、Y軸を中心に回転させます。
function rotate() {
yAngle += 10;
cube.style.webkitTransform = "rotateY(" + yAngle + "deg)";
setTimeout("rotate()", 20);
}
yAngleは初期状態からの回転角で、直前に行ったTransformからの差分ではありません。
ループで回転させる場合はsetTimeoutを使用します。
3D Transformの留意点
3Dのtransformで頭が混乱するのは主に次の二点でしょう。
- XYZについてのtransformの順序で結果が異なる。
上の例ではY軸で回転してからZ軸で移動しているが、Z軸で移動してからY軸で回転したのでは全く異なる結果となります。 - rotateは座標の回転で、オブジェクトの回転ではない。
上の例ではY軸で回転してていますが、Z軸も共に回転しているため、回転後の移動は前後の移動とは限らず、回転角に応じた方向に移動することになります。
3D 四面、八面のソースコード
//コンストラクタ
Faces = function(divFaces, divCube) {
//直方体が配置されていDIV(divFaces)
this.div = divFaces;
this.cubeDiv = divCube;
//移動量計算のため直前のtouch位置をセット。初期値=Number.MIN_VALUE
this.x = Number.MIN_VALUE;
//Y軸を中心とした回転角。
this.y = 0;
//Startイベントハンドラをセット。
this.div.addEventListener(Faces.EventStart, Faces.touchStart, false);
Faces.instance = this;
Faces.div = this.div;
}
//iOS, デスクトップでイベント名を使い分ける。
Faces.EventStart = isIOS ? "touchstart" : "mousedown";
Faces.EventMove = isIOS ? "touchmove" : "mousemove";
Faces.EventEnd = isIOS ? "touchend" : "mouseup";
Faces.EventOut = isIOS ? null : "mouseout";
//Facesが作られたときの引数のdiv。イベントが発生するDIVと異なる。
Faces.div = null;
//Startイベントで作られたオブジェクト。
Faces.instance = null;
//Startイベントハンドラ。Moveイベントハンドラセット。
//thisはイベントが発生するDIV.
Faces.touchStart = function() {
if (Faces.div == null) return;
Faces.div.addEventListener(Faces.EventMove, Faces.touchMove, false);
Faces.div.addEventListener(Faces.EventEnd, Faces.touchEnd, false);
if (!isIOS) {
Faces.div.addEventListener(Faces.EventOut, Faces.touchEnd, false);
}
}
//Moveイベントハンドラ。
Faces.touchMove = function() {
if (Faces.instance == null) return;
Faces.instance.newX = event.pageX;
with (Faces.instance) {
if (x != Number.MIN_VALUE) {
y += (newX - x);
var m = y % 90;
if (m <= 2 && m >= -2) {
y -= m;
}
cubeDiv.style.webkitTransform = "rotateY(" + y + "deg)";
}
x = newX;
}
}
//Endイベントハンドラ。End, Moveイベントハンドラ解除。
Faces.touchEnd = function() {
if (Faces.instance == null) return;
Faces.instance.x = Number.MIN_VALUE;
var div = Faces.instance.div;
div.removeEventListener(Faces.EventMove, Faces.touchMove);
div.removeEventListener(Faces.EventEnd, Faces.touchEnd);
div.removeEventListener(Faces.EventOut, Faces.touchEnd);
}
Faces使用例
上記のようにDIVを配置し、body.onloadなどでFacesを作る。
<body onload="new Faces(divFaces, cube)">
3D 八面の場合
八面の場合もJavaScriptは共通で、面のモデリング、CSSが異なります。
<div id="divFaces8" style="margin-top:180px">
<div id="cube8">
<div class="face8 f1" id="f1">One</div>
<div class="face8 f2" id="f2">Two</div>
<div class="face8 f3" id="f3">Three</div>
<div class="face8 f4" id="f4">Four</div>
<div class="face8 f5" id="f5">Five</div>
<div class="face8 f6" id="f6">Six</div>
<div class="face8 f7" id="f7">Seven</div>
<div class="face8 f8" id="f8">Eight</div>
</div>
</div>
Facesを作成するときの引数を8面のものに変更します。
<body onload="new Faces(divFaces8, cube8)">
8面の場合のCSS
#divFaces8
{
-webkit-perspective: 250;
-webkit-perspective-origin: 50% 80px;
}
#cube8 {
position:relative;
margin: -100px auto 0px auto;
height: 133px;
width: 100px;
-webkit-transition: -webkit-transform 0.2s linear;
-webkit-transform-style: preserve-3d;
}
.face8 {
position:absolute;
height: 120px;
width: 100px;
padding: 0px;
background-color: rgba(50, 50, 50, 0.5);
font-size: 20px;
line-height: 1em;
color: #fff;
border: none;
}
#cube8 .f1 {
-webkit-transform: translateZ(120px);
background:gray;
}
#cube8 .f2 {
-webkit-transform: rotateY(45deg) translateZ(120px);
background:green;
}
#cube8 .f3 {
-webkit-transform: rotateY(90deg) translateZ(120px);
---webkit-transform: translateZ(-100px);
background:blue;
}
#cube8 .f4 {
-webkit-transform: rotateY(135deg) translateZ(120px);
background:yellow;
}
#cube8 .f5 {
-webkit-transform: rotateY(180deg) translateZ(120px);
background:red;
}
#cube8 .f6 {
-webkit-transform: rotateY(225deg) translateZ(120px);
background:green;
}
#cube8 .f7 {
-webkit-transform: rotateY(270deg) translateZ(120px);
background:blue;
}
#cube8 .f8 {
-webkit-transform: rotateY(315deg) translateZ(120px);
background:yellow;
}
Manifest+LocalStorageによるオフラインアプリ作成
<body onload="new Faces(divFaces8, cube8)">
8面の場合のCSS
#divFaces8
{
-webkit-perspective: 250;
-webkit-perspective-origin: 50% 80px;
}
#cube8 {
position:relative;
margin: -100px auto 0px auto;
height: 133px;
width: 100px;
-webkit-transition: -webkit-transform 0.2s linear;
-webkit-transform-style: preserve-3d;
}
.face8 {
position:absolute;
height: 120px;
width: 100px;
padding: 0px;
background-color: rgba(50, 50, 50, 0.5);
font-size: 20px;
line-height: 1em;
color: #fff;
border: none;
}
#cube8 .f1 {
-webkit-transform: translateZ(120px);
background:gray;
}
#cube8 .f2 {
-webkit-transform: rotateY(45deg) translateZ(120px);
background:green;
}
#cube8 .f3 {
-webkit-transform: rotateY(90deg) translateZ(120px);
---webkit-transform: translateZ(-100px);
background:blue;
}
#cube8 .f4 {
-webkit-transform: rotateY(135deg) translateZ(120px);
background:yellow;
}
#cube8 .f5 {
-webkit-transform: rotateY(180deg) translateZ(120px);
background:red;
}
#cube8 .f6 {
-webkit-transform: rotateY(225deg) translateZ(120px);
background:green;
}
#cube8 .f7 {
-webkit-transform: rotateY(270deg) translateZ(120px);
background:blue;
}
#cube8 .f8 {
-webkit-transform: rotateY(315deg) translateZ(120px);
background:yellow;
}
Manifest+LocalStorageによるオフラインアプリ作成