theme: qklhk-chocolate
- 引言:你有沒好奇過,在一個使用了transform變換的元素上使用window.getComputedStyle(htmlElement)['transform'] 查詢出來的值代表什麼?
- 為什麼硬體加速要使用transform,以及為什麼硬體加速會快?
小科普:關於矩陣的乘法
以兩個二階齊次矩陣相乘為例
[ [ [
a11,a12, * b11,b12, = a11*b11 + a12* b21 , a11*b12 + a12*b22,
a21,a22 b21,b22 a21*b11 + a22* b21 , a21*b12 + a22*b22
] ] ]
由此,可以看到兩個矩陣相乘就是拿第一個的每一行,乘以第二個的每一列,因此兩個矩陣相乘也有個規定就是第一個矩陣的列數(每一行元素的個數),要與第二個矩陣的行數(每一列元素的個數)相等才可以發生乘法運算。
首先回答第一個問題: 由window.getComputedStyle(htmlElement)['transform']查詢出來的值代表一個matrix3d函式的引數,形如
matrix3d(a1, b1, c1, d1, a2, b2, c2, d2, a3, b3, c3, d3, a4, b4, c4, d4)
其中a1 b1 c1 d1 a2 b2 c2 d2 a3 b3 c3 d3,代表了線性變換,a4 b4 c4 d4代表的是位移變換。若空間中點的表示是一個列向量表示,那麼,他的矩陣形式應該是這樣的:
a1 a2 a3 a4
b1 b2 b3 b4
c1 c2 c3 c4
d1 d2 d3 d4
在下邊的例子中我們都假定空間中的點是以列向量形式表示的。使用右手座標系。
(實際這裡也可以寫成該矩陣的轉置形式,在下邊進行乘法運算時都分別轉置下,然後交換兩個矩陣的位置也是可以的,結果是一樣的)
3d變換的初始矩陣如下:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
這是一個單位矩陣,這樣的矩陣滿足矩陣乘法運算的交換律,且和整數中1的作用一樣,乘以任何數還是任何數,因此單位矩陣與其他矩陣的乘法運算不產生任何效果。
translate
translate代表一個位移變換,3d的translate變換矩陣對應的是一個如下的4階齊次矩陣 T :
1 0 0 dx
0 1 0 dy
0 0 1 dz
0 0 0 1
假設空間內某個點D的座標形如:
x
y
z
1
給他施加一個translate矩陣之後就可以得到的結果為 T*D :
1*x + 0*y + 0 * z + dx, x + dx
0*x + 1*y + 0 * z + dy, = y + dy
0*x + 0*y + 1 * z + dz, z + dz
0 + 0 + 0 + 1, 1
以上的變換寫成matrix3d函式的形式就是:
matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dy, dz, 1)
等價於:
translate3d(x,y,z)
。
變換矩陣的前三行可以分別看做一個空間座標系上對某個點要施以某種變換的x,y,z軸上的表示,每一行的前三列代表x,y,z軸上具體要有的變換,比如在位移變換矩陣中第一行代表x軸上該點要做的變換,那麼對應的y,z的值為0。(這是我個人的一點思考。可跳過。。。)
scale
scale表示的是一個縮放變換,縮放的具體數值體現在主對角線上,比如一個1.5倍的縮放矩陣S:
1.5 0 0 0
0 1.5 0 0
0 0 1.5 0
0 0 0 1
給上邊的點D施加一個縮放矩陣得到的結果是 S*D:
1.5*x + 0*y + 0*z + 0 * 1 1.5x
0*x + 1.5*y + 0*z + 0 * 1 = 1.5y
0*x + 0*y + 1.5*z + 0 * 1 1.5z
0*x + 0*y + 0*z + 1 * 1 1
等價於:
matrix3d(1.5, 0, 0, 0, 0, 1.5, 0, 0, 0, 0, 1.5, 0, 0, 0, 0, 1)
可簡寫為:
scale3d(1.5, 1.5, 1.5)
rotate
rotate 代表旋轉
二維平面的rotate
二維平面的rotate代表某個點繞著某個點旋轉,一個二維變換可以使用一個三階齊次矩陣表示,其矩陣的推導可以這麼推導:
- 首先是主對角線上的縮放數值,和第三列的位移值,都為預設值1(代表原始大小)和0(代表原始位置):
1 0 0
0 1 0
0 0 1
- 我們拿如下二維座標系舉例:
將兩個單位向量分別在x y軸上旋轉θ角度,得到的A'便是x軸上旋轉矩陣的值,得到的B' 便是y軸上旋轉矩陣的值,因此2d的旋轉矩陣是這樣的(圖片上的座標系是以行向量表示的,為了統一 我們進行一次轉置):
cos0 -sin0
sin0 cos0
3d空間的rotate
在一個3d空間中,旋轉不再是繞某個點的旋轉,而是繞某個軸的旋轉(x,y,z軸),以繞x軸的旋轉舉例(這個圖有點出入,實際BB'應該與y軸是平行的,AA'與Z軸平行,理解就好,沒辦法繪畫功底實在太差 - -,繞某個軸旋轉就是想象該條軸不動然後另一條與他垂直的軸繞著其旋轉):
因此繞x軸的旋轉矩陣可以寫成這樣:
1 0 0 0
0 cos0 -sin0 0
0 sin0 cos0 0
0 0 0 1
我們可以寫一個rotateX(60deg)
,那麼對應的matrix3d的引數就是:
matrix3d(1, 0, 0, 0, 0, 0.5, 0.866025, 0, 0, -0.866025, 0.5, 0, 0, 0, 0, 1)
同理可得出繞z軸和y軸的旋轉矩陣:
Z:
cos0 -sin0 0 0
sin0 cos0 0 0
0 0 1 0
0 0 0 1
Y:
cos0 0 sin0 0
0 1 0 0
-sin0 0 cos0 0
0 0 0 1
至此,css中的3d變換大體上講完了,然後將以上幾種矩陣經過不同組合相乘就能得到複合變換,值得注意的是 矩陣乘法一般不滿足交換律,所以運算順序還是比較重要的。
那為什麼圖形變換在計算機中一般使用矩陣表示呢?據我現在看到的資料來看,就是方便,使用矩陣運算之後,可以將多種變換統一成矩陣的乘法運算,方便計算機流水線式處理。
最後回答一下第二個問題:
2.為什麼硬體加速要使用transform,以及為什麼硬體加速會快?
第一點是因為css的3d transform屬性使用在一個元素上該元素會被提升成一個單獨的layer,在一個單獨的layer上使用一些變換可以直接跳過瀏覽器渲染的一般流程layout,paint,直接在計算結束之後進行一次composite ,這是快的第一點, 第二點是因為使用transform變換的元素其運算發生在gpu上,而gpu的多核在處理併發運算的時候本身就要比cpu快 基本是這樣。
最後分享一個矩陣的乘法運算,可以驗證一下css transform相關的矩陣運算
// 矩陣相乘
function multipy(a,b){
let r = a.length;
let col = a[0].length;
let result = [];
for( let i = 0; i < r; i++ ){
let row = a[i];
result[i] = [];
for( let j = 0; j < r; j++){
let count = 0;
for( let x = 0;x < col; x++ ){
let item1 = row[x];
let item2 = b[x][j]
count += (item1*item2)
}
result[i].push(count)
}
}
return result;
}
const deg45 = Math.PI/4;
後記
image-preview是一款移動端圖片瀏覽器外掛,在這個分支得提交中 將變換矩陣直接應用到了實際應用中,基本得圖形變換已經全部由變換矩陣實現。