matrix矩阵是什么
CSS3 transform 中的 matrix
Transform属性应用于元素的2D或3D转换。这个属性允许你将元素旋转,缩放,移动,倾斜等。
(translate/rotate/skew/scale 只是一个语法糖,其底层依然使用的是 matrix。)
/* 使用translate/rotate/skew/scale */
.demo{
transform:translate(10px, 20px) scale(1.5, 2);
}
当然 Transform 也提供 matrix来实现
/* matrix */
.demo{
transform: matrix(1.5, 0, 0, 2, 10, 20);
}
上面两段代码配合使用 发现了matrix 与 正常变换的相似之处
matrix与矩阵对应
css3里面可以用矩阵表示2D和3D转换
.demo{
transform:matrix(a,b,c,d,e,f)
}
2D的转换是由一个3*3的矩阵表示的,前两行代表转换的值,分别是 a b c d e f ,要注意是竖着排的,第一行代表的是X轴变化,第二行代表的是Y轴的变化,第三行代表的是Z轴的变化,2D不涉及到Z轴,这里使用 0 0 1
这图就很容易让人懵逼
为啥是用中括号,这只是矩阵的一种表现形式 就如同[ ] 表示数组一样
为啥要学矩阵
实际上在日常开发周 transform 提供的语法糖(translate/rotate/skew/scale)已经满足了变形的基本需求。那为啥还学矩阵。
- 通过学习矩阵了解:这玩意儿怎么就能让div翻过来,转过去,扭的他爹都不认识他的。 // 为什么矩阵可以应用在空间操作(变换)
为了装x为了消耗为数不多的秀发
矩阵的本质
如果想要理解矩阵为何可以应用到 2D/3D 变换,那么只从数值水平的角度理解是不够的,需要从几何的角度去理解矩阵,这存在着根本性的差异。而这,也就是了解矩阵的真正意义。
不过,这需要我们先了解一些必要的基本概念,这些概念至关重要,首先就是向量
向量
什么是向量
既然矩阵是线性代数的一部分,那么就不得不提到 向量,因为向量是线性代数最基础、最根源的组成部分,所以我们要先搞清楚,向量是什么?我说过,这篇文章不会很“数学”,所以大家不要被吓到。用一句话描述向量是什么:
向量:空间中的箭头
这个在大家的印象里应该很好理解,这个箭头由两个因素决定:方向 和 长度,我们先把目光局限在二维空间下,如图:
上图中,在一个坐标系中画了四个不同长度和方向的箭头,每个箭头从原点发出,他们代表了二维空间中的四个向量,在线性代数中,向量通常以原点作为起点。
如果大家已经理解了“向量是空间中的箭头”这种观点,下面我们再进一步,我们重新用一句话来描述向量:
向量:是有序的数字列表
假设大家对坐标系的概念都有所了解,我们还是把目光局限在二维空间,在坐标系中任意选取单位长度,这样我们就能够使用一个一个的刻度来标刻这个坐标系,选取特定的方向为x/y轴的正方向,那么不难看出,每一个向量,都可以用唯一的一个坐标来表示,同样的,坐标系中的每一个坐标都对应着一个唯一的箭头(向量),如下图:
在坐标系中,由于坐标通常用来标示一个点,如 P(-2, 3) 表示点 P 的坐标为 (-2, 3),为了区分点和向量,在表示向量时,我们通常把坐标竖着写,然后用一对儿中括号来描述,如上图中的:
在三维空间也是一样的道理,如下图,我就不做重复的解释,唯一不同的是,每一个向量由 x/y/z 三个数字组成的坐标来表示:
对于向量,你只需要知道它是“空间中的箭头”或者“有序的数字列表”这就足够了,怎么样?不难理解吧,我们继续往下看,在坐标系中存在一种特殊的向量,我们称之为 基向量。
基向量
基向量,也叫单位向量,是单位长度为1的向量,如下图中:i帽 和 j帽 就是这个二维坐标系的基向量:
对于向量,我们就先介绍到这里,这已经足够了。除了向量,还有一个概念需要大家了解,即线性变换。
线性变换
“变换”本质上是“函数”的一种花哨的叫法,对 - 就是大家平常写的函数 function,与在数学中的概念类似,函数接收输入的内容,并输出对应的结果,如图:
变换也是同样的道理,只不过接收向量作为输入,并输出变换后的向量:
instance 向量
function (x:向量):向量 {
return x
}
既然 “变换” 与 “函数” 本质相同,那么为什么叫变换而不叫函数呢?这实际上就暗示了我们,你可以把这个输入输出的过程,看做一个向量从初始状态到最终状态的一个变化过程,如下图:
现在,我们把情况宏观一下,目前只讨论一个向量的变换,我们知道,二维空间的一整个平面,可以看做是由无数个向量组成(或者无数个点组成,因为每一个点唯一标识一个向量,所以这里说平面由无数个向量组成),假如这无数个向量同时做相同的变换,那其实就可以看做是平面的变换,如下图:
变换前:
变换后:
不过,并非所有变换都叫做线性变换,线性变换必须要满足下面两个条件:
- 直线在变换后仍然为直线,不能有所弯曲
- 原点不能移动 如下变换,就不是一个线性变换,因为直线变成了曲线:
如何用数值描述线性变换?
在上面我们知道,空间的变换也可以说是向量的变换,而向量在空间中,可以用一组有序的数字列表来表示(即向量的坐标),所以向量变换前后,必然会引起“有序数字列表的变换”,那么我们是否可以用数字去描述变换呢?
之前在向量一节中,我们了解过基向量,单位长度为1,其实空间中的任意一个向量我们都可以看做是:基向量变换后的和向量,如下图:
向量 v 的坐标是
如果我们把 3 和 -2 看做两个标量 (标量是只有大小,没有方向的量),也就是纯数字.
那么向量 v 可以看做是基向量被标量缩放后相加得到的和向量(和向量是两向量和): v = 3i + (-2j)
了解了这些,我们现在就通过一个例子,来认识一个至关重要的事实,假如我们有向量 v = -1i + 2j,如下图:
此时,基向量 i 的坐标是 (1, 0)【注意:为了方便,这里就用圆括号代表向量的坐标,下同】,基向量 j 的坐标是 (0, 1),假设经过了某些变换之后,基向量 i 的坐标变为 (1, -2),基向量 j 的坐标变为 (3, 0),如下图:
那么变换后的向量 v 依然满足 v = -1i + 2j,如下:
以上例子所描述的事实,实际上是线性变换的性质的推论,该性质可以从几何角度表述为:线性变换后的网格平行且等距。
既然线性变换前后都满足该线性关系:v = -1i + 2j
那么很容易根据变换后 i帽 和 j帽 的坐标推算出变换后 v 的坐标:
也就是 (5, 2),即:
那么我们是否可以认为,给定任意一个向量,其坐标 (x, y),我们可以通过变换后的基向量的坐标推断出该向量变换后的坐标呢?答案是肯定的,假如基向量变换后的坐标 i帽 和 j帽 如下图:
那么任意向量 (x, y) 在经过变换后的坐标计算如下:
这告诉我们另外一个事实,二维空间的线性变换仅由四个数字完全确定,这四个数字就是基向量 i 变换后 i帽 的坐标,以及基向量 j 变换后 j帽 的坐标,如下图:
是不是很酷?只需要四个数字,我们就确定了二维空间的一个变换。通常,我们把这四个数字放到一个 2 x 2 的格子中,我们称之为 2 x 2 矩阵:
现在,当你再看到 2 x 2 矩阵的时候,你的第一几何直观反映应该是:它描述了一个二维空间的变换。
我们把情况一般化,如下图:
我们有一个 2 x 2 的矩阵 [a, c] [b, d],其中 [a, c] 是基向量 i 变换后的坐标,[b, d] 是基向量 j 变换后的坐标,那么根据这个变换,以及线性变换的性质,我们可以推断出任意向量 [x, y] 变换后的坐标:
实际上,这就是数学家之所以这样定义 矩阵的向量乘法 的原因。
到了这里,让我们整理一下思路,首先,对于一个 2 x 2 的矩阵,你的直观几何感受应该是,第一列的两个数是对基向量 i 的变换,第二列的两个数是对基向量 j 的变换,这四个数字组成的 2 x 2 的矩阵,描述了一个对空间的线性变换,我们可以根据这个变换推断出任意一点(或者任意向量)变换后的坐标。
回到 CSS 的 transform
说了一大堆,是时候回到 CSS 的 transform,我们来看一下2D变换下 transform 属性的 matrix 写法:
transform: matrix(a, b, c, d, e, f);
在开始时,我们知道各个参数默认值如下:
transform: matrix(1, 0, 0, 1, 0, 0);
有个问题:说好的 2 x 2 矩阵也就是四个数字就能确定一个二维空间变换,你这里明明有6个数啊,其实,transform 2D变换是一个 3 * 3 的矩阵,为什么是这样?因为:位移(translate),前面我们说过,线性变换要满足其中一个特点:原点不能移动,但是位移却使原点发生了移动,所以 2 x 2 矩阵满足不了需求,只能再加一列,也就是 3 x 3 的矩阵。
把 matrix 中的 a b c d e f 放到一个 3 x 3 的矩阵中应该是这样的:
其实,在没有位移(translate)的情况下,[a, b] [c, d] 四个数字组成的 2 x 2 矩阵是完全可以描述2D变换的,现在我们只看由 [a, b] [c, d] 组成的 2 x 2 矩阵:
我们把 a b c d 四个数字使用默认值替换一下,即:a = 1,b = 0,c = 0,d = 1,如下:
通过之前的介绍,我们在看到这个矩阵的时候,应该知道,第一列的坐标 (1, 0) 应该是基向量 i 变换后的坐标,但是基向量 i 在变换前的坐标就是 (1, 0),也就是说没有任何变换,同理,基向量 j 也没有任何变换,所以说,这就是 a b c d 默认值设定为下面代码所示的值的原因:
transform: matrix(a, b, c, d, e, f);
// a b c d 默认值为 1 0 0 1
transform: matrix(1, 0, 0, 1, e, f);
那么大家想想一下,我们把 a 的值从 1 变为 2 会发生什么?如果把 a 的值从 1 变为 2 那么矩阵如下:
也就是说,基向量 i 的坐标从 (1, 0) 变成了 (2, 0),这是在干什么?是不是基向量 i 被放大为了原来的二倍?举一个通俗的例子:原本单位长度1代表20px,被放大后单位长度1则代表40px。同样的,当我们把 a 的值从 1 变为 0.5 则意味着把基向量 i 缩小为原来的一半。事实上:在 transform: matrix() 中,修改 a 的值,就是在改变 x 轴方向的缩放比例:
transform: matrix(2, 0, 0, 1, 0, 0);
/* 等价于 */
transform: scaleX(2);
相信大家已经知道了,修改 d 的值,就是改变 y 轴的缩放比例:
transform: matrix(1, 0, 0, 4, 0, 0);
/* 等价于 */
transform: scaleY(4);
那么旋转要如何修改 matrix 中的值呢?其实,想要知道如何修改 a b c d 的值,只需要知道,旋转后基向量 i 和 j 的坐标就可以了,将旋转后的坐标对号填入就可以得到变换矩阵,-- 下面,来看如何确定旋转后基向量 i 和 j 的坐标。
在 web 开发中的坐标系和数学中的坐标系在正方向的选取上不太一致,在大家所熟悉的坐标系中,正方向的选取如下:
而在 web 开发中,坐标系的正方向选取是这样的:
假设我们将其顺时针旋转 45 度,如下图:
假设,上图中我们旋转的是单位向量,那么旋转后单位向量 i 的坐标应该是 (cosθ, sinθ),单位向量 j 的坐标应该是 (-sinθ, cosθ),所以如果用矩阵表示的话,应该是这样的:
如果写到 matrix 里,自然就是下面这个样子:
transform: matrix(cosθ, sinθ, -sinθ, cosθ, 0, 0)
所以,如果我们要顺时针旋转 45 度,下面两种写法是等价的:
/*
* Math.cos(Math.PI / 180 * 45) = 0.707106
* Math.sin(Math.PI / 180 * 45) = 0.707106
*/
transform: matrix(0.707106, 0.707106, -0.707106, 0.707106, 0, 0)
/* 等价于 */
transform: rotate(45deg);
通过上面缩放和旋转的例子,我们已经知道了,2 x 2 的矩阵确实能够描述二维空间的变换,这也就是矩阵能够操作空间的原因。
无论 缩放(scale)、旋转(rotate) ,他们都不会是原点发生改变,所以使用 a b c d 四个数字组成的矩阵完全可以描述,但是不要忘了,我们还有一个 位移(translate),这时,就不得不提到 e f 了,e f 分别代表了 x y 方向的位移:
transform: matrix(1, 0, 0, 1, 100, 200)
/* 等价于 */
transform: translateX(100px) translateY(200px);
至此,transform 使用 3 x 3 矩阵:
矩阵怎么用
假设一个问题 创建一个宽高为200px的div ,div 里面有一个红色的点,位置是{x:181px y:50px}
倘若将这个div向右平移20px,旋转37°,x轴缩放1.5倍,y轴缩放2倍
transform:tranlate(10px,20px) rotate(37deg) scale(1.5, 2)
缩放scale(x,y)
缩放对应的是矩阵中的a和d,x轴的缩放比例对应a,y轴的缩放比例对应d。
transform:scale(x,y)
a = x
d = y
所以scale(1.5, 2)对应的矩阵是
matrix:(1.5,0,0,2,0,0)
如果元素没有被缩放,默认a = 1 d =1
平移 translate(10, 20)
平移对应的是矩阵中 e 和 f,平移的x 和 y分别对应 e 和 f
transform:translate(10, 20)
e = 10
f = 20
对应:transform: matrix(a, b, c, d ,10, 20);
结合缩放:transform: matrix(1.5 0, 0, 2, 10, 20)
旋转 rotate(0deg)
旋转影响的是a/b/c/d四个值,分别是什么呢?
transform: rotate(θdeg)
a=cosθ
b=sinθ
c=-sinθ
d=cosθ
如果要计算 30° 的sin值:
首先我们要将 30° 转换为弧度,传递给三角函数计算。用 JS 计算就是下面的样子了。
// 弧度和角度的转换公式:弧度=π/180×角度
const radian = Math.PI / 180 * 30 // 算出弧度
const sin = Math.sin(radian) // 计算 sinθ
const cos = Math.cos(radian) // 计算 cosθ
console.log(sin, cos) // 输出 ≈ 0.5, 0.866
transform: rotate(30deg)
a=0.866
b=0.5
c=-0.5
d=0.866
transform: matrix(0.866, 0.5, -0.5, 0.866, 0, 0);
旋转+缩放+位移怎么办?
如果我们既要旋转又要缩放,我们需要将旋转和缩放和偏移和位移多个矩阵相乘,要按照transform里面rotate/scale/skew/translate所写的顺序相乘。 这里我们先考虑旋转和缩放,需要将旋转的矩阵和缩放的矩阵相乘
实在是用语言解释不清楚如何去乘,用一张图解释吧:
这里我用小写字母代表第一个矩阵中的值,大写字母代表第二个矩阵里的值
将我们的已经得到的矩阵带入到公式
得出:
transform: rotate(30) scale(1.5 2);
转换为 matrix 表示为:
transform: matrix(1.299, 0.75, -1, 1.732, 0, 0);
找到这次转换的矩阵
div 的 transform 值如下
transform: translate(10px, 20px) rotate(37deg) scale(1.5, 2);
## translate(10px, 20px)
// x 平移 10px,y 平移 20px,所以 e=10,f=20。
rotate(37deg)
sin37° ≈ 0.6
cos37° ≈ 0.8
根据 a 对应 cos b,对应 sin,c 对应 -sin,d 对应 cos 的值
得到:
a=0.8,b=0.6,c=-0.6,d=0.8
scale(1.5, 2)
x 轴缩放 1.5,y 轴缩放 2,所以 a=1.5,d=2
结合
transform: translate(10px, 20px) rotate(37deg) scale(1.5, 2);
我们使用 位移矩阵 旋转矩阵 缩放矩阵(根据transform中的变换类型书写的顺序)
可以使用矩阵计算器进行计算
从左往右依次计算
所以最终得到矩阵
matrix(1.2, 0.9, -1.2 1.6, 10, 20)
验证一下
transform: matrix(1.2, 0.9, -1.2 1.6, 10, 20)
和
transform: translate(10px, 20px) rotate(37deg) scale(1.5, 2);
效果是一样的
如何对一个坐标进行矩阵变换 我们已经知道了这个矩阵,如何通过矩阵对一个坐标进行变化,找到这个坐标变化后的位置呢?
我们用之前得出的变换矩阵去乘以这一个坐标组成的3*1(三排一列)矩阵。
上面已经介绍过如何进行矩阵乘法了,这里再温习一遍
上图中左右两个矩阵颜色相同的位置相乘后相加,每一行都进行这样的计算:
得到一个3*1的矩阵,第一行是转换后的 x 值,第二行是转换后的 y 值,第三行是转换后的 z 值(2d不考虑z值)。
前面讲到,矩阵的第一行影响 x,第二行影响 y,也体现在这个地方。
假设我们的坐标是(50, 80),这里还没有针对我们提出的问题上面的点进行计算。
我们把坐标写成矩阵的形式,设置 z 轴是1:
然后进行乘法计算:
通过我们计算出来的矩阵变换得到新的位置(46, 172)
继续刚刚问题 坐标是需要基于一个坐标系存在的,我们需要找到正确的坐标系才能算出准确的坐标。 在 CSS transform 中,有个属性是 transform-origin,来设置变换所基于的点,默认是transform-origin: 50% 50%,基于中间元素的中心点。我们需要以这个点建立坐标系。
在网页中,坐标系是 x 轴向右,y 轴向下。
转换前:
转换后:
根据题目我们知道,这个点相对于绿色div左上角的坐标是(181, 50) 绿色div的宽高为200 基于绿色div中心点建立的坐标系,这个点的坐标是(81, -50)
将坐标代入公式进行计算:
得到坐标约为(167, 13)
再将这个坐标转换成页面坐标系(267,113)
最终我们得到了这个点在经过转换后的坐标
实战代码
https://github.com/whorcare/vue-css-matrix
clone 到本地
yarn run serve