在SVG繪製的任何一個時刻,你可以通過巢狀<svg>
或者使用例如<symbol>
的元素來建立新的viewport和使用者座標系。在這篇文章中,我們將看一下我們如何這樣做,以及這樣做如何幫助我們控制SVG元素並讓它們變得更加靈活(或流動)。
這是SVG座標系和變換系列的第三篇也是最後一篇文章。在第一篇中,包括了任何要理解SVG座標系統基礎的需要知道的內容;更具體的是, SVG viewport, viewBox
和 preserveAspectRatio
屬性。在第二篇文章裡,你可以瞭解到任何你需要了解的關於SVG系統變換的內容。
- 理解SVG座標系和變換(第一部分)-viewport,
viewBox
,和preserveAspectRatio
- 理解SVG座標系和變換(第二部分)-
transform
屬性 - 理解SVG座標系和變換(第三部分)-建立新視窗
通過這篇文章,我假定你已經讀了這個系列的第一部分關於SVG viewport, viewBox
和 preserveAspectRatio
屬性的內容。在閱讀這篇文章之前你不需要讀第二篇關於座標系變換的內容。
巢狀<svg>
元素
在第一部分我們討論了<svg>
元素如何為SVG畫布內容建立一個視窗。在SVG繪製過程中的任何一個時刻,你可以建立一個新的視窗其中包含的圖形是通過把一個<svg>
元素包含在另一箇中繪製的。通過建立新視窗,你隱性得建立了一個新視窗座標系和新使用者座標系。
例如,試想有一個<svg>
以及裡面的內容:
1 2 3 4 5 6 |
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <!-- some SVG content --> <svg> <!-- some inner SVG content --> </svg> <svg> |
第一件需要注意的是內容<svg>
元素不需要宣告一個名稱空間xmlns因為預設和外層<svg>
的名稱空間相同。當然,如果在HTML5文件中外層<svg>
也不需要名稱空間。
你可以使用一個巢狀的SVG來把元素組合在一起然後在父SVG中定位它們。現在,你也可以把元素組合在一起並且使用組<g>
來定位-通過把元素包括在一組<g>
元素中。你可以使用transform
屬性在畫布中定位它們。然而,使用<svg>
肯定好過使用<g>
。使用x和y座標來定位,在許多情況下,比使用變換更加方便。另外,<svg>
元素接受寬高值,<g>
不行。這意味著,<svg>
也許並必要的,因為它可以建立一個新的viewport和座標系,你可以不需要也不想要。
通過給<svg>
宣告寬高值,你把內容限制在通過x
,y
,width
和height
屬性定義的viewport的邊界。任何超過邊界的內容會被裁切。
如果你不宣告x
和y
屬性,它們預設是0。如果你不宣告height
和width
屬性,<svg>
會是父SVG寬度和高度的100%。
另外,宣告使用者座標系而不是預設的也會影響內部<svg>
的內容。
給<svg>
內的元素百分比值的宣告會根據<svg>
計算,而不是外層<svg>
。例如,下面的程式碼會導致內層SVG等於400
單位,裡面的長方形是200
個單位:
1 2 3 4 5 |
<svg width="800" height="600"> <svg width="50%" ..> <rect width="50%" ... /> </svg> </svg> |
如果最外層<svg>
的寬度為100%(例如,如果它在一個文件中內聯或者你想要它可以流動),內層SVG會擴充套件拉伸來保持寬度為外層SVG的一半-這是強制的。
巢狀SVG在給SVG畫布中的元素增加靈活性和擴充套件性時尤其有用。我們知道,使用viewBox
值和preserveAspectRatio
,我們已經可以建立響應式SVG。最外層<svg>
的寬度可以設定成100%來確保它擴充套件拉伸到它的容器(或頁面)擴充套件或拉伸。然後通過使用viewBox值和 preserveAspectRatio,我們可以保證SVG畫布可以自適應viewport中的改變(最外層svg)。我在CSSConf演講的幻燈片中寫到了關於響應式SVG的內容。你可以在這裡檢視這個技術。
然而,當我們像這樣建立一個響應式SVG,整個畫布以及所有繪製在上面的元素都會有反應並且同時改變。但有時候,你只想讓圖形中的一個元素變為響應式,並且保持其他東西“固定”在一個位置和/或尺寸。這時候巢狀svg
就很有用。
svg
元素有獨立於它父元素的座標系,它可以有獨立的viewBox
和preserveAspectRatio
屬性,你可以任意修改裡面內容的尺寸和位置。
所以,要讓一個元素更加靈活,我們可以把它包裹在<svg>
元素中,並且給svg
一個彈性的寬度來適應最外層SVG的寬度,然後宣告preserveAspectRatio="none"
這樣的話裡面的圖形會擴充套件和拉伸到容器的寬度。注意svg
可以多層巢狀,但是為了讓事情簡潔,我在這篇文章裡只巢狀一層深度。
為了演示巢狀svg
如何發揮作用,讓我們來看一些例子。
例子
試想我們有如下的SVG:
上述SVG是響應式的。改變螢幕的尺寸會導致整個SVG圖形根據需要做出反應。下面的截圖展示了拉伸頁面的結果,以及SVG如何變得更小。注意SVG的內容如何根據SVG視窗和相互之間保持它們的初始位置。
使用巢狀SVG,我們將改變這個情況。我們可以對SVG中每個獨立的元素根據SVG視窗宣告一個位置,所以隨著SVG 視窗尺寸的改變(即最外層svg
的改變),每個元素獨立於其他元素髮生改變。
注意,在這個時候,你需要熟悉SVG viewport, viewBox
, 和preserveAspectRatio
是如何生效的。
我們將要建立一個效果,當螢幕尺寸變化時,蛋殼的上部分移動使得其中的可愛的小雞顯示出來,如下圖所示:
為了達到這個效果,蛋的上半部分必須和其他部分分離出來單獨包含一個自己的svg
。這個svg
包含框會有一個IDupper-shell
。
然後,我們保證新的svg#upper-shell
和外層SVG有一樣的高度和寬度。可以通過在svg
上宣告width="100%"
height="100%"
或者不宣告任何高度和寬度來實現。如果內層SVG上沒有宣告任何寬高,它會自動擴充套件為外層SVG寬高的100%
。
最終,為了確保上殼被“抬”起或定位在svg#upper-shell
頂部的中心,我們將使用適當的preserveAspectRatio
值來確保viewBox被定位在視窗的頂部中心-值是xMidYMin
。
SVG圖形的程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <!-- ... --> <svg viewBox="0 0 315 385" preserveAspectRatio="xMidYMid meet"> <!-- the chicken illustration --> <g id="chicken"> <!-- ... --> </g> <!-- path forming the lower shell --> <path id="lower-shell" fill="url(#gradient)" stroke="#000000" stroke-width="1.5003" d="..."/> </svg> <svg id="upper-shell" viewBox="0 0 315 385" preserveAspectRatio="xMidYMin meet"> <!-- path forming the upper shell --> <path id="the-upper-shell" fill="url(#gradient)" stroke="#000000" stroke-width="1.5003" d="..."/> </svg> </svg> |
這個時候,注意在巢狀svg#upper-shell
上宣告的viewBox
和最外層svg
有相同的值(在它被移除之前)。我們用相同的viewBox
值我原因就是這樣,SVG在大螢幕上保持最初的樣子。
所以,這件事是這樣的:我們開始一個SVG-在我們的例子中,這是一張裡面藏著一個小雞的帶裂紋的蛋。然後,我們建立了另一“層”並把上部分的殼放在裡面-這一層通過使用巢狀svg
建立。巢狀svg
和外層svg
的尺寸和viewBox
一樣。最終,內層SVG的viewBox被設定成不管螢幕尺寸是多少都“固定”在viewport的頂部-這確保了當螢幕尺寸很窄時SVG被拉長,上層的殼被向上舉起,因此展示出“隱藏”在裡面的小雞。
一旦螢幕尺寸拉伸,SVG被拉長,使用preserveAspectratio="xMidYMin meet"
把包含上部分殼的viewBox被定位到viewport的頂部。
點選下面按鈕來檢視線上SVG。記住改變螢幕尺寸再看SVG變化。
巢狀或”分層”SVG使你可以根據改變的視窗定位SVG的一部分,在保持元素寬高比的情況下。所以圖片可以在不扭曲內容元素的情況下自適應。
如果我們想要整個雞蛋剝離顯示出小雞,我們可以單獨用一個svg
層包含下部分殼,viewBox
也相同。確保下部分殼向下移動並固定在視窗的底部中心,我們使用preserveAspectRatio="xMidYMax meet"
來定位。程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg id="chick" viewBox="0 0 315 385" preserveAspectRatio="xMidYMid meet"> <!-- the chicken illustration --> <g id="chick"> <!-- ... --> </g> </svg> <svg id="upper-shell" viewBox="0 0 315 385" preserveAspectRatio="xMidYMid meet"> <!-- path forming the upper shell --> <path id="the-upper-shell" fill="url(#gradient)" stroke="#000000" stroke-width="1.5003" d="..."/> </svg> <svg id="lower-shell" viewBox="0 0 315 385" preserveAspectRatio="xMidYMax meet"> <!-- path forming the lower shell --> <path id="the-lower-shell" fill="url(#gradient)" stroke="#000000" stroke-width="1.5003" d="..."/> </svg> </svg> |
每個svg
層/viewport等於最外層svg
寬高的100%。所以我們基本有了三個副本。每層包含一個元素-上部分殼,下部分殼,或小雞。三層的viewBox
是相同的,只有preserveAspectRatio
不同。
當然,在這個例子裡,一開始的圖形中小雞隱藏在蛋裡,隨著螢幕變小才顯示出來。然而,你可以做一些不一樣的:你可以開始在小螢幕上建立一個圖形,然後在大螢幕上顯示一些東西;即當svg
變寬時才有更多垂直空間來展示元素。
你可以更有創造性,根據不同螢幕尺寸來顯示和隱藏元素-使用媒體查詢-把新元素通過特定方式定位來達到特定的效果。想象力是無窮的。
同時注意巢狀svg
不需要和容器svg
有相同的寬高;你可以宣告寬高並且限制svg
內容,超出邊界裁切-這都取決於你想要達到什麼效果。
使用巢狀SVG使元素流動
在保持寬高比的情況下定位元素,我們可以使用巢狀svg
只允許特定元素流動-可以不保持這些特定元素的寬高比。
例如,如果你只想SVG中的一個元素流動,你可以把它包含在一個svg
中,並且使用preserveAspectRatio="none"
來讓這個元素擴充套件始終撐滿這個視窗的寬,並且保持寬高比和像我們在之前例子中做的一樣定位其他元素。
1 2 3 4 5 6 7 8 9 10 |
<svg> <!-- ... --> <svg viewBox=".." preserveAspectRatio="none"> <!-- this content will be fluid --> </svg> <svg viewBox=".." preserveAspectRatio=".."> <!-- content positioned somewhere in the viewport --> </svg> <!-- ... --> </svg> |
Jake Archibald建立了一個簡單實用的巢狀SVG使用案例:一個簡單的UI可以包含定位在最外層svg
角落的元素,並且保持寬高比,UI的中間部分浮動並且根據svg寬度改變進行拉伸。你可以在這裡檢視。確保你在開發工具裡檢查程式碼來選取和想象不同viewbox和svg使用的效果。
其他建立新視窗的方法
svg
不是唯一能在SVG中建立新視窗的元素。在下面部分,我們會討論使用其他SVG元素建立新視窗的方法。
使用<use>
ing <symbol>
建立一個新的視窗
symbol
元素會定義新視窗,無論它什麼時候被use
元素例項化。
symbol
元素的使用可以參考use
元素中的xlink:href
屬性:
1 2 3 4 5 6 7 |
<svg> <symbol id="my-symbol" viewBox="0 0 300 200"> <!-- contents of the symbol --> <!-- this content is only rendered when `use`d --> </symbol> <use xlink:href="#my-symbol" x="?" y="?" width="?" height="?"> </svg> |
上面值中的問號表示這些值也許沒有宣告-如果x
和y
沒有宣告,預設值為0
,也不需要宣告寬高。
看到了吧,當你use
一個symbol
元素,然後使用開發工具檢查DOM,你不會看到use
標籤中symbol
的內容。因為use的內容在shadow tree裡被渲染,如果你在開發工具中允許shadow DOM顯示你就能看到。
當symbol
被使用時,它被深度克隆到生成的shadow tree中,例外是symbol
被svg
替換。這個生成的svg
總是有明確的寬高。如果寬高的值在use
元素上,這些值會被轉換生成svg
。如果屬性寬和/或高沒有宣告,生成的svg
元素會使用這些值的100%。
因為我們在DOM中使用了svg
,並且因為這個svg
實際上包含在外層svg
中,我們遇到的巢狀svg
的狀況和我們在之前一章討論到的並沒有多少不一樣-巢狀的svg
形成了一個新的viewport
。巢狀svg
的viewBox
是在symbol
元素上宣告的viewBox
。(symbol
元素接受viewBox
元素值。更多資訊,閱讀這篇文章:Structuring, Grouping, and Referencing in SVG – The , , and Elements)
所以我們現在有了一個新的viewport,尺寸和位置可以使用元素(x
,y
, width
, height
)宣告,viewBox
值可以在symbol
元素上宣告。symbol
的內容隨後再這個視窗和viewBox中被渲染和定位。
最後,symbol
元素也接收preserveAspectratio
屬性值,你可以在由use
建立的新視窗中定位viewBox
。這很清楚,不是嗎?你可以像我們在之前的部分裡一樣控制新建立的巢狀svg
。
Dirk Weber 也建立了一個使用巢狀SVG和symbol
元素來模仿CSS border images的表現。你可以在這裡檢視文章。
參考<image>
中的SVG image建立一個新視窗
images
元素表明整個檔案的內容被渲染到一個當前使用者座標系中給定的長方形。image
元素可以代表圖片檔案例如PNG或JPEG或者有”image/svg+xml”的MIME型別的檔案。
代表SVG檔案的image
元素會導致建立一個臨時新視窗因為定義相關資源有svg
元素。
1 |
<image xlink:href="myGraphic.svg" x="?" y="?" width="?" height="?" preserveAspectRatio="?" /> |
<image>
元素接收許多屬性,其中一些屬性-和這篇文章有關的-是x
和y
位置屬性,width
和height
屬性以及preserveAspectratio
。
通常,SVG檔案會包含一個根<svg>
元素;這個元素也許宣告位置和尺寸,另外也許有viewBox
和preserveAspectratio
值。
當一個image
元素代表SVG圖片檔案,根svg的x
,y
,width
和height
屬性被忽略。除非image
元素上的preserveAspectRatio
值以“defer”開頭,根元素上的preserveAspectRatio
值在代表SVG圖片時也被忽略。然而相關image
元素上的preserveAspectRatio
屬性定義SVG圖片內容如何適應視窗。
評估被參考內容定義的preserveAspectRatio
屬性時使用viewBox
屬性值。對於明確定義的viewBox內容(例如,最外層元素上有viewBox
屬性的SVG檔案)值應該被使用。對於大多數值(PING,JPEG),圖片邊界應該被使用(即image
元素有隱含的尺寸為’0 0 raster-image-width raster-image-height’的viewBox
)。如果值不全的話(例如,外層的svg元素沒有viewbox
屬性的SVG檔案)preserveAspectRatio
值被忽略,只有視窗x & y
屬性引起的移動才用來顯示內容。
例如,如果一個image元素代表PNG或JPEG並且preserveAspectRatio="xMinYMin meet"
,那麼柵格的寬高比會保持,柵格會在保證整個柵格適應視窗的情況下儘可能放大尺寸,柵格的左上角會和由image
元素上x
,y
,width
和height
定義的視窗的左上角對齊。
如果preserveAspectRatio
的值是“none”那麼圖片的寬高比不會保持不變。圖片會自適應,柵格的左上角和座標系(x
,y
)完全對齊,柵格的右下角和座標系(x
+width
, y
+height
)完全對齊。
使用<iframe>
建立新視窗
代表SVG檔案的iframe
元素建立新座標系的情況類似於上述解釋的image
元素的情況。iframe
元素也可以有x
,y
,width
和height
屬性,除了它自身的preserveAspectratio
之外。
使用<foreignObject>
建立新視窗
foreignObject
元素建立一個新的viewport來渲染這個元素的內容。
foreignObject
標籤允許你把非SVG內容新增到SVG檔案中。通常,foreignObject
的內容被認為不同於名稱空間。例如,你可以把一些HTML放到SVG元素的中間。
foreignObject
接收屬性包括x
,y
,height
和width
,用來定位物件和調整尺寸,建立用於呈現它裡面所引用的內容的範圍。
有需要關於foreignObject
元素的要說因為它給內容建立了新的viewport。如果你感興趣,可以檢視MDN entry或者在The Nitty Gritty Blog上檢視Christian Schaeffer建立的實際使用例子。
結束語
建立新的viewports和座標系-像上述提到的一樣通過巢狀svg
和其他元素-允許你控制SVG的部分內容而通過其他方式你可能沒法一樣控制。
在寫這片文章以及思考例子和使用情況的整個過程中,我一直在思考巢狀SVG如何讓我們在處理SVG時能更好控制並有更靈活的方式。自適應SVG可以通過簡潔的程式碼建立,在SVG中可以建立獨立於其他元素的流動元素,用來模擬CSS border images來在高分屏上定義背景。
你是否已經在SVG中使用巢狀視窗來建立有趣的例子了呢?你能否相處更多有創意的例子呢?
這篇文章總結了“理解SVG座標系和變換”這個系列。下一步,我們會討論動畫,甚至更多!敬請期待,感謝你的閱讀!