JavaScriptからCSSのtransform:matrix3dを扱う覚書

## はじめに

CSSに新しく追加された目玉機能の一つ、transformプロパティではDOMオブジェクトに対し2D/3Dの変形を適用する事ができます。transitionなんかとの合わせ技で、お手軽に見栄え良くできて大変便利な素敵機能なんですが、JavaScripから扱おうとすると結構めんどくさいです。いや、かなりめんどくさいです。なので忘却録をつらつらと。

基本的にマトリックでの変形を扱いますが

* 行列計算を理解している
* 理解してないけど使える！大丈夫！調べれる！(説明がFxxkinでも)めげない！ 

って方を対象にします。

## おさらい

transformプロパティに渡す値は各種transform関数を使用します。transform関数は各種変形用途の関数とマトリックス変形用関数が用意されています。

各種変形用途

* 回転: rotate()系
* 拡大/縮小: scale()系
* 移動: translate()系
* 傾斜: skew()系
* 遠近効果: perspective()

マトリックス変形用

* 2D変形: matrix()
* 3D変形: matrix3d()

なお引数など詳細な解説はリファレンスをどうぞ。

## JavaScriptからtransformを適用する

適用は簡単です。他のスタイルと同じように要素のstyleプロパティに指定するだけ。
  ` elm.style.transform = "transform関数" `

でも、まだベンダープレフィックスが必要なのでブラウザによって下記のように指定する。
Firefix
  ` elm.style.MozTransform = "transform関数" `
Chrome/Safari
  ` elm.style.webkitTransform = "transform関数" `
  
IEやOperaはしらんですが多分にたような雰囲気。

firefoxで下記のように記述すればx軸に30度傾かせることができます。

```js
// id = elmの要素に適用する。
var elm = document.getElementById('elm')
elm.style.MozTransform = "rotateX(30deg)"
    
```

## 更に傾きたい場合
既にtransformが効いてる要素に対して更に15度傾けたいなんてのはままある話だと思います。そんな時、なにも考えずにrotateX(15deg)をelm.style.transformに倍プッシュすると30度は45度にならず15度になってしまいます。変形はあくまで基準点に対してなので現在値を考慮する必要があります。

## rotate()だと思ったのにmatrix()だったでござる
そんなわけなので倍プッシュするために現行値を取得します。

####現在適用中のtransformプロパティの値を取得

基本的にelm.style.transformを参照すれば良い。

```js
var matrix = elm.style.MozTransform;
```

ただ、rotate()やtranslatr()を指定していたとしても、内部で変換されるようで、返される値はmatrix関数の形式となります。正直めんどくさい。

```js
// 2Dの場合
console.log(matrix)  // ex) matrix(1,0,0,0,1,0,0,0,1)が表示される。

    /* matrixの形式
       "matrix(a,b,c,d,e,f,g,h,i)"でそれぞれの引数は
       ↓の3次元行列を表す
       
       | a, b, c |
       | d, e, f |
       | g, h, i |
         
    */
    
// 3Dの場合
console.log(matrix)  // ex) matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)が表示される。

    /* matrixの形式
       "matrix3d(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)"でそれぞれの引数は
       ↓の4次元行列を表す
       
       | a, b, c, d |
       | e, f, g, h |
       | i, j, k, l |
       | m, n, o, p |
         
    */
```

### めんどいので扱いやすいように配列化する
2D/3Dのmatrix関数形式から値を配列に格納する。

めんどいので関数定義する。

```js
//3d版
//正規表現で抽出して配列に入れるだけの関数。

function matrix3dToArray(matrix3d){
	var re = /matrix3d\((.*)\)/;
	var vals = matrix3d.match(re)[1].replace(/ /g, "").split(",");
	vals = vals.map(Number)
	
	var matrix = [new Array(4),new Array(4),new Array(4),new Array(4)];
	
	matrix[0][0] = vals[0]; 
	matrix[0][1] = vals[1]; 
	matrix[0][2] = vals[2]; 
	matrix[0][3] = vals[3]; 
	matrix[1][0] = vals[4]; 
	matrix[1][1] = vals[5]; 
	matrix[1][2] = vals[6]; 
	matrix[1][3] = vals[7]; 
	matrix[2][0] = vals[8]; 
	matrix[2][1] = vals[9]; 
	matrix[2][2] = vals[10]; 
	matrix[2][3] = vals[11]; 
	matrix[3][0] = vals[12]; 
	matrix[3][1] = vals[13]; 
	matrix[3][2] = vals[14]; 
	matrix[3][3] = vals[15];
	
	return matrix; 
}
```

```js
// 2d版
// 3x3の行列を4x4に変換してるだけで基本は3d版と一緒
function matrixToArray(matrix3d){
	var re = /matrix\((.*)\)/;
	var vals = matrix3d.match(re)[1].replace(/ /g, "").split(",");
	vals = vals.map(Number)
	
	var matrix = [new Array(4),new Array(4),new Array(4),new Array(4)];
	
	matrix[0][0] = vals[0]; 
	matrix[0][1] = vals[1]; 
	matrix[0][2] = 0; 
	matrix[0][3] = 0; 
	matrix[1][0] = vals[2]; 
	matrix[1][1] = vals[3]; 
	matrix[1][2] = 0; 
	matrix[1][3] = 0; 
	matrix[2][0] = 0; 
	matrix[2][1] = 0; 
	matrix[2][2] = 1; 
	matrix[2][3] = 0; 
	matrix[3][0] = vals[4]; 
	matrix[3][1] = vals[5]; 
	matrix[3][2] = 0; 
	matrix[3][3] = 1;
	
	return matrix; 
}
```

実行してみる。

```js
// matrix()形式の場合
matrixToArray(elm.style.MozTransform);

// matrix3d()形式の場合
matrix3dToArray(elm.style.MozTransform);

/* 
    どちらも4x4の配列を返します。
    [
        [a, b, c, d],
        [e, f, g, h],
        [i, j, k, l],
        [m, n, o, p]
    ]
*/

```

*transformプロパティが適用されていないエレメントは値が""なので注意。
その場合は[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]として扱うようにする。

**transform-styleプロパティにpreserve-3dが指定されてないエレメントは、z軸への変化量が0になるとmatrix3d()でなくmatrix()形式が値に設定されるので注意。preserve-3dを設定していなくても3D変形は可能。z軸への変化がある間はmatrix3d()で設定される。ブラウザによるかも…

## 現行値を考慮しながら変形さす
現行値を4x4の配列として取得できたので、しかるべき配列との行列積を求めれば未来の姿が見えます。
行列積の計算には数値計算ライブラリのNumeric.jsのdot()関数などを使いましょう。

しかるべき配列の求め方については解説できません。めんどい。

例としてX軸への角運動を求めます。

```js
//度/ラジアン変換
function deg2rad(deg){
	var rad = deg * Math.PI / 180;
	return rad;
}

//角運動 X軸
function rotateX(rx){
    var rad = deg2rad(rx);
    var matrix = [
        [1,0,0,0],
        [0,Math.cos(rad),Math.sin(rad),0],
        [0,-Math.sin(rad),Math.cos(rad),0],
        [0,0,0,1]
    ]    
    return matrix;
}

```

この関数を実行すれば移動量を4x4の配列で表現できます。

```js
// x軸に30度傾ける
var matrixRotateX = rotateX(30);  // [[...],[...],[...],[...]]が入ってる
```

行列積を算出。

```js
// 現行値の取得
var matrix = matrix3dToArray(elm.style.MozTransform);

// 現行値との行列積を求める。
var newMatrix = numeric.dot(matrix, matrixRotateX);
// 変形後の[[...],[...],[...],[...]]が入ってる

```

あとはmatrix3d()の形式に戻してstyleに適用するだけ。

```js

//matrixをtransform3d値に変換
function matrixToMatrix3d(matrix){
    var vals = [];
 
    // 有効桁数をそろえる
    vals[0] = matrix[0][0].toFixed(20);
    vals[1] = matrix[0][1].toFixed(20);
    vals[2] = matrix[0][2].toFixed(20);
    vals[3] = matrix[0][3].toFixed(20);
    vals[4] = matrix[1][0].toFixed(20);
    vals[5] = matrix[1][1].toFixed(20);
    vals[6] = matrix[1][2].toFixed(20);
    vals[7] = matrix[1][3].toFixed(20);
    vals[8] = matrix[2][0].toFixed(20);
    vals[9] = matrix[2][1].toFixed(20);
    vals[10] = matrix[2][2].toFixed(20);
    vals[11] = matrix[2][3].toFixed(20);
    vals[12] = matrix[3][0].toFixed(20);
    vals[13] = matrix[3][1].toFixed(20);
    vals[14] = matrix[3][2].toFixed(20);
    vals[15] = matrix[3][3].toFixed(20);

    //地道に文字列化
    // fofEachしろとか苦情はうけつけ、ごめんなさい。
    var matrix3d = "matrix3d(" + 
        vals[0] + "," +
	vals[1] + "," +
	vals[2] + "," +
	vals[3] + "," +
	vals[4] + "," +
	vals[5] + "," +
	vals[6] + "," +
	vals[7] + "," +
	vals[8] + "," +
	vals[9] + "," +
	vals[10] + "," +
	vals[11] + "," +
	vals[12] + "," +
	vals[13] + "," +
	vals[14] + "," +
	vals[15] + ")";
    return matrix3d; 
}

```

変換してstyleに適用。ヤッター。

```js
elm.style.MozTransform = matrixToMatrix3d(newMatrix);
```

と、まぁこんな感じになります。

## おわりに
てさぐりまさぐり駆け足で書きなぐってみましたが、正直よくわかってない部分も多いので、そこ違うよとかあればなるべく優しく諭していただけると助かります。

本文中で使用したコードとかまとめてGistにおいてあるので、よければご参考程度にばどうぞ。
x軸以外のrotateとかtranslateも入ってます。
[gist](https://gist.github.com/shogito/8436825)

ついでにデモ
[demo(動作環境: 新しめのFirefox/Chrome/Safari)](https://dl.dropboxusercontent.com/u/257313006/bingo_demo/3DWrap.html)