CSS3 transform 和 canvas 背后不为人知的秘密

2022-10-08 13:54:35 浏览数 (1)

身为一个合格切图仔,CSS3 的 transfrom 那是熟的不能再熟,什么平移、缩放、旋转更是信手捏来,完全没有难度。不过碰到 transform: matrix() 这个样式,立刻脑袋一片空白,这个 matrix() CSS 函数接收高达 6 个参数!完全不知道它是用来干啥的,去看官方文档也完全没说,一条下面这个简单的样式。

代码语言:javascript复制
transform: matrix(1.2, 0.2, -1, 0.9, 0, 20);

却可以实现下面这个效果。

看上面代码 6 个参数是传满了,但是每个参数表示什么含义?完全一脸懵逼。

这篇文章将详细讲解 transfrom 2D 变换背后的原理,相信看完这篇文章,你可以从脑袋一片空白进来到头皮发麻的出去。

起步

在详细聊到 matrix() 之前,首先来看看那些简单的平移、缩放、旋转变换是如何实现的,这里不使用现成的方法,完全自己来实现!

我们知道一个图形是由一个个点组成的,例如矩形是由 4 个顶点组成,三角形是由 3 个顶点组成,就连圆形也是由一个个点组成,只是点的数量非常多。那么我们对这些图形做 2D 变换其实就是对组成相应图形的点做变换。

为了简单,本篇使用一个 100x100 正方形做变换演示,以下代码作为变换的模板,这个代码非常简单,就是把变换过后的点全部画到画布上。

代码语言:javascript复制
const canvas = document.createElement('canvas')
canvas.width = canvas.height = 300;
document.body.appendChild(canvas)
const ctx = canvas.getContext('2d')

const transform = `{下面小节的变换函数}`

const shape = [[0,0],[0,100],[100,100],[100,0]] // 正方形的 4 个顶点

ctx.beginPath()
shape.forEach(p => ctx.lineTo(...transform(p))) 
// "transform" 函数为下方小节指定的变换函数
// 下面的 "transform" 函数返回新的坐标位置
ctx.closePath()
ctx.fillStyle='rgba(0,255,255,1)'
ctx.fill()

上面代码中的 transform 是一个函数,它完全由我们自己来实现。shape 是我们要去变换的图形,现在它是一个 4 个点组成的正方形,你完全可以将它替换成任意图形。

平移

首先第一个,从简单的平移变换开始。在 CSS3 我们可以用 transform: translateX(100px) 将图形平移 100 像素,在 canvas 中,会使用渲染 2d 上下文的 transform() 方法来实现平移变换。

对图形做平移变换,其实就是增加或减少相应图形 X 和 Y 坐标,如下所示。

代码语言:javascript复制
function translate([x, y], dx = 0, dy = 0) {
  return [x   dx, y   dy]
}

缩放

第二是缩放操作,在 CSS3 我们会使用 transform: scale(1) 来做缩放操作,在 canvas 中是使用 scale() 方法来实现缩放操作。

对一个图形做缩放操作,只需要将它的坐标乘上一个缩放因子就行了,比如这个正方形缩放 0.5 倍,它的新坐标就为 [[0,0],[0,50],[50,50],[50,0]]

我们可以使用两个缩放因子来分别缩放 X 轴和 Y 轴。

代码语言:javascript复制
function scale([x, y], xs = 1, ys = xs) {
  return [x * xs, y * ys]
}

另外还有一个镜像变换,其实就是将图形缩放 -1 倍。

错切

第三个是错切变换,错切变换可以用来倾斜物体,它会不均匀拉伸物体,物体错切后它的面积和体积不会发生变化。在 CSS3 中可以使用 transform: skew() 来实现错切变换,canvas 中好像没有错切变换的方法。

执行错切的一个思路是将一个坐标的倍数加到另一个坐标上,比如下面将 1 倍的 Y 坐标加到 X 坐标上。

代码语言:javascript复制
function skew([x, y]) {
  return [x   1 * y, y]
}

效果如下。

不过 CSS 中的 skew 函数支持设置倾斜角度,如下所示。

代码语言:javascript复制
div {
  transform: skew(30deg);
}

我们可以稍微改造下,来支持度数参数。

代码语言:javascript复制
function skew([x, y], sx = 0, sy = 0) {
  const rad = r => r * Math.PI / 180
  return [x   Math.tan(rad(sx)) * y, y   Math.tan(rad(sy)) * x]
}

我们首先把角度变为弧度,然后利用 tan 函数来计算具体倾斜量,因为 tan 等于对边比临边,再乘上临边就是需要的倾斜量了。

旋转

最后一个变换,我们来实现最难的旋转变换。在 CSS3 中可以使用 transfrom: rotate() 实现,在 canvas 中可以使用 rotate() 方法来实现。

在实现 rotate 函数之前,我们可以先看下下面这幅图。

代码语言:javascript复制
function rotate([x, y], deg = 0) {
  const rad = deg * Math.PI / 180
  return [
     x * Math.cos(rad) - y * Math.sin(rad), 
     x * Math.sin(rad)   y * Math.cos(rad)
  ]
}

效果如下,其中有一部分是超出画布被截掉了。

矩阵

我们上面实现的这些变换,其实都可以使用一个矩阵来实现。在 CSS3 中就是使用 transform: matrix() 方法,canvas 中的 transform() 方法,它们的参数都是传入一个 3x3 的矩阵来实现变换。

这里我们使用 2x2 的矩阵来实现,例如下方将缩放变换变成矩阵形式。

begin{aligned} scaledPosition&=begin{bmatrix} sx & 0 \ 0 & sy end{bmatrix} begin{bmatrix} x \ y end{bmatrix} \ &=begin{bmatrix} sx * x \ sy * y end{bmatrix} end{aligned}

同样的错切可以用这个矩阵。

skewMatrix=begin{bmatrix} 1 & tan(theta) \ tan(theta) & 1 end{bmatrix}

旋转可以用这个矩阵。

rotationMatrix=begin{bmatrix} cos(theta) & -sin(theta) \ sin(theta) & cos(theta) end{bmatrix}

需要注意我们使用的是列矢量,如果使用的是横矢量(也就是矢量在矩阵左边),需要将旋转矩阵转置下。

begin{bmatrix} x & y end{bmatrix}begin{bmatrix} cos(theta) & sin(theta) \ -sin(theta) & cos(theta) end{bmatrix}

但是对于最简单的平移变换,我们没有办法使用 2x2 矩阵来完成。要实现平移矩阵我们需要提升一个维度,也就是二维升到三维。二维坐标 [x,y] 升级为 [x,y,w]w 一般为 1,这也可以称为齐次坐标。这也我们就可以使用如下矩阵来实现平移变换了。

begin{aligned} translated&=begin{bmatrix} 1 & 0 & dx \ 0 & 1 & dy \ 0 & 0 & 1 end{bmatrix} begin{bmatrix} x \ y \ 1 end{bmatrix} \ &=begin{bmatrix} x dx \ y dy \ 1 end{bmatrix} end{aligned}

关于矩阵相关知识和一些操作技巧以后专门写文章讲解~

我们在 2x2 矩阵上加了一行和一列,在新增的一列中包含了平移的信息。如果我们对矢量进行平移变换。这也是为什么 CSS3 的 matrix() 和 canvas 的 transform() 方法参数为什么是 3x3 矩阵。

CSS3 的 matrix() 参数如上图所示,其中 txty 就是上面介绍的 X 和 Y 轴的位移量,abcd 就是上面计算出来的二维矩阵中的项目,套用上面二维矩阵中的值,就可以利用 matrix() 来实现旋转、缩放、错切等变换。

终于我们通过矩阵实现了所有的变换了。使用矩阵看起来没有上面那些方法直观,但是矩阵有很多优势,例如非常方便就可以是组合变换,逆变换等。

总结

这篇文章介绍了 CSS3 transform 和 canvas 变换方法背后的数学原理,完全自己实现 2D 变换,看到这里相信你应该大概差不多对 CSS3 的 transform 有更深的了解了吧。

参考资料

[1]https://juejin.cn/post/7112770927082864653: https://juejin.cn/post/7112770927082864653

0 人点赞