大家好,我是前端西瓜哥。
今天我們來研究一下 Figma 是如何表示圖形的,這里以矩形為切入點(diǎn)進(jìn)行研究。
明白最簡單的矩形的表示后,研究其他的圖形就可以舉一反三。
如果讓我設(shè)計(jì)一個(gè)矩形圖形的物理屬性,我會怎么設(shè)計(jì)?
我張口就來:x、y、width、height、rotation。
對一些簡單的圖形編輯操作,這些屬性基本上是夠用的,比如白板工具,如果你不考慮或者不希望圖形可以翻轉(zhuǎn)(flip) 的話。
Figma 需要考慮翻轉(zhuǎn)的情況的,此外還有斜切的情況。
翻轉(zhuǎn)的場景:
還有斜切的場景,在選中多個(gè)圖形然后縮放時(shí)有發(fā)生。
這些表達(dá)光靠上面的幾個(gè)屬性是不夠的,我們看看 Figma為了表達(dá)這些效果,是怎么去設(shè)計(jì)矩形的。
與物理信息相關(guān)的屬性如下:
{ "size": { "x": 100, "y": 100 }, "transform": { "m00": 1, "m01": 3, "m02": 5, "m10": 2, "m11": 4, "m12": 6 }, // 省略其他無關(guān)屬性}
沒有位置屬性,這個(gè)屬性默認(rèn)是 (0, 0),實(shí)際它轉(zhuǎn)移到 transform 的矩陣的位移子矩陣上了。
size 表示寬高,但屬性名用的是 x(寬) 和 y(高),理論上 width 和 height 語義更好,這樣應(yīng)該是用了矢量類型。
size 表示寬高,理論上 width 和 height 語義更好,這樣應(yīng)該是用了平面矢量類型的結(jié)構(gòu)體,所以是 x 和 y。
transform 表示一個(gè) 3x3 的變換矩陣。
m00 | m01 | m02m10 | m11 | m12 0 | 0 | 1
上面的 transform 屬性的值所對應(yīng)的矩陣為:
1 | 3 | 52 | 4 | 60 | 0 | 1
再看看這些屬性對應(yīng)的右側(cè)屬性面板。
x、y 分別是 5 和 6,它是 (0, 0) 進(jìn)行 transform 后的結(jié)果,這個(gè)直接對應(yīng) transform.m02 和 tansfrom.m12。
import { Matrix } from "pixi.js";const matrix = new Matrix(1, 2, 3, 4, 5, 6);const topLeft = matrix.apply({ x: 0, y: 0 }); // { x: 5, y: 6 }// 或直接點(diǎn)const topLeft = { x: 5, y: 6 }
這里引入了 pixi.js 的 matrix 類,該類使用列向量方式進(jìn)行表達(dá)。
文末有 demo 源碼以及線上 demo,可打開控制臺查看結(jié)果驗(yàn)證正確性。
然后這里的 width 和 height,是 223.61 和 500, 怎么來的?
它們對應(yīng)的是矩形的兩條邊變形后的長度,如下:
uiWidth 為 (0, 0) 和 (width, 0) 進(jìn)行矩陣變換后坐標(biāo)點(diǎn)之間的距離。
const distance = (p1, p2) => { const a = p1.x - p2.x; const b = p1.y - p2.y; return Math.sqrt(a * a + b * b);};const matrix = new Matrix(1, 2, 3, 4, 5, 6);const topLeft = { x: 5, y: 6 }const topRight = matrix.apply({ x: 100, y: 0 });distance(topRight, topLeft); // 223.60679774997897
最后計(jì)算出 223.60679774997897,四舍五入得到 223.61。
高度計(jì)算同理。
uiHeight 為 (0, 0) 和 (0, height) 進(jìn)行矩陣變換后坐標(biāo)點(diǎn)之間的距離。
const matrix = new Matrix(1, 2, 3, 4, 5, 6);const topLeft = { x: 5, y: 6 }const bottomLeft = matrix.apply({ x: 0, y: 100 });distance(bottomLeft, topLeft); // 500
最后是旋轉(zhuǎn)角度,它是寬度對應(yīng)的矩形邊向量,逆時(shí)針旋轉(zhuǎn) 90 度的向量所對應(yīng)的角度。
先計(jì)算寬邊向量,然后逆時(shí)針旋轉(zhuǎn) 90 度得到旋轉(zhuǎn)向量,最后計(jì)算旋轉(zhuǎn)向量對應(yīng)的角度。
const wSideVec = { x: topRight.x - topLeft.x, y: topRight.y - topLeft.y };// 逆時(shí)針旋轉(zhuǎn) 90 度,得到旋轉(zhuǎn)向量const rotationMatrix = new Matrix(0, -1, 1, 0, 0, 0);const rotationVec = rotationMatrix.apply(wSideVec);const rad = calcVectorRadian(rotationVec);const deg = rad2Deg(rad); //
這里用了幾個(gè)工具函數(shù)。
// 計(jì)算和 (0, -1) 的夾角const calcVectorRadian = (vec) => { const a = [vec.x, vec.y]; const b = [0, -1]; // 這個(gè)是基準(zhǔn)角度 // 使用點(diǎn)積公式計(jì)算夾腳 const dotProduct = a[0] * b[0] + a[1] * b[1]; const d = Math.sqrt(a[0] * a[0] + a[1] * a[1]) * Math.sqrt(b[0] * b[0] + b[1] * b[1]); let rad = Math.acos(dotProduct / d); if (vec.x > 0) { // 如果 x > 0, 則 rad 轉(zhuǎn)為 (-PI, 0) 之間的值 rad = -rad; } return rad;}// 弧度轉(zhuǎn)角度const rad2Deg = (rad) => (rad * 180) / Math.PI;
Figma 的角度表示比較別扭。
特征為:基準(zhǔn)角度朝上,對應(yīng)向量為 (0, -1),角度方向?yàn)槟鏁r(shí)針,角度范圍限定為 (-180, 180]
,計(jì)算向量角度時(shí)要注意這個(gè)特征進(jìn)行調(diào)整。
線上 demo:
https://codepen.io/F-star/pen/WNPVWwQ?editors=0012。
代碼實(shí)現(xiàn):
import { Matrix } from "pixi.js";// 計(jì)算和 (0, -1) 的夾角const calcVectorRadian = (vec) => { const a = [vec.x, vec.y]; const b = [0, -1]; const dotProduct = a[0] * b[0] + a[1] * b[1]; const d = Math.sqrt(a[0] * a[0] + a[1] * a[1]) * Math.sqrt(b[0] * b[0] + b[1] * b[1]); let rad = Math.acos(dotProduct / d); if (vec.x > 0) { // 如果 x > 0, 則 rad 為 (-PI, 0) 之間的值 rad = -rad; } return rad;}// 弧度轉(zhuǎn)角度const rad2Deg = (rad) => (rad * 180) / Math.PI;const distance = (p1, p2) => { const a = p1.x - p2.x; const b = p1.y - p2.y; return Math.sqrt(a * a + b * b);};const getAttrs = (size, transform) => { const width = size.x; const height = size.y; const matrix = new Matrix( transform.m00, // 1 transform.m10, // 2 transform.m01, // 3 transform.m11, // 4 transform.m02, // 5 transform.m12 // 6 ); const topLeft = { x: transform.m02, y: transform.m12 }; console.log("x:", topLeft.x) console.log("y:", topLeft.y) const topRight = matrix.apply({ x: width, y: 0 }); console.log("width:", distance(topRight, topLeft)); // 223.60679774997897 const bottomLeft = matrix.apply({ x: 0, y: height }); console.log("height:", distance(bottomLeft, topLeft)); // 500 const wSideVec = { x: topRight.x - topLeft.x, y: topRight.y - topLeft.y }; // 逆時(shí)針旋轉(zhuǎn) 90 度,得到旋轉(zhuǎn)向量 const rotationMatrix = new Matrix(0, -1, 1, 0, 0, 0); const rotationVec = rotationMatrix.apply(wSideVec); const rad = calcVectorRadian(rotationVec); const deg = rad2Deg(rad); console.log("rotation:", deg); // -63.43494882292201};getAttrs( // 寬高 { x: 100, y: 100 }, // 變換矩陣 { m00: 1, m01: 3, m02: 5, m10: 2, m11: 4, m12: 6, });
運(yùn)行一下,結(jié)果和屬性面板一致。
Figma 只用寬高和變換矩陣來表達(dá)矩形,在數(shù)據(jù)層可以用精簡的數(shù)據(jù)表達(dá)豐富的變形,此外在渲染的時(shí)候也能將矩陣運(yùn)算交給 GPU 進(jìn)行并行運(yùn)算,是不錯(cuò)的做法。
本文鏈接:http://www.www897cc.com/showinfo-26-74664-0.html學(xué)到了!Figma 原來是這樣表示矩形的
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com