前言

在平时项目中绘制箭头算是很常见的一个需求,在如今的icon大行其道的时候,直接用代码写箭头样式的估计已经很少了。不过这种基础知识我们还是要好好掌握的。

1、箭头实现的老办法

老办法的实现方式如下:

老办法是使用rotate来实现上下左右的箭头的,那么今天我们不打算使用这个,而是使用CSS3更加高大上的一个属性matrix

2、Show Code Show code

代码demo如下:(Jsfiddle上)

3、Transform的matrix属性

想要搞懂这个matrix属性,那么不得不提到大学学过的线性代数,那么顺便我们来预习预习线性代数如何?

丫丫,说过头了,其实不用拿本线性代数书出来,只需要回忆一下矩阵就行了。

3.1、矩阵

matrix的中文也就是矩阵,根据wiki)的解释,矩阵是一个按照行和列来排列的矩形阵列,里面的值可以是数字、符号或者表达式。比如下图:

[123456789] \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix}

矩阵在数学科学上有很多的应用。比如物理学家用它们来研究量子力学,通信学上用来做天线与层流的计算,而今天我们要说的是利用矩阵来做CSS上的线性变换。我们不会对线性代数有太深的挖掘,不过还是需要你回顾一下以前线性代数的一些矩阵基础。而这里我们需要知道的基础便是矩阵乘法

3.1.1、矩阵乘法

那么矩阵乘法的运算规则是什么呢? 来来,再次引用维基百科中专业的定义:

矩阵相乘最重要的方法是一般矩阵乘积。它只有在第一个矩阵的列数(column)和第二个矩阵的行数(row)相同时才有定义。一般单指矩阵乘积时,指的便是一般矩阵乘积。若A为 m X n矩阵,B为 n X p矩阵,则他们的乘积AB(有时记做A · B)会是一个 m X p矩阵。其乘积矩阵的元素如下面式子得出:
(AB)ij=r=1nairbrj=ai1b1j+ai2b2j+...+ainbnj (AB)_{ij} = \sum_{r=1}^na^{ir}b^{rj}=a_{i1}b_{1j}+a_{i2}b_{2j}+...+a_{in}b_{nj}

以向量[123]\begin{bmatrix}1 \\ 2 \\ 3\end{bmatrix}和向量[1 2 3]的乘法为例子,其结果应为:

[123246369] \begin{bmatrix} 1 & 2 & 3 \\ 2 & 4 & 6 \\ 3 & 6 & 9 \end{bmatrix}

3.2、Transform(以2D为例子)的四大属性

我们平时一直使用translate,scale,rotate,skewX/Y来实现各种动画变换:平移、缩放、旋转、倾斜。我们大致回忆一下这四个属性的用法:

  1. translate(offsetX, offsetY): 平移,可以在X轴或者Y轴上平移,平移前后的坐标变换关系是(newX, newY) = (oldX + offsetX, oldY+offsetY), 其期望的矩阵应该是这种类型的:[10offsetX01offsetY][oldXoldY1]=[offsetX+oldXoffsetY+oldY] \begin{bmatrix} 1 & 0 & offsetX \\ 0 & 1 & offsetY \end{bmatrix} \begin{bmatrix} oldX \\ oldY \\ 1 \end{bmatrix} = \begin{bmatrix} offsetX + oldX \\ offsetY + oldY \end{bmatrix}
  2. scale(scaleX, scaleY): 缩放,可以在X轴或者Y轴上缩放,缩放前后的坐标变换关系是 (newX, newY) = (oldX scaleX, oldY scaleY),其期望的矩阵应该是这种类型:[scaleX000scaleY0][oldXoldY1]=[scaleXoldXscaleYoldY] \begin{bmatrix} scaleX & 0 & 0 \\ 0 & scaleY & 0 \end{bmatrix} \begin{bmatrix} oldX \\ oldY \\ 1 \end{bmatrix} = \begin{bmatrix} scaleX * oldX \\ scaleY * oldY \end{bmatrix}
  3. rotate(θ\theta): 旋转,围绕着transform-origin配置的中心点旋转,旋转前后的坐标变换关系是(newX, newY) = (oldX cosθcos\theta - oldY sinθsin\theta, oldX sinθsin\theta + oldY cosθcos\theta),其期望的矩阵应该是这种类型的:[cosθsinθ0sinθcosθ0][oldXoldY1]=[oldXcosθoldYsinθoldXsinθ+oldYcosθ] \begin{bmatrix} cos\theta & -sin\theta & 0 \\ sin\theta & cos\theta & 0 \end{bmatrix} \begin{bmatrix} oldX \\ oldY \\ 1 \end{bmatrix} = \begin{bmatrix} oldX * cos\theta - oldY * sin\theta \\ oldX * sin\theta + oldY * cos\theta \end{bmatrix} 旋转的这个公式示意图会在后续的更新中添加上,我将使用SVG来描述这种转换关系,让大家知其所以然
  4. skew(θ\theta, α\alpha): 倾斜,围绕着transform-origin配置的中心点倾斜,倾斜前后的坐标变换关系是(newX, newY) = (oldX cosθcos\theta - oldY sinθsin\theta, oldX sinθsin\theta + oldY cosθcos\theta),其期望的矩阵应该是这种类型的:[1tanθ0tanα10][oldXoldY1]=[oldX+oldYtanθoldXtanα+oldY] \begin{bmatrix} 1 & tan\theta & 0 \\ tan\alpha & 1 & 0 \end{bmatrix} \begin{bmatrix} oldX \\ oldY \\ 1 \end{bmatrix} = \begin{bmatrix} oldX + oldY * tan\theta \\ oldX * tan\alpha + oldY \end{bmatrix}

3.3、matrix上场

前面我们在说明transform的四大属性的时候,一直在强调可以使用相关的矩阵来表示,从数学上来说,所有的变化都可以使用下列3*3的变换矩阵来表示

[acebdf001] \begin{bmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{bmatrix}

因此之前提到的相关矩阵都可以算成是该通用矩阵的子集,但机智的童鞋们肯定会问到:为什么刚才明明使用2 X 3的矩阵就可以计算出来,为什么通用矩阵就变成了3 X 3的呢?这不是多此一举呢?如果是3 X 3的矩阵,为什么第三行就必须是[ 0 0 1]呢?

这个问题我们先不马上说原因,大家往后继续阅读文章自然会找到答案

看到该通用矩阵后我们知道只用了6个值,对应到CSS3的matrix属性便是:transform: matrix(a, b, c, d, e, f)。然后再结合上一小节的各种变换公式,我们是不是可以总结出什么?

如下表:

通过这张表你可以清晰地看出四大属性与matrix属性之间的关联,那么问题来了,如果我是多种变换组合在一起呢?那么这个时候就可以使用矩阵连续相乘得到结果,于是乎,在这个点上我们就可以回答刚才的问题了,因为根据矩阵的乘法计算规则,两个矩阵相乘必须满足只有在第一个矩阵的列数(column)和第二个矩阵的行数(row)相同时才有定义,所以你不可能将一个2 X 3的矩阵乘以一个2 X 3的矩阵,因此我们需要扩展成3 X 3的矩阵,然后为了保证第三行的数值不会影响到我们的结果,其值只能是[0 0 1]

举个栗子:

.demo{
   transform-origin: 0px 0px;
   transform: rotate(45deg) translate(100px, 0) scale(2);
}

那么其矩阵的表达式应该是:

[cos45sin450sin45cos450001][00100000001][200010001] \begin{bmatrix} cos45^\circ & -sin45^\circ & 0 \\ sin45^\circ & cos45^\circ & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 0 & 0 & 100 \\ 0 & 0 & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 2 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}

然后再乘以对应的点,我们只需要提供上述矩阵相乘得到的结果赋值给matrix属性,其他的点积计算交由浏览器来执行,所以其等效的配置是:

.demo{
   transform-origin: 0px 0px;
   transform: matrix(1.41421, 1.41421, -1.41421, 1.41421, 70.7107, 70.7107);
}

这里写了一个小demo,可以方便地进行二者转换,狠狠戳demo

3.4、matrix3D

学习了2D的矩阵变换,那么3D的变换也就不在话下了,其语法是:

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

这里就不再细说3D的矩阵变换了。

4、写在最后

如此一来,想必对CSS3的整个transfrom属性有了质的理解了吧?通过写这篇文章,我自己都学到很多,不仅仅是专业知识,还有其他一些基本技能,比如:

  1. 为了让我的博客支持数学公式,于是我就在我的markdown中嵌入了kaTex的插件,手动改了marked.js的源码,
  2. 为了写出数学公式,还需要学习LaTex的语法(后续写一篇介绍LaTex语法的文章,敬请期待)
  3. 为了展示旋转的原理,使用SVG画图,重新温习了一下几何代数。

因此一篇文章的完成会给你带来的知识会是很多很多,在此鼓励童鞋们多多总结,深入挖掘,共同学习。

参考

  1. https://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
  2. The MathML markup used to display the mathematical equations in this article were rendered by MathJax, an excellent open-source JavaScript library
  3. https://www.w3.org/TR/css-transforms-1/#mathematical-description
  4. //www.useragentman.com/blog/2011/01/07/css3-matrix-transform-for-the-mathematically-challenged/
  5. https://zh.wikipedia.org/wiki/%E7%9F%A9%E9%99%A3%E4%B9%98%E6%B3%95