CSS `Transform Functions` `matrix()` / `matrix3d()` 的手写与解析

各位靓仔靓女,早上好!今天我们来聊聊CSS transform 里的 matrix()matrix3d() 这两个看起来有点吓人的函数。别怕,其实它们就是把我们常用的变换操作,比如平移、旋转、缩放、倾斜,一股脑儿打包成一个矩阵而已。理解了矩阵的本质,你就掌握了操控网页元素的“变形金刚”的钥匙!

开场白:矩阵的魅力

你可能在数学课上见过矩阵,一堆数字排列成方阵。当时你可能觉得它跟你的生活八竿子打不着,但是,在图形学里,矩阵可是个宝贝。它可以表示各种变换,而且最酷的是,多个变换可以合并成一个矩阵,一次性应用到元素上。是不是有点像“乾坤大挪移”?

第一部分:2D 矩阵 matrix()

matrix(a, b, c, d, tx, ty) 是 CSS 中 2D 变换的矩阵表示。这六个参数代表着一个 3×3 的矩阵(虽然实际上你只需要写 6 个数字):

[ a  c  tx ]
[ b  d  ty ]
[ 0  0  1  ]

这个矩阵会作用于元素的每个像素点 (x, y),计算公式如下:

  • x’ = a*x + c*y + tx
  • y’ = b*x + d*y + ty

其中 (x, y) 是原始坐标,(x’, y’) 是变换后的坐标。

  • a: 水平缩放。 当 a=1,scaleX(1);
  • b: 垂直倾斜。
  • c: 水平倾斜。
  • d: 垂直缩放。当 d=1,scaleY(1);
  • tx: 水平平移。
  • ty: 垂直平移。

1.1 常用变换的矩阵表示

我们来看看常用的变换,是如何用 matrix() 表示的:

  • 平移:translate(tx, ty)

    matrix(1, 0, 0, 1, tx, ty)

    • a = 1
    • b = 0
    • c = 0
    • d = 1
    • tx = 水平平移距离
    • ty = 垂直平移距离

    例如:translate(10px, 20px) 等价于 matrix(1, 0, 0, 1, 10, 20)

    .element {
      transform: matrix(1, 0, 0, 1, 10, 20); /* 平移 10px, 20px */
    }
  • 缩放:scale(sx, sy)

    matrix(sx, 0, 0, sy, 0, 0)

    • a = sx (水平缩放比例)
    • b = 0
    • c = 0
    • d = sy (垂直缩放比例)
    • tx = 0
    • ty = 0

    例如:scale(2, 0.5) 等价于 matrix(2, 0, 0, 0.5, 0, 0)

    .element {
      transform: matrix(2, 0, 0, 0.5, 0, 0); /* 水平放大 2 倍,垂直缩小 0.5 倍 */
    }
  • 旋转:rotate(angle)

    matrix(cos(angle), sin(angle), -sin(angle), cos(angle), 0, 0)

    • a = cos(angle)
    • b = sin(angle)
    • c = -sin(angle)
    • d = cos(angle)
    • tx = 0
    • ty = 0

    例如:rotate(45deg) 等价于 matrix(cos(45deg), sin(45deg), -sin(45deg), cos(45deg), 0, 0)matrix(0.707, 0.707, -0.707, 0.707, 0, 0)

    .element {
      transform: matrix(0.707, 0.707, -0.707, 0.707, 0, 0); /* 旋转 45 度 */
    }
  • 倾斜:skew(ax, ay)

    matrix(1, tan(ay), tan(ax), 1, 0, 0)

    • a = 1
    • b = tan(ay) (垂直倾斜角度的tan值)
    • c = tan(ax) (水平倾斜角度的tan值)
    • d = 1
    • tx = 0
    • ty = 0

    例如:skew(30deg, 60deg) 等价于 matrix(1, tan(60deg), tan(30deg), 1, 0, 0)matrix(1, 1.732, 0.577, 1, 0, 0)

    .element {
      transform: matrix(1, 1.732, 0.577, 1, 0, 0); /* 水平倾斜 30 度,垂直倾斜 60 度 */
    }

1.2 手写 matrix() 的乐趣

现在,让我们尝试手写一个稍微复杂一点的 matrix(),比如先旋转 30 度,然后平移 (50px, 100px):

  1. 旋转 30 度: matrix(cos(30deg), sin(30deg), -sin(30deg), cos(30deg), 0, 0)matrix(0.866, 0.5, -0.5, 0.866, 0, 0)
  2. 平移 (50px, 100px): matrix(1, 0, 0, 1, 50, 100)

现在,问题来了,怎么把这两个矩阵合并成一个? 答案是:矩阵乘法!

1.3 矩阵乘法:合并变换

矩阵乘法的规则是:第一个矩阵的每一行,依次乘以第二个矩阵的每一列,对应位置的元素相乘后求和,得到新矩阵对应位置的元素。

假设我们要把矩阵 A 和矩阵 B 相乘,得到矩阵 C:

A = [ a11 a12 a13 ]    B = [ b11 b12 b13 ]    C = [ c11 c12 c13 ]
    [ a21 a22 a23 ]        [ b21 b22 b23 ]        [ c21 c22 c23 ]
    [ a31 a32 a33 ]        [ b31 b32 b33 ]        [ c31 c32 c33 ]

那么:

  • c11 = a11*b11 + a12*b21 + a13*b31
  • c12 = a11*b12 + a12*b22 + a13*b32
  • c13 = a11*b13 + a12*b23 + a13*b33
  • c21 = a21*b11 + a22*b21 + a23*b31
  • c22 = a21*b12 + a22*b22 + a23*b32
  • c23 = a21*b13 + a22*b23 + a23*b33
  • c31 = a31*b11 + a32*b21 + a33*b31
  • c32 = a31*b12 + a32*b22 + a33*b32
  • c33 = a31*b13 + a32*b23 + a33*b33

记住这个规则,我们就可以把旋转和平移的矩阵相乘了。 注意矩阵乘法不满足交换律,即A*B != B*A

先旋转后平移,意味着平移矩阵乘以旋转矩阵。

  • 平移矩阵 T = [1 0 50]
    [0 1 100]
    [0 0 1]
  • 旋转矩阵 R = [0.866 0.5 0]
    [-0.5 0.866 0]
    [0 0 1]

所以 T * R 结果如下:

[ 0.866   0.5    50  ]
[ -0.5    0.866  100 ]
[ 0       0      1   ]

那么 CSS 代码就是:

.element {
  transform: matrix(0.866, -0.5, 0.5, 0.866, 50, 100); /* 先旋转 30 度,然后平移 (50px, 100px) */
}

一个小技巧: 如果你觉得手动计算矩阵乘法太麻烦,可以使用在线矩阵计算器,或者自己写一个 JavaScript 函数来完成。

第二部分:3D 矩阵 matrix3d()

matrix3d() 是 3D 变换的矩阵表示,它使用一个 4×4 的矩阵:

[ a1  a2  a3  a4 ]
[ a5  a6  a7  a8 ]
[ a9  a10 a11 a12 ]
[ a13 a14 a15 a16]

matrix3d(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)

这个矩阵会作用于元素的每个 3D 坐标点 (x, y, z, 1),计算公式如下:

  • x’ = a1*x + a5*y + a9*z + a13
  • y’ = a2*x + a6*y + a10*z + a14
  • z’ = a3*x + a7*y + a11*z + a15
  • w’ = a4*x + a8*y + a12*z + a16

最后的 w’ 用于透视投影,通常情况下我们不用关心它。 最终的坐标需要除以 w’ x’ = x’ / w’; y’ = y’ / w’; z’ = z’ / w’;

2.1 常用 3D 变换的矩阵表示

  • 3D 平移:translate3d(tx, ty, tz)

    [ 1  0  0  0 ]
    [ 0  1  0  0 ]
    [ 0  0  1  0 ]
    [ tx ty tz 1 ]

    matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1)

  • 3D 缩放:scale3d(sx, sy, sz)

    [ sx 0  0  0 ]
    [ 0  sy 0  0 ]
    [ 0  0  sz 0 ]
    [ 0  0  0  1 ]

    matrix3d(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1)

  • 绕 X 轴旋转:rotateX(angle)

    [ 1    0         0          0 ]
    [ 0    cos(a)   -sin(a)    0 ]
    [ 0    sin(a)    cos(a)    0 ]
    [ 0    0         0          1 ]

    matrix3d(1, 0, 0, 0, 0, cos(angle), sin(angle), 0, 0, -sin(angle), cos(angle), 0, 0, 0, 0, 1)

  • 绕 Y 轴旋转:rotateY(angle)

    [ cos(a)  0   sin(a)   0 ]
    [ 0       1   0        0 ]
    [ -sin(a) 0   cos(a)   0 ]
    [ 0       0   0        1 ]

    matrix3d(cos(angle), 0, -sin(angle), 0, 0, 1, 0, 0, sin(angle), 0, cos(angle), 0, 0, 0, 0, 1)

  • 绕 Z 轴旋转:rotateZ(angle)

    [ cos(a)   -sin(a)  0   0 ]
    [ sin(a)    cos(a)  0   0 ]
    [ 0         0       1   0 ]
    [ 0         0       0   1 ]

    matrix3d(cos(angle), sin(angle), 0, 0, -sin(angle), cos(angle), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) (这个和2D的rotate很相似)

2.2 3D 矩阵的组合

和 2D 矩阵一样,3D 矩阵也可以通过矩阵乘法进行组合。 比如我们想先绕 X 轴旋转 45 度,然后沿 Z 轴平移 100px,就需要将对应的矩阵相乘。

2.3 透视 (Perspective)

3D 变换中一个很重要的概念是透视。透视会使远处的物体看起来比近处的物体小,从而产生 3D 的视觉效果。 在 CSS 中,我们可以使用 perspective 属性来设置透视距离。

perspective: distance;

这个属性应该设置在变换元素的父元素上。 distance 表示观察者到 z=0 平面的距离。 数值越小,透视效果越强烈。

有了 perspective,我们才能看到 3D 旋转的效果。

第三部分:实战演练

让我们用一个简单的例子来演示 matrix()matrix3d() 的用法。

3.1 2D 变换:卡片翻转效果

<div class="container">
  <div class="card">
    <div class="front">Front</div>
    <div class="back">Back</div>
  </div>
</div>

<style>
.container {
  width: 200px;
  height: 150px;
  perspective: 800px; /* 设置透视距离 */
}

.card {
  width: 100%;
  height: 100%;
  position: relative;
  transition: transform 1s; /* 添加过渡效果 */
  transform-style: preserve-3d; /* 保持 3D 变换 */
}

.card:hover {
  transform: rotateY(180deg); /* 鼠标悬停时绕 Y 轴旋转 180 度 */
}

.front, .back {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  backface-visibility: hidden; /* 隐藏背面 */
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 24px;
}

.front {
  background-color: lightblue;
}

.back {
  background-color: lightcoral;
  transform: rotateY(180deg); /* 初始时将背面旋转 180 度 */
}
</style>

在这个例子中,我们使用了 rotateY(180deg) 来实现卡片的翻转效果。 我们可以将 rotateY(180deg) 替换成 matrix3d() 的形式:

.card:hover {
  transform: matrix3d(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1); /* 绕 Y 轴旋转 180 度 */
}

.back {
  transform: matrix3d(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1); /* 初始时将背面旋转 180 度 */
}

3.2 3D 变换:立方体

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

<style>
.container {
  width: 200px;
  height: 200px;
  perspective: 800px;
  margin: 100px auto;
}

.cube {
  width: 200px;
  height: 200px;
  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(-30deg) rotateY(30deg); /* 初始旋转角度 */
}

.face {
  width: 200px;
  height: 200px;
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 48px;
  background-color: rgba(0, 0, 255, 0.5);
  border: 1px solid black;
}

.front { transform: translateZ(100px); }
.back  { transform: rotateY(180deg) translateZ(100px); }
.right { transform: rotateY(90deg) translateZ(100px); }
.left  { transform: rotateY(-90deg) translateZ(100px); }
.top   { transform: rotateX(90deg) translateZ(100px); }
.bottom{ transform: rotateX(-90deg) translateZ(100px); }
</style>

在这个例子中,我们创建了一个简单的立方体。 每个面都通过 transform 属性进行平移和旋转,从而形成立方体的形状。

总结

matrix()matrix3d() 虽然看起来有点复杂,但它们是 CSS transform 的底层实现。 掌握了矩阵变换,你就能更加灵活地控制网页元素的变形,创造出各种炫酷的效果。

记住,多练习、多尝试,你也能成为“变形金刚”的 Master! 今天就到这里, 谢谢大家! 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注