文章首發自本人博客 hcysun.me。javascript
前不久開源了一個插件化移動端運動效果庫 finger-mover,說到運動效果,不得不提到CSS3的 transform
,也就是變換。這篇文章歸納了在實現 finger-mover 時對 transform
的理解與總結。css
注:文中的圖片多數截取自視頻:線性代數的本質,也強烈建議你們系統的觀看這套視頻。另外若是文中有誤請不吝指教。html
文章結構以下:前端
* 矩陣
* 概述
* 向量
* 什麼是向量
* 基向量
* 線性變換
* 如何用數值描述線性變換?
* 回到 CSS 的 transform複製代碼
我不知道你們所理解的矩陣是怎樣的,但我所理解的矩陣是:該陣法免疫法術攻擊且100%反傷對方隨機一個單位(回合制遊戲)。java
以上描述是在小學時代的理解,如今可能有所不一樣,慢慢說......git
矩陣,是線性代數中涉及的內容,線性代數在科學領域有不少應用的場景,以下:github
大部分同窗在大學時期應該都學過一本叫作線性代數的書,若是沒猜錯的話,大家的老師在教學的時候大多都是概念性的灌輸,好比矩陣乘法如何運算,加法如何運算,你們只要記住就ok了,可是大部分同窗都不理解,爲何矩陣的乘法要這樣算?矩陣乘法的意義是什麼?,特別是咱們搞計算機的,若是有作過 2D/3D 變換的同窗必定據說過矩陣,好比在前端的CSS中,使用 transform
作 2D/3D 的變換,其中就應用到了矩陣的知識,這篇文章並非一篇數學性質的文章,因此你們不要看了感受一陣眩暈,這篇文章的目的在於:從矩陣與空間之間的關係講述:爲何矩陣能夠應用在空間操做(變換)。或者用一句大白話:這玩意兒怎麼就能讓div
翻過來,轉過去,扭的他爹都不認識他的。web
先看一段 css 代碼:編程
/* 2D */
transform: matrix(1, 0, 0, 1, 0, 0);
/* 3D */
transform: matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);複製代碼
上面兩行 css 代碼其實什麼變換都不會作,由於那是變換的默認狀態,即沒有變換。可是其中使用到了 matrix
,翻譯成中文叫作:矩陣。若是有深刻研究過 css 的同窗對這兩行代碼也許不陌生,可是大多數人在使用 transform
變換時不多直接使用 matrix
矩陣,除非你不想讓人看懂你在作些什麼鳥變換...,因此更多的時候,咱們會使用相似以下語法:ide
transform: translateX(100px) rotateZ(30deg);複製代碼
如上代碼所示,一目瞭然,要作什麼變換你們一看就知道了。但其實,這只是一個語法糖,其底層依然使用的是 matrix
。
若是想要理解矩陣爲什麼能夠應用到 2D/3D 變換,那麼只從數值水平的角度理解是不夠的,你須要從幾何的角度去理解矩陣,這存在着根本性的差別。而這,也就是本篇文章的真正意義。
不過,這須要咱們先了解一些必要的基本概念,這些概念相當重要,首先就是向量
既然矩陣是線性代數的一部分,那麼就不得不提到 向量,由於向量是線性代數最基礎、最根源的組成部分,因此咱們要先搞清楚,向量是什麼?我說過,這篇文章不會很「數學」,因此你們不要被嚇到。用一句話描述向量是什麼:
向量:空間中的箭頭
這個在你們的印象裏應該很好理解,這個箭頭由兩個因素決定:方向
和 長度
,咱們先把目光侷限在二維空間下,如圖:
上圖中,在一個座標系中畫了四個不一樣長度和方向的箭頭,每一個箭頭從原點發出,他們表明了二維空間中的四個向量,在線性代數中,向量一般以原點做爲起點。
若是你已經理解了「向量是空間中的箭頭」這種觀點,下面咱們再進一步,咱們從新用一句話來描述向量:
向量:是有序的數字列表
假設你們對座標系的概念都有所瞭解,咱們仍是把目光侷限在二維空間,在座標系中任意選取單位長度,這樣咱們就可以使用一個一個的刻度來標刻這個座標系,選取特定的方向爲x/y軸的正方向,那麼不難看出,每個向量,均可以用惟一的一個座標來表示,一樣的,座標系中的每個座標都對應着一個惟一的箭頭(向量),以下圖:
在座標系中,因爲座標一般用來標示一個點,如 P(2, 8)
表示點 P 的座標爲 (2, 8)
,爲了區分點和向量,在表示向量時,咱們一般把座標豎着寫,而後用一對兒中括號來描述,如上圖中的:
在三維空間也是同樣的道理,以下圖,我就不作重複的解釋,惟一不一樣的是,每個向量由 x/y/z 三個數字組成的座標來表示:
對於向量,你只須要知道它是「空間中的箭頭」或者「有序的數字列表」這就足夠了,怎麼樣?不難理解吧,咱們繼續往下看,在座標系中存在一種特殊的向量,咱們稱之爲 基向量。
基向量,也叫單位向量,是單位長度爲1的向量,以下圖中:i帽
和 j帽
就是這個二維座標系的基向量:
對於向量,咱們就先介紹到這裏,這已經足夠了。除了向量,還有一個概念須要你們瞭解,即線性變換。
「變換」本質上是「函數」的一種花哨的叫法,玩編程的都知道函數,與在數學中的概念相似,函數接收輸入的內容,並輸出對應的結果,如圖:
變換也是一樣的道理,只不過接收向量做爲輸入,並輸出變換後的向量:
既然 「變換」 與 「函數」 本質相同,那麼爲何叫變換而不叫函數呢?這實際上就暗示了咱們,你能夠把這個輸入輸出的過程,看作一個向量從初始狀態到最終狀態的一個變化過程,以下圖:
如今,咱們把狀況宏觀一下,目前只討論一個向量的變換,咱們知道,二維空間的一整個平面,能夠看作是由無數個向量組成(或者無數個點組成,由於每個點惟一標識一個向量,因此這裏說平面由無數個向量組成),假如這無數個向量同時作相同的變換,那其實就能夠看作是平面的變換,以下圖:
變換前:
變換後:
不過,並不是全部變換都叫作線性變換,線性變換必需要知足下面兩個條件:
以下變換,就不是一個線性變換,由於直線變成了曲線:
在上一小節中咱們知道,空間的變換也能夠說是向量的變換,而向量在空間中,能夠用一組有序的數字列表來表示(即向量的座標),因此向量變換先後,必然會引發「有序數字列表的變換」,那麼咱們是否能夠用數字去描述變換呢?
以前在向量一節中,咱們瞭解過基向量,單位長度爲1,其實空間中的任意一個向量咱們均可以看作是:基向量變換後的和向量,以下圖:
向量 v 的座標是
,若是咱們把3
和
-2
看作兩個標量,也就是純數字,那麼向量
v 能夠看作是基向量被標量縮放後相加獲得的和向量:
v = 3
i + (-2
j)
瞭解了這些,咱們如今就經過一個例子,來認識一個相當重要的事實,假如咱們有向量 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
的矩陣,描述了一個對空間的線性變換,咱們能夠根據這個變換推斷出任意一點(或者任意向量)變換後的座標。
其實我麼你還能夠換一個角度考慮,咱們就單純的把 2 x 2
矩陣叫作變換,那麼向量與矩陣的乘積,就要能夠看作是該向量應用了這個變換。其實,這就是矩陣向量乘法的幾何意義。
說了一大堆,是時候回到 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
的矩陣確實可以描述二維空間的變換,這也就是矩陣可以操做空間的緣由。在 transform
中,除了縮放(scale
)、旋轉(rotate
) 還有傾斜(skew
),對於傾斜,相似於咱們尋找旋轉後基向量的座標同樣,你只須要根據傾斜所定義的變換規則,找到基向量變換後的座標就能夠了,實際上傾斜對應以下規則:
transform: matrix(1, tan(θy), tan(θx), 1, 0, 0);複製代碼
你們本身拿只筆在紙上畫一畫應該就能搞清楚傾斜在作什麼樣子的變換。
不管 縮放(scale
)、旋轉(rotate
) 仍是傾斜(skew
),他們都不會是原點發生改變,因此使用 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
矩陣:
除了2D變換,還有3D變換,在 transform
中,使用 4 x 4
的矩陣描述3D變換,但實際上,三維空間的線性變換隻須要一個 3 x 3
的矩陣就能夠描述了,那麼爲何搞了一個 4 x 4
矩陣呢?實際上這和咱們在將二維空間的變換使用 3 x 3
矩陣的道理是同樣的,那就是位移。
咱們來看一下3D變換的 matrix
默認值:
transform: matrix(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p);
transform: matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);複製代碼
這十六個數字就是 4 x 4
矩陣的 16 個數值:
若是換成對應數字,是這樣的:
相似於咱們講解 2D 變換同樣,其中由
組成的3 x 3
矩陣用來描述空間的 3D 線性變換,如:
rotateX
rotateY
scaleZ
等等,注意:
rotateZ
是 2D 變換哦。
而 m
n
o
則分別用來描述位移:translateX
translateY
translateZ
。