身为一个合格切图仔,CSS3 的 transfrom
那是熟的不能再熟,什么平移、缩放、旋转更是信手捏来,完全没有难度。不过碰到 transform: matrix()
这个样式,立刻脑袋一片空白,这个 matrix()
CSS 函数接收高达 6 个参数!完全不知道它是用来干啥的,去看官方文档也完全没说,一条下面这个简单的样式。
transform: matrix(1.2, 0.2, -1, 0.9, 0, 20);
却可以实现下面这个效果。
看上面代码 6 个参数是传满了,但是每个参数表示什么含义?完全一脸懵逼。
这篇文章将详细讲解 transfrom
2D 变换背后的原理,相信看完这篇文章,你可以从脑袋一片空白进来到头皮发麻的出去。
起步
在详细聊到 matrix()
之前,首先来看看那些简单的平移、缩放、旋转变换是如何实现的,这里不使用现成的方法,完全自己来实现!
我们知道一个图形是由一个个点组成的,例如矩形是由 4 个顶点组成,三角形是由 3 个顶点组成,就连圆形也是由一个个点组成,只是点的数量非常多。那么我们对这些图形做 2D 变换其实就是对组成相应图形的点做变换。
为了简单,本篇使用一个 100x100
正方形做变换演示,以下代码作为变换的模板,这个代码非常简单,就是把变换过后的点全部画到画布上。
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
函数支持设置倾斜角度,如下所示。
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
函数之前,我们可以先看下下面这幅图。
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
的矩阵来实现,例如下方将缩放变换变成矩阵形式。
同样的错切可以用这个矩阵。
旋转可以用这个矩阵。
需要注意我们使用的是列矢量,如果使用的是横矢量(也就是矢量在矩阵左边),需要将旋转矩阵转置下。
但是对于最简单的平移变换,我们没有办法使用 2x2
矩阵来完成。要实现平移矩阵我们需要提升一个维度,也就是二维升到三维。二维坐标 [x,y]
升级为 [x,y,w]
,w
一般为 1
,这也可以称为齐次坐标。这也我们就可以使用如下矩阵来实现平移变换了。
关于矩阵相关知识和一些操作技巧以后专门写文章讲解~
我们在 2x2
矩阵上加了一行和一列,在新增的一列中包含了平移的信息。如果我们对矢量进行平移变换。这也是为什么 CSS3 的 matrix()
和 canvas 的 transform()
方法参数为什么是 3x3
矩阵。
CSS3 的 matrix()
参数如上图所示,其中 tx
和 ty
就是上面介绍的 X 和 Y 轴的位移量,a
、b
、c
和 d
就是上面计算出来的二维矩阵中的项目,套用上面二维矩阵中的值,就可以利用 matrix()
来实现旋转、缩放、错切等变换。
终于我们通过矩阵实现了所有的变换了。使用矩阵看起来没有上面那些方法直观,但是矩阵有很多优势,例如非常方便就可以是组合变换,逆变换等。
总结
这篇文章介绍了 CSS3 transform 和 canvas 变换方法背后的数学原理,完全自己实现 2D 变换,看到这里相信你应该大概差不多对 CSS3 的 transform
有更深的了解了吧。
参考资料
[1]https://juejin.cn/post/7112770927082864653: https://juejin.cn/post/7112770927082864653