【CSS】transformのmatrixって何ですか?

2023/12/15に公開
2

CSSのtransformプロパティにおけるmatrix関数matrix()およびmatrix3d())について書いていきます。

transformプロパティとは

https://developer.mozilla.org/ja/docs/Web/CSS/transform

transform は CSS のプロパティで、与えられた要素を回転、拡大縮小、傾斜、移動することできます。

MDNの説明にあるようにtransformプロパティは要素に平行移動、拡大縮小、回転、傾斜などの座標変換を適用するものです。

これらの座標変換はtranslate関数scale関数rotate関数skew関数などのCSS関数で簡単に実現することができ、CSSに精通している方々には馴染み深いものかもしれません。筆者の経験上においても前述したCSS関数はよく使用します。

一方で本記事の主題であるmatrix関数は使用頻度が比較的低く、筆者自身も案件で使ったことはほぼありません。MDNの説明を読んでみて、正直よくわからないと感じる方もいるのではないでしょうか。

行列(matrix)とは

matrixには、日本語で数学における「行列」という意味があります。

https://ja.wikipedia.org/wiki/行列

数学の線型代数学周辺分野における行列(ぎょうれつ、英: matrix)は、数や記号や式などを縦と横に矩形状に配列したものである。

Wikipediaの行列のページには上記のように書かれています。ざっくりいうと行列は数字を縦と横の矩形状に並べたものです。行列の横方向を、縦方向をと呼びます。

1,2,3,4,5,6の数字を矩形状に並べた行列の図。一行目に1,2,3の数字が、2行目に4,5,6の数字が並んでいる。1,4を青色の円で囲んで列という文字が、1,2,3を赤色の円で囲んで行という文字が割り当てられている

https://developer.mozilla.org/ja/docs/Web/API/WebGL_API/Matrix_math_for_the_web

行列は、空間内のオブジェクトの変換を表すために使用でき、画像を構築したり、ウェブ上でデータを視覚化したりするときに、多くの主要な種類の計算を実行するために使用されます。

行列は空間内のオブジェクトの変換を表現するために使用され、画像の構築やウェブ上でのデータの視覚化など、さまざまな分野で重要な計算を実行するのに使われています。

CSSのmatrix関数では、これらの行列を用いて座標変換の計算を行います。すでに登場したtranslate関数scale関数rotate関数skew関数などの他のCSS座標変換関数を代替することも可能です。

行列を使った座標変換のイメージ

matrix関数を使うことで行列を用いた座標変換が行えることがわかりましたが、どのように行列を使用するのかがイメージしづらいですね。ここでは、その流れを簡単に確認してみましょう。

座標変換と変換行列

座標変換では、座標に何かしらの行列を乗算することで回転、拡大縮小、傾斜、移動のような変換を適用します。この何かしらの行列のことを「変換行列」と呼びます。

たとえば(x、y)の座標に変換行列[?]を乗算して、新たな(x′, y′)の座標を取得したいときは以下のような式で表現できます。

\begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} ? \end{bmatrix} \times \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}

上記の式を見てみるとなにやら不思議な箇所がありますね。xy(およびx′y′)まではわかりますが、1はどこから出てきたのでしょうか。これは同次座標系と呼ばれるものです。

同次座標系

同次座標系はN次元の座標系に対し、次元が1つ多いN+1次元の座標系です。たとえば2次元平面上の座標(x,y)を同次座標系で表すと(x,y,w)、3次元空間上の座標(x,y,z)を同次座標系で表すと(x,y,z,w)となります。また通常このwは1に設定されます(前述の式でも1に設定しています)。

なぜ同次座標系(つまりN+1次元の座標系)を使うのかというと、計算上都合がいいからです。同次座標系を使うことで平行移動、拡大縮小、回転などの座標変換を一度の乗算で計算できるようになります。

座標と行列の乗算

ここで座標と行列の乗算について確認しておきます。

\begin{bmatrix} x' \\ y' \\ w' \end{bmatrix} = \begin{bmatrix} m00 & m01 & m02 \\ m10 & m11 & m12 \\ m20 & m21 & m22 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ w \end{bmatrix}

上記のような座標と変換行列で乗算を行う場合、変換後の座標(x′, y′, w′)はそれぞれ次のように計算されます。

x' = x \times m00 + y \times m01 + w \times m02 \\ y' = x \times m10 + y \times m11 + w \times m12 \\ w' = x \times m20 + y \times m21 + w \times m22

変換行列の「行」と座標の各々の要素を乗算し、それらすべてを足し合わせています。以下のような形の方がわかりやすいかもしれません。

\begin{bmatrix} x' \\ y' \\ w' \end{bmatrix} = \begin{bmatrix} x \times m00 + y \times m01 + w \times m02 \\ x \times m10 + y \times m11 + w \times m12 \\ x \times m20 + y \times m21 + w \times m22 \end{bmatrix}

また計算方法で気付いた方もいるかもしれませんが、行列Aと行列Bで乗算するためには行列Aの列数と行列Bの行数が等しい必要があります。たとえば座標が3次元(x,y,w)であれば、変換行列は3列になります。ちなみに本記事で出てくる変換行列は2次元平面の場合は3×3型、3次元空間の場合は4×4型です。

行列の乗算についてのイメージが湧かない場合は、Matrix Multiplicationというサイトが役立ちます。このサイトでは、行列の乗算がどのように動作するかが視覚的に示されています。

4つの変換行列と座標変換

さて、ここでは平行移動、拡大縮小、回転、傾斜の各座標変換とそれに対応する変換行列について、もう少し詳しく見ていきましょう。これらの座標変換はCSSのtranslate関数scale関数rotate関数skew関数に相当します。

CSSでは3次元空間での座標変換が可能なので、基本的には3次元の座標(x,y,z)を考えていきます。ただし、傾斜(skew関数)に関しては2次元の座標(x,y)を対象としているので、この場合は2次元の座標だけを考慮します。

平行移動

平行移動の変換行列は以下の通りです。

\begin{bmatrix} 1 & 0 & 0 & tx \\ 0 & 1 & 0 & ty \\ 0 & 0 & 1 & tz \\ 0 & 0 & 0 & 1 \end{bmatrix}

txty, tzはそれぞれX軸、Y軸、Z軸における移動量です。したがって、平行移動の計算は次のように表されます。

\begin{bmatrix} x' \\ y' \\ z' \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & tx \\ 0 & 1 & 0 & ty \\ 0 & 0 & 1 & tz \\ 0 & 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x + tx \\ y + ty \\ z + tz \\ 1 \end{bmatrix}

三次元空間で正六面体が平行移動している図

拡大縮小

拡大縮小の変換行列は以下の通りです。

\begin{bmatrix} sx & 0 & 0 & 0 \\ 0 & sy & 0 & 0 \\ 0 & 0 & sz & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

sxsy, szはそれぞれX軸、Y軸、Z軸における拡大縮小率です。したがって、拡大縮小の計算は次のように表されます。

\begin{bmatrix} x' \\ y' \\ z' \\ 1 \end{bmatrix} = \begin{bmatrix} sx & 0 & 0 & 0 \\ 0 & sy & 0 & 0 \\ 0 & 0 & sz & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x \times sx \\ y \times sy \\ z \times sz \\ 1 \end{bmatrix}

三次元空間で正六面体が拡大している図

回転

回転の座標変換はX軸、Y軸、Z軸のそれぞれの軸に対する変換行列が関与します。

X軸回転

X軸回転の変換行列は以下の通りです。

\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos{\theta} & -\sin{\theta} & 0 \\ 0 & \sin{\theta} & \cos{\theta} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

したがって、X軸回転の計算は次のように表されます。

\begin{bmatrix} x' \\ y' \\ z' \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos{\theta} & -\sin{\theta} & 0 \\ 0 & \sin{\theta} & \cos{\theta} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x \\ y \times \cos{\theta} - z \times \sin{\theta} \\ y \times \sin{\theta} + z \times \cos{\theta} \\ 1 \end{bmatrix}

三次元空間で正六面体がX軸に対して回転している図

Y軸回転

Y軸回転の変換行列は以下の通りです。

\begin{bmatrix} \cos{\theta} & 0 & \sin{\theta} & 0 \\ 0 & 1 & 0 & 0 \\ -\sin{\theta} & 0 & \cos{\theta} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

したがって、Y軸回転の計算は次のように表されます。

\begin{bmatrix} x' \\ y' \\ z' \\ 1 \end{bmatrix} = \begin{bmatrix} \cos{\theta} & 0 & \sin{\theta} & 0 \\ 0 & 1 & 0 & 0 \\ -\sin{\theta} & 0 & \cos{\theta} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x \times \cos{\theta} + z \times \sin{\theta} \\ y \\ -x \times \sin{\theta} + z \times \cos{\theta} \\ 1 \end{bmatrix}

三次元空間で正六面体がY軸に対して回転している図

Z軸回転

Z軸回転の変換行列は以下の通りです。

\begin{bmatrix} \cos{\theta} & -\sin{\theta} & 0 & 0 \\ \sin{\theta} & \cos{\theta} & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

したがって、Z軸回転の計算は次のように表されます。

\begin{bmatrix} x' \\ y' \\ z' \\ 1 \end{bmatrix} = \begin{bmatrix} \cos{\theta} & -\sin{\theta} & 0 & 0 \\ \sin{\theta} & \cos{\theta} & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x \times \cos{\theta} - y \times \sin{\theta} \\ x \times \sin{\theta} + y \times \cos{\theta} \\ z \\ 1 \end{bmatrix}

三次元空間で正六面体がZ軸に対して回転している図

傾斜

傾斜の変換行列は以下の通りです。2次元における同次座標系なので3×3型の変換行列になることに注意してください。

\begin{bmatrix} 1 & \tan{\theta}\small x & 0 \\ \tan{\theta}\small y & 1 & 0 \\ 0 & 0& 1 \end{bmatrix}

tanθxおよびtanθyはそれぞれX軸、Y軸に対応しています。したがって、傾斜の計算は次のように表されます。

\begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & \tan{\theta}\small x & 0 \\ \tan{\theta}\small y & 1 & 0 \\ 0 & 0& 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} = \begin{bmatrix} x + y \times \tan{\theta}\small x \\ x \times \tan{\theta}\small y + y \\ 1 \end{bmatrix}

二次元平面で正方形が傾斜している図

CSSのmatrix関数を用いた座標変換

ここまできてようやくCSSのmatrix関数の出番です。座標変換と変換行列について把握したところで、実際にどのようにmatrix関数を使用するのかについて考えていきましょう。

matrix3d()とは

まず3次元の座標(x,y,z)を対象とするmatrix3d()について考えてみましょう。

https://developer.mozilla.org/ja/docs/Web/CSS/transform-function/matrix3d

matrix3d() は CSS の関数で、 4x4 の 3D 同次変換行列を定義します。

MDNでは上記のように書かれています。matrix3d()はこれまでに登場した同次座標系による変換行列を定義すると考えて良さそうです。

実際にmatrix3d()は4×4=16個の値で指定を行います。

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

ただし、その値の順番には気を付ける必要があります。どういうことかというとmatrix3d()列優先で記述するということです。JavaScriptの配列などで行列を考える際は、行優先で考えることも多いと思いますので注意が必要です。

matrix3d()を4×4で記述すると

matrix3d \begin{pmatrix} a1, & b1, & c1, & d1, \\ a2, & b2, & c2, & d2, \\ a3, & b3, & c3, & d3, \\ a4, & b4, & c4, & d4 \end{pmatrix}

となり、これは次の変換行列

\begin{bmatrix} a1 & a2 & a3 & a4 \\ b1 & b2 & b3 & b4 \\ c1 & c2 & c3 & c4 \\ d1 & d2 & d3 & d4 \end{bmatrix}

に対応します。

matrix()とは

次に2次元の座標(x,y)を対象とするmatrix()についても考えてみます。

https://developer.mozilla.org/ja/docs/Web/CSS/transform-function/matrix

matrix() は CSS の関数で、二次元同次変換行列を定義します。

matrix3d()と同様に同次座標系の変換行列を定義すると考えて良さそうです。設定できる値は以下になります。

matrix(a, b, c, d, tx, ty)

注目すべきなのは2次元の同次座標系で考えているのにもかかわらず、設定できる値が6個(3×3=9個ではなく)になるということでしょう。

実はmatrix()の場合、対応する変換行列は以下のようになります。

\begin{bmatrix} a & c & tx \\ b & d & ty \\ 0 & 0 & 1 \end{bmatrix}

3行目の値は常に固定なのでmatrix()では省略されます。

matrix(a, b, c, d, tx, ty) は matrix3d(a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1) の短縮形です。

さらにmatrix()matrix3d()の短縮系であり、matrix3d()に変換することが可能です。3次元として考えれば以下のような変換行列となります。

\begin{bmatrix} a & c & 0 & tx \\ b & d & 0 & ty \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

これを任意の座標(x,y,z)を用いて、実際に計算してみます。

\begin{bmatrix} a & c & 0 & tx \\ b & d & 0 & ty \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x \times a + y \times c + tx \\ x \times b + y \times d + ty \\ z \\ 1 \end{bmatrix}

この計算からわかる通り、Z座標には何の変換も行われていないことが確認できます。3次元座標においても、Z座標に対する変換が行われないのであれば、実質的には2次元座標の考え方を適用できますね。

平行移動とmatrix関数

CSSのmatrix関数による平行移動を実装してみましょう。他の座標変換のことも考えると手で計算するのは少し面倒なので、計算にはJavaScriptを使用します。

以下は平行移動の座標変換を行う実装例です。水色の「Origin」と書かれたボックスと、紫色の「Matrix」と書かれたボックスが存在します。水色のボックスは座標変換適用前、紫色のボックスは座標変換適用後の要素です(ここで指している座標変換の対象は.boxの要素になります)。

平行移動の座標変換を行なっているのは次の箇所になります。

JavaScript
// 平行移動の座標変換
const translate = (tx, ty, tz) => {
  // 平行移動の変換行列
  return [
    1, 0, 0, tx,
    0, 1, 0, ty,
    0, 0, 1, tz,
    0, 0, 0, 1,
  ]
};
const target = document.getElementById("matrix");
target.style.transform = convertArrayToCSSMatrix3d(translate(30, 100, 50));

上記のtranslate(30, 100, 50)はCSSのtranslate3d(30px, 100px,50px)に相当します。

convertArrayToCSSMatrix3d関数

convertArrayToCSSMatrix3dはJavaScriptの配列をCSSのmatrix3dに変換する関数です。

JavaScript
// JavaScriptの配列をCSSのmatrix3d()に変換する
const convertArrayToCSSMatrix3d = (matrix) => {
  if (matrix.length !== 16) {
    throw new Error("Matrix must be 16 elements");
  }
  // 行優先から列優先への変換
  const rearrangedMatrix = [
    matrix[0], matrix[4], matrix[8], matrix[12],
    matrix[1], matrix[5], matrix[9], matrix[13],
    matrix[2], matrix[6], matrix[10], matrix[14],
    matrix[3], matrix[7], matrix[11], matrix[15],
  ]
  return `matrix3d(${rearrangedMatrix.join(",")})`;
};

拡大縮小とmatrix関数

次にCSSのmatrix関数による拡大縮小の座標変換を実装してみましょう。

以下は拡大縮小の座標変換を行う実装例です。

拡大縮小の座標変換を行っているのは次の箇所になります。

JavaScript
// 拡大縮小の座標変換
const scale = (sx, sy, sz) => {
  // 拡大縮小の変換行列
  return [
    sx, 0, 0, 0,
    0, sy, 0, 0,
    0, 0, sz, 0,
    0, 0, 0, 1,
  ]
};

const target = document.getElementById("matrix");
target.style.transform = convertArrayToCSSMatrix3d(scale(1.2, 1.2, 1.2));

上記のscale(1.2, 1.2, 1.2)はCSSのscale3d(1.2, 1.2, 1.2)に相当します。

回転とmatrix関数

次にmatrix関数を用いた回転の座標変換について考えてみます。本記事において回転はX軸、Y軸、Z軸をそれぞれ分けて考えていたので、ここでも別々に実装を行います。

以下は上からX軸回転、Y軸回転、Z軸回転の座標変換を行う実装例です。

回転の座標変換を行なっているのは次の箇所になります。

JavaScript
const sin = Math.sin;
const cos = Math.cos;

// 度(度数法)から、ラジアン(弧度法)に変換
const degreesToRadians = (degrees) => {
  const radian = (Math.PI / 180) * degrees;
  return radian;
};

// X軸回転の座標変換
const rotateX = (theta) => {
  const radian = degreesToRadians(theta);
  // X軸回転の変換行列
  return [
    1, 0, 0, 0,
    0, cos(radian), -1 * sin(radian), 0,
    0, sin(radian), cos(radian), 0,
    0, 0, 0, 1,
  ]
};
// Y軸回転の座標変換
const rotateY = (theta) => {
  const radian = degreesToRadians(theta);
  // Y軸回転の変換行列
  return [
    cos(radian), 0, sin(radian), 0,
    0, 1, 0, 0,
    -1 * sin(radian), 0, cos(radian), 0,
    0, 0, 0, 1,
  ]
};
// Z軸回転の座標変換
const rotateZ = (theta) => {
  const radian = degreesToRadians(theta);
  // Z軸回転の変換行列
  return [
    cos(radian), -1 * sin(radian), 0, 0,
    sin(radian), cos(radian), 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1,
  ]
};

const targetX = document.getElementById("matrixX");
targetX.style.transform = convertArrayToCSSMatrix3d(rotateX(45));

const targetY = document.getElementById("matrixY");
targetY.style.transform = convertArrayToCSSMatrix3d(rotateY(45));

const targetZ = document.getElementById("matrixZ");
targetZ.style.transform = convertArrayToCSSMatrix3d(rotateZ(45));

上記のrotateX(45)rotateY(45)rotateZ(45)はそれぞれCSSのrotateX(45deg)rotateY(45deg)rotateZ(45deg)に相当します。

傾斜とmatrix関数

最後にmatrix関数を用いた傾斜の座標変換について考えてみます。傾斜は2次元の座標を対象としていたので、ここでは2次元の座標で考えます。

以下は傾斜の座標変換を行う実装例です。

傾斜の座標変換を行なっているのは次の箇所になります。

JavaScript
// 傾斜の座標変換
const skew = (thetaX, thetaY) => {
  const tan = Math.tan;
  const radianX = degreesToRadians(thetaX);
  const radianY = degreesToRadians(thetaY);
  // 傾斜の変換行列
  return [
    1, tan(radianX), 0,
    tan(radianY), 1, 0,
    0, 0, 1
  ]
};

const target = document.getElementById("matrix");
target.style.transform = convertArrayToCSSMatrix3d(skew(10, 20));

上記のskew(10, 20)はCSSのskew(10deg, 20deg)に相当します。

おわりに

CSSのtransformプロパティにおけるmatrix関数について書いてみました。本記事で紹介したようにmatrix関数はあまり直感的ではないので、ユースケースとしてはそれほど多くはないかもしれません。
ただ行列による座標変換についてはCSSに限った話ではなく、たとえばCanvasやWebGLにおいても用いいられることがあるので、どこかで役に立つことがあれば幸いです。

参考

https://drafts.csswg.org/css-transforms-2/
https://amzn.asia/d/2jB3088
https://ginpen.com/2018/11/13/understanding-transform-matrix/

Discussion