SVG 圓分解為路徑
已發表: 2022-03-10這篇文章從一個坦白開始:我喜歡手工編碼 SVG。 情況並非總是如此,但對於那些不喜歡我的人來說,這似乎很奇怪。 能夠手動編寫 SVG 有很多好處,例如以工具無法實現的方式優化 SVG(將路徑轉換為更簡單的路徑或形狀),或者只需了解 D3 或 Greensock 等庫的工作方式.
話雖如此,我想更仔細地研究 SVG 中的圓形以及當我們經過一個基本圓形時我們可以用它們做的事情。 為什麼是圓圈? 嗯,我喜歡圓圈。 它們是我最喜歡的形狀。
首先(希望您之前在 SVG 中看到過一個基本的圓圈),這裡有一支筆顯示了一個:
一個圓圈可以做很多事情:它可以是動畫的,它可以應用不同的顏色。 不過,在 SVG 1.1 中,有兩件非常好的事情不能讓圓圈做:不能讓另一個圖形元素沿著圓圈的路徑移動(使用animateMotion
元素),並且不能沿著圓圈的路徑塑造文本(這將僅在 SVG 2.0 發布後才允許使用)。
把我們的圈子變成一條路
有一個小的在線工具可以幫助您創建圓圈外的路徑(您可以在此處嘗試),但我們將從頭開始創建所有內容,以便我們可以了解幕後的真實情況。
為了製作一條圓形路徑,我們將實際製作兩條弧線,即在一條路徑中完成圓的半圓。 您可能已經在上面的 SVG 中註意到,屬性CX
、 CY
和R
分別定義了沿 X 和 Y 軸繪製圓的位置,而R
定義了圓的半徑。 CX
和CY
創建圓心,所以圓是圍繞該點繪製的。
複製該圓圈可能如下所示:
<path d=" M (CX - R), CY a R,R 0 1,0 (R * 2),0 a R,R 0 1,0 -(R * 2),0 " />
注意CX
與圓的cx
屬性相同; CY
和圓的cy
屬性以及圓的R
和r
屬性也是如此。 小a
字符用於定義一段橢圓弧。 您可以使用可選的Z
(或z
)來關閉路徑。
小寫字母a
表示相對於當前位置繪製的橢圓弧的開始——或者在我們的特定情況下:
<path d=" M 25, 50 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 " />
你可以看到這支筆的神奇之處:
隱藏在路徑下方的是一個帶有紅色填充的圓圈。 當你玩弄路徑的值時,只要路徑完全覆蓋圓圈,你就會看到那個圓圈(路徑本身是一個大小相同的圓圈),我們就會知道我們做對了.
您還應該知道的一件事是,只要您繪製相對弧線,就不需要為您繪製的每條弧線重複a
命令。 當您的前 7 個輸入為您的弧完成後,接下來的 7 個輸入將用於下一個弧。
您可以通過刪除路徑中的第二個a
來嘗試使用上面的筆:
a 25,25 0 1,1 50,0 25,25 0 1,1 -50,0
這可能看起來一樣,但我更喜歡把它留在裡面,直到我準備好完成一幅畫,這也有助於我跟踪我的位置。
這條路徑如何運作
首先,我們移動到圖像中絕對定位的X,Y
坐標。 它不會在那裡畫任何東西——它只是移動到那裡。 請記住,對於圓元素CX
, CY
表示圓的中心; 但正如橢圓弧中發生的那樣,弧的真實CX
和CY
將根據該弧的其他屬性計算。
換句話說,如果我們希望我們的CX
是50
並且我們的半徑是25
,那麼我們需要移動到50 - 25
(當然,如果我們是從左到右繪製的話)。 這意味著我們的第一條弧線是從25 X, 50 Y
繪製的,這導致我們的第一條弧線是25,25 0 1,0 50,0
。
讓我們分解弧的值25,25 0 1,0 50,0
的實際含義:
-
25
:圓弧的相對X半徑; -
25
:圓弧的相對Y半徑; -
0 1,0
:我不打算討論三個中間值(旋轉、大弧標誌和掃描標誌屬性),因為它們在當前示例的上下文中並不是很重要,只要它們兩條弧線相同; -
50
:圓弧的終點X坐標(相對); -
0
: 圓弧的結束 Y 坐標(相對)。
第二個弧是25,25 0 1,0 -50,0
。 請記住,此弧將從最後一個弧停止繪製的位置開始繪製。 當然,X 和 Y 半徑是相同的( 25
),但結束 X 坐標是當前坐標的-50
。
顯然,這個圓圈可以用許多不同的方式繪製。 這種將圓變成路徑的過程稱為分解。 在 SVG 2 規範中,圓的分解將使用 4 條弧線完成,但是,它推薦的方法還不能使用,因為它目前依賴於尚未指定的名為分段完成關閉路徑的功能。
為了向大家展示我們可以通過多種方式繪製圓圈,我準備了一支帶有各種示例的小筆:
如果您仔細觀察,您會看到我們的原始圓圈以及如何在該圓圈頂部繪製路徑的五個不同示例。 每條路徑都有一個子desc
元素,描述使用CX
、 CY
和R
值來構建圓。 第一個示例是我們在此處討論的示例,而其他三個示例使用了通過閱讀代碼應該可以理解的變體; 最後一個示例使用四個半圓弧而不是兩個,在一定程度上複製了上面鏈接的 SVG 2 規範中描述的過程。
使用 SVG 的自然 z-indexing 將標記中較晚出現的元素放置在較早出現的元素之上,圓圈相互疊加。
如果你點擊筆中的圓形路徑,第一次點擊會打印出路徑是如何構造到控制台的,並給元素添加一個類,這樣你就可以看到圓是如何繪製的描邊顏色(你可以看到第一個圓圈是用筆劃的起始楔形繪製的)。 第二次單擊將刪除圓圈,因此您可以與下面的圓圈進行交互。
每個圓圈都有不同的填充顏色; 實際的圓形元素是黃色的,並且每當單擊它時都會對控制台說“您單擊了圓形”。 當然,您也可以簡單地閱讀代碼,因為desc
元素非常簡單。
從路徑到圓
我想你已經註意到了,雖然有很多不同的方法來繪製圓圈,但使用的路徑看起來仍然非常相似。 通常——尤其是在繪圖程序輸出的 SVG 中——圓將由路徑表示。 這可能是由於圖形程序代碼的優化; 一旦你有了繪製路徑的代碼,你就可以繪製任何東西,所以就使用它。 這可能會導致 SVG 有點臃腫,難以理解。
推薦閱讀: Sara Soueidan 的“為 Web 創建和導出更好的 SVG 的技巧”
讓我們以維基百科中的以下 SVG 為例。 當您查看該文件的代碼時,您會發現一旦您通過 Jake Archibald 的 SVGOMG 運行它,就會發現它有很多編輯器! (您可以在此處閱讀更多信息),您最終會得到類似以下文件的內容,該文件已經過相當優化,但文檔中的圓圈仍呈現為路徑:
所以,讓我們看看我們是否可以根據我們對路徑工作原理的了解,弄清楚如果這些圓圈是實際的圓圈元素,它們應該是什麼。 文檔中的第一個路徑顯然不是一個圓圈,而接下來的兩個是(僅顯示d
屬性):
M39 20a19 19 0 1 1-38 0 19 19 0 1 1 38 0z
M25 20a5 5 0 1 1-10 0 5 5 0 1 1 10 0z
所以記住第二個a
可以省略,讓我們重寫這些以使其更有意義。 (第一條路徑是大圓圈。)
M39 20 a19 19 0 1 1-38 0 a19 19 0 1 1 38 0z
這些弧顯然如下:
aR R 0 1 1 - (R * 2) 0 aR R 0 1 1 (R * 2) 0
這意味著我們的圓半徑是19
,但是我們的CX
和CY
值是多少? 我認為我們的M39
實際上是CX + R
,這意味著CX
是20
而CY
也是20
。
假設您在所有路徑之後添加一個圓圈,如下所示:
<circle fill="none" stroke-width="1.99975" stroke="red" r="19" cx="20" cy="20" />
你會看到這是正確的,紅色的描邊圓圈正好覆蓋了大圓圈。 重新制定的第二個圓形路徑如下所示:
M25 20 a5 5 0 1 1-10 0 5 5 0 1 1 10 0z
顯然,半徑是5
,我敢打賭我們的CX
和CY
值和以前一樣: - 20
。
注意:如果CX = 20
,則CX + R = 25
。 圓圈位於中心較大的圓圈內,因此顯然它應該具有相同的CX
和CY
值。
在路徑末尾添加以下圓圈:
<circle fill="yellow" r="5" cx="20" cy="20" />
您現在可以通過查看以下筆看到這是正確的:
現在我們知道圓圈應該是什麼,我們可以刪除那些不需要的路徑並實際創建圓圈 - 正如您在此處看到的那樣:
使用我們的循環路徑包裝文本
所以現在我們在路徑中有了圓圈,我們可以在這些路徑上包裝文本。 下面是一支與我們之前的“All Circles”筆具有相同路徑的筆,但路徑上包含了文本。 每當您單擊路徑時,該路徑將被刪除,並且文本將被包裝在下一個可用路徑上,如下所示:
查看不同的路徑,您會看到每條路徑之間的微小差異(稍後會詳細介紹),但首先會看到一些跨瀏覽器不兼容 - 在第一個路徑中尤其明顯:
火狐開發者 | |
鉻合金 | |
微軟邊緣 |
在 Firefox 解決方案中,“Smashing”的起始“S”位於那個有趣的角度的原因是,它實際上是我們開始繪製路徑的位置(由於我們使用了 vR 命令)。 這在 Chrome 版本中更為明顯,您可以清楚地看到我們繪製的圓圈的第一個餅形楔形:
Chrome 並沒有遵循所有的楔形,所以當您將文本更改為“Smashing Magazine”時,就會出現這種結果。 |
原因是 Chrome 有一個關於繼承父text
元素上聲明的textLength
屬性的錯誤。 如果您希望它們看起來相同,請將textLength
屬性放在textPath
元素和文本上。 為什麼? 因為事實證明,如果text
元素上沒有指定textLength
屬性,Firefox 開發人員也有同樣的錯誤(這種情況已經有好幾年了)。
Microsoft Edge 有一個完全不同的錯誤; 它無法處理Text
和子TextPath
元素之間的空格。 刪除空格並將textLength
屬性放在text
和textPath
元素上後,它們看起來都相對相同(由於默認字體等方面的差異而略有不同)。 因此,三種不同瀏覽器上的三種不同錯誤——這就是人們經常喜歡使用庫的原因!
下面的筆顯示瞭如何解決這些問題:
我還刪除了各種填充顏色,因為它可以更容易地查看文本換行。 刪除填充顏色意味著我的小功能允許您循環瀏覽路徑並查看它們的外觀,除非我添加一個pointer-events="all"
屬性,否則我也添加了這些屬性。
注意:您可以在 Tiffany B. Brown 解釋的“使用指針事件屬性管理 SVG 交互”中閱讀更多關於其原因的信息。
我們已經討論了多弧路徑的包裝,現在讓我們看看其他的。 由於我們有一條路徑,因此文本將始終沿同一方向移動。
圖片 | 小路 | 解釋 |
---|---|---|
M CX, CY a R, R 0 1,0 -(R * 2), 0 a R, R 0 1,0 R * 2, 0 並使用 translate 函數在 X 軸上移動+R 。 | 我們的textPath 的起始位置(因為我們沒有以任何方式指定它)由我們的第一個結束弧-(R * 2) 確定,給定弧本身的半徑。 | |
M (CX + R), CY a R,R 0 1,0 -(R * 2),0 a R,R 0 1,0 (R * 2),0 | 與上一條路徑相同。 | |
M CX CY m -R, 0 a R,R 0 1,0 (R * 2),0 a R,R 0 1,0 -(R * 2),0 | 由於我們在第一個弧中結束於(R * 2 ) ,顯然我們將從相反的位置開始。 換句話說,這一條從我們前兩條路徑結束的地方開始。 | |
M (CX - R), CY a R,R 0 1, 1 (R * 2),0 a R,R 0 1, 1 -(R * 2),0 | 由於(R * 2) ,它從與最後一個相同的位置開始,但它是順時針運行的,因為我們已將掃描標誌屬性(標記為黃色)設置為1 。 |
我們已經了解瞭如何將文本環繞在一個圓圈中的單個路徑上。 現在讓我們看看如何將這條路徑分成兩條路徑,以及從中獲得的好處。
將我們的路徑分成幾部分
您可以對路徑中的文本做很多事情,即使用tspan
元素實現風格效果、設置文本的偏移量或為文本設置動畫。 基本上,無論您做什麼,都會受到路徑本身的限制。 但是通過將我們的多弧路徑分解為單個弧路徑,我們可以調整文本的方向、文本不同部分的 z 索引,並實現更複雜的動畫。
首先,我們將要使用另一個 SVG 圖像來顯示一些效果。 我將使用前面提到的關於指針事件的文章中的菱形。 首先,讓我們展示在其頂部放置單個路徑圓形文本的情況。
假設我們的圈子是CX 295, CY 200, R 175
。 現在,按照循環路徑方法,我們現在看到以下內容:
M (CX - R), CY a R,R 0 1,1 (R * 2),0 a R,R 0 1,1 -(R * 2),0
我不會談論路徑或文本大小、填充或描邊顏色。 我們現在都應該明白這一點,並且能夠讓它成為我們想要的任何東西。 但是通過查看文本,我們可以立即看到一些缺點或限制:
- 文本都向一個方向運行;
- 將一些文字放在紫水晶後面可能會很好,尤其是在它寫著 MAGAZINE 的地方。 為了使“M”和“E”在圓圈上對齊,“A”必須位於紫水晶的側面較低點,這在另一方面感覺有點不平衡。 (我覺得'A'應該精確定位並指向那個點。)
如果我們想解決這些問題,我們需要將我們的單一路徑一分為二。 在下面的筆中,我將路徑分成了兩條路徑,(並將它們放入 SVG 的defs
區域以供我們的textPath
參考):
同樣,假設我們的CX
是295, CY 200, R 175
,那麼兩條路徑的格式如下(對於頂部半圓形路徑):
M (CX - R), CY a R,R 0 1,1 (R * 2),0
下面是底部:
M (CX + R), CY a R,R 0 1,1 -(R * 2),0
但是,我們仍然有圓形文本,它們都向同一方向移動。 要解決除 Edge 之外的所有問題,您所要做的就是將side="right"
屬性添加到包含 'MAGAZINE' textPath
的text
元素中。
使文本走向另一個方向
如果我們想支持盡可能多的瀏覽器,我們必須改變路徑,而不是依賴不完全支持的side
屬性。 我們可以做的是複制我們的頂部半圓路徑,但將掃描從1
更改為0
:
前:
M 120, 200 a 175,175 0 1,
M 120, 200 a 175,175 0 1,
1
350,0
350,0
後:
M 120, 200 a 175,175 0 1,
M 120, 200 a 175,175 0 1,
0
350,0
350,0
但是我們的文本現在繪製在由掃描定義的內圈上,在不同的瀏覽器中看起來不太好。 這意味著我們將不得不移動路徑的位置以與“Smashing”的“S”對齊,使路徑的結尾 X 更大,並為文本設置一些偏移量。 如您所見,Firefox 與其他文本之間也存在一些差異,我們可以通過增加text
元素的textLength
屬性以及從textPath
中刪除空格來改進這一點(因為 Firefox 顯然認為空格是有意義的)。
解決方案:
更改部分循環文本的 Z-Index
最後,我們想讓我們的文本在紫水晶的前面和後面都出現。 嗯,這很容易。 還記得 SVG 的元素 z 索引是基於它們在標記中的位置嗎? 所以如果我們有兩個元素,元素1
將被繪製在元素2
後面。 接下來,我們要做的就是在 SVG 標記中向上移動一個text
元素,以便在紫水晶之前繪製它。
您可以看到下面的結果,其中“MAGAZINE”這個詞的部分被紫水晶的下端隱藏了。
如果您看一下標記,您會發現文本的下半圓已移動到繪製紫水晶的路徑之前。
為我們圈子的各個部分設置動畫
所以現在我們有能力通過將文本放入兩個半圓來完全控製文本部分的方向性來製作圓形文本。 當然,這也可以用來製作文本的動畫。 製作跨瀏覽器 SVG 動畫確實是另一篇文章(或更多文章)的主題。 這些示例僅適用於 Chrome 和 Firefox,因為使用 SMIL 動畫語法而不是 CSS 關鍵幀或 Greensock 等工具。 但它很好地指示了您可以通過為分解的圓圈設置動畫來實現的效果。
拿下面的筆:
請按 codepen 上的“重新運行”按鈕以查看正在運行的動畫。 我們的循環文本的兩個部分同時開始動畫,但有不同的持續時間,因此它們在不同的時間結束。 因為我們正在為textLength
屬性設置動畫,所以我們在每個文本下放置了兩個animate
指令——一個用於text
元素(因此 Firefox 可以工作),一個用於textpath
元素(因此 Chrome 可以工作)。
結論
在本文中,我們了解瞭如何將圓變成路徑並再次返回,以便更好地了解何時需要優化路徑,何時不需要。 我們已經看到瞭如何將圓形變成一條路徑讓我們可以將文本放置在圓形路徑上,以及如何進一步將圓形路徑分割成半圓形,並更全面地控制圓形文本的組成部分的方向性和動畫.
關於 SmashingMag 的進一步閱讀:
- 重新思考響應式 SVG
- 使用 SVGator 為 SVG 文件製作動畫
- 使用 CSS 為 SVG 設置樣式和動畫
- 使用指針事件屬性管理 SVG 交互