一、前言
前段時間研究 SVG 壓縮優化,發現SVG預定義的 rect
、circle
、ellipse
、line
、polyline
、polygon
六種基本形狀可通過path路徑轉換實現,這樣可以在一定程度上減少程式碼量。不僅如此,我們常用的 SVG Path 動畫(路徑動畫),是以操作path中兩個屬性值stroke-dasharray
和stroke-dashoffset
來實現,基本形狀轉換為path路徑,有利於實現路徑動畫。
二、SVG基本形狀
SVG 提供了rect
、circle
、ellipse
、line
、polyline
、polygon
六種基本形狀用於圖形繪製,這些形狀可以直接用來繪製一些基本的形狀,如矩形、橢圓等,而複雜圖形的繪製則需要使用 path 路徑來實現。
1.rect
矩形
1 2 |
<rect x="10" y="10" width="30" height="30"/> <rect x="60" y="10" rx="10" ry="10" width="30" height="30"/> |
SVG中rect
元素用於繪製矩形、圓角矩形,含有6個基本屬性用於控制矩形的形狀以及座標,具體如下:
1 2 3 4 5 6 |
x 矩形左上角x位置, 預設值為 0 y 矩形左上角y位置, 預設值為 0 width 矩形的寬度, 不能為負值否則報錯, 0 值不繪製 height 矩形的高度, 不能為負值否則報錯, 0 值不繪製 rx 圓角x方向半徑, 不能為負值否則報錯 ry 圓角y方向半徑, 不能為負值否則報錯 |
這裡需要注意,rx
和 ry
的還有如下規則:
rx
和ry
都沒有設定, 則 rx = 0 ry = 0rx
和ry
有一個值為0, 則相當於 rx = 0 ry = 0,圓角無效rx
和ry
有一個被設定, 則全部取這個被設定的值rx
的最大值為width
的一半,ry
的最大值為height
的一半
12345678910rx = rx || ry || 0;ry = ry || rx || 0;rx = rx > width / 2 ? width / 2 : rx;ry = ry > height / 2 ? height / 2 : ry;if(0 === rx || 0 === ry){rx = 0,ry = 0; //圓角不生效,等同於,rx,ry都為0}
2.circle
圓形
1 |
<circle cx="100" cy="100" r="50" fill="#fff"></circle> |
SVG中circle
元素用於繪製圓形,含有3個基本屬性用於控制圓形的座標以及半徑,具體如下:
1 2 3 |
r 半徑 cx 圓心x位置, 預設為 0 cy 圓心y位置, 預設為 0 |
3.ellipse
橢圓
1 |
<ellipse cx="75" cy="75" rx="20" ry="5"/> |
SVG中ellipse
元素用於繪製橢圓,是circle
元素更通用的形式,含有4個基本屬性用於控制橢圓的形狀以及座標,具體如下:
1 2 3 4 |
rx 橢圓x半徑 ry 橢圓y半徑 cx 圓心x位置, 預設為 0 cy 圓心y位置, 預設為 0 |
4.line
直線
1 |
<line x1="10" x2="50" y1="110" y2="150"/> |
Line
繪製直線。它取兩個點的位置作為屬性,指定這條線的起點和終點位置。
1 2 3 4 |
x1 起點的x位置 y1 起點的y位置 x2 終點的x位置 y2 終點的y位置 |
5.polyline
折線
1 |
<polyline points="60 110, 65 120, 70 115, 75 130, 80 125, 85 140, 90 135, 95 150, 100 145"/> |
polyline
是一組連線在一起的直線。因為它可以有很多的點,折線的的所有點位置都放在一個points屬性中:
1 |
points 點集數列,每個數字用空白、逗號、終止命令符或者換行符分隔開,每個點必須包含2個數字,一個是x座標,一個是y座標 如0 0, 1 1, 2 2” |
6.polygon
多邊形
1 |
<polygon points="50 160, 55 180, 70 180, 60 190, 65 205, 50 195, 35 205, 40 190, 30 180, 45 180"/> |
polygon
和折線很像,它們都是由連線一組點集的直線構成。不同的是,polygon
的路徑在最後一個點處自動回到第一個點。需要注意的是,矩形也是一種多邊形,如果需要更多靈活性的話,你也可以用多邊形建立一個矩形。
1 |
points 點集數列,每個數字用空白、逗號、終止命令符或者換行符分隔開,每個點必須包含2個數字,一個是x座標,一個是y座標 如0 0, 1 1, 2 2, 路徑繪製完閉合圖形” |
三、SVG path 路徑
SVG 的路徑<path>
功能非常強大,它不僅能建立基本形狀,還能建立更多複雜的形狀。<path>
路徑是由一些命令來控制的,每一個命令對應一個字母,並且區分大小寫,大寫主要表示絕對定位,小寫表示相對定位。<path>
通過屬性 d 來定義路徑, d 是一系列命令的集合,主要有以下幾個命令:
通常大部分形狀,都可以通過指令M(m)
、L(l)
、H(h)
、V(v)
、A(a)
來實現,注意特別要區分大小寫,相對與絕對座標情況,轉換時推薦使用相對路徑可減少程式碼量,例如:
1 2 3 4 5 6 7 |
// 以下兩個等價 d='M 10 10 20 20' // (10, 10) (20 20) 都是絕對座標 d='M 10 10 L 20 20' // 以下兩個等價 d='m 10 10 20 20' // (10, 10) 絕對座標, (20 20) 相對座標 d='M 10 10 l 20 20' |
四、SVG 基本形狀路徑轉換原理
1.rect to path
如下圖所示,一個 rect
是由 4 個弧和 4 個線段構成;如果 rect
沒有設定 rx 和 ry 則 rect
只是由 4 個線段構成。rect
轉換為 path
只需要將 A ~ H 之間的弧和線段依次實現即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
function rect2path(x, y, width, height, rx, ry) { /* * rx 和 ry 的規則是: * 1. 如果其中一個設定為 0 則圓角不生效 * 2. 如果有一個沒有設定則取值為另一個 */ rx = rx || ry || 0; ry = ry || rx || 0; //非數值單位計算,如當寬度像100%則移除 if (isNaN(x - y + width - height + rx - ry)) return; rx = rx > width / 2 ? width / 2 : rx; ry = ry > height / 2 ? height / 2 : ry; //如果其中一個設定為 0 則圓角不生效 if(0 == rx || 0 == ry){ // var path = // 'M' + x + ' ' + y + // 'H' + (x + width) + 不推薦用絕對路徑,相對路徑節省程式碼量 // 'V' + (y + height) + // 'H' + x + // 'z'; var path = 'M' + x + ' ' + y + 'h' + width + 'v' + height + 'h' + -width + 'z'; }else{ var path = 'M' + x + ' ' + (y+ry) + 'a' + rx + ' ' + ry + ' 0 0 1 ' + rx + ' ' + (-ry) + 'h' + (width - rx - rx) + 'a' + rx + ' ' + ry + ' 0 0 1 ' + rx + ' ' + ry + 'v' + (height - ry -ry) + 'a' + rx + ' ' + ry + ' 0 0 1 ' + (-rx) + ' ' + ry + 'h' + (rx + rx -width) + 'a' + rx + ' ' + ry + ' 0 0 1 ' + (-rx) + ' ' + (-ry) + 'z'; } return path; } |
2.circle/ellipse to path
圓可視為是一種特殊的橢圓,即 rx 與 ry 相等的橢圓,所以可以放在一起討論。 橢圓可以看成A點到C做180度順時針畫弧、C點到A做180度順時針畫弧即可。
1 2 3 4 5 6 7 8 9 10 11 12 |
function ellipse2path(cx, cy, rx, ry) { //非數值單位計算,如當寬度像100%則移除 if (isNaN(cx - cy + rx - ry)) return; var path = 'M' + (cx-rx) + ' ' + cy + 'a' + rx + ' ' + ry + ' 0 1 0 ' + 2*rx + ' 0' + 'a' + rx + ' ' + ry + ' 0 1 0 ' + (-2*rx) + ' 0' + 'z'; return path; } |
3.line to path
相對來說比較簡單,如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
function line2path(x1, y1, x2, y2) { //非數值單位計算,如當寬度像100%則移除 if (isNaN(x1 - y1 + x2 - y2)) return; x1 = x1 || 0; y1 = y1 || 0; x2 = x2 || 0; y2 = y2 || 0; var path = 'M' + x1 + ' '+ y1 + 'L' + x2 + ' ' + y2; return path; } |
4.polyline/polygon to path
polyline
折線、polygon
多邊形的轉換為path比較類似,差別就是polygon
多邊形會閉合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// polygon折線轉換 points = [x1, y1, x2, y2, x3, y3 ...]; function polyline2path (points) { var path = 'M' + points.slice(0,2).join(' ') + 'L' + points.slice(2).join(' '); return path; } // polygon多邊形轉換 points = [x1, y1, x2, y2, x3, y3 ...]; function polygon2path (points) { var path = 'M' + points.slice(0,2).join(' ') + 'L' + points.slice(2).join(' ') + 'z'; return path; } |
五、convertpath 轉換工具
為了方便處理SVG基本元素路徑轉換,本人抽空寫了convertpath工具,具體如下:
安裝:
1 |
npm i convertpath |
使用:
1 2 3 4 5 6 7 8 9 10 |
const parse = require('convertpath'); parse.parse("./test/test.svg") /** * <circle cx="500" cy="500" r="20" fill="red"/> */ console.log(parse.toSimpleSvg()) /** * <path d="M500,500,m-20,0,a20,20,0,1,0,40,0,a20,20,0,1,0,-40,0,Z" fill="red"/> */ |
參考資料:
Basic Shapes – SVG 1.1 (Second Edition)
基本形狀 – SVG | MDN
SVG (一) 圖形, 路徑, 變換總結; 以及橢圓弧線, 貝塞爾曲線的詳細解釋
路徑 – SVG | MDN
XMLDOM