理解SVG座標系統和變換: 建立新視窗

發表於2015-09-23

在SVG繪製的任何一個時刻,你可以通過巢狀<svg>或者使用例如<symbol>的元素來建立新的viewport和使用者座標系。在這篇文章中,我們將看一下我們如何這樣做,以及這樣做如何幫助我們控制SVG元素並讓它們變得更加靈活(或流動)。

這是SVG座標系和變換系列的第三篇也是最後一篇文章。在第一篇中,包括了任何要理解SVG座標系統基礎的需要知道的內容;更具體的是, SVG viewport, viewBox和 preserveAspectRatio屬性。在第二篇文章裡,你可以瞭解到任何你需要了解的關於SVG系統變換的內容。

通過這篇文章,我假定你已經讀了這個系列的第一部分關於SVG viewport, viewBox 和 preserveAspectRatio 屬性的內容。在閱讀這篇文章之前你不需要讀第二篇關於座標系變換的內容。

巢狀<svg>元素

第一部分我們討論了<svg>元素如何為SVG畫布內容建立一個視窗。在SVG繪製過程中的任何一個時刻,你可以建立一個新的視窗其中包含的圖形是通過把一個<svg>元素包含在另一箇中繪製的。通過建立新視窗,你隱性得建立了一個新視窗座標系和新使用者座標系。

例如,試想有一個<svg>以及裡面的內容:

 

第一件需要注意的是內容<svg>元素不需要宣告一個名稱空間xmlns因為預設和外層<svg>的名稱空間相同。當然,如果在HTML5文件中外層<svg>也不需要名稱空間。

你可以使用一個巢狀的SVG來把元素組合在一起然後在父SVG中定位它們。現在,你也可以把元素組合在一起並且使用組<g>來定位-通過把元素包括在一組<g>元素中。你可以使用transform屬性在畫布中定位它們。然而,使用<svg>肯定好過使用<g>。使用x和y座標來定位,在許多情況下,比使用變換更加方便。另外,<svg>元素接受寬高值,<g>不行。這意味著,<svg>也許並必要的,因為它可以建立一個新的viewport和座標系,你可以不需要也不想要。

通過給<svg>宣告寬高值,你把內容限制在通過x,y,widthheight屬性定義的viewport的邊界。任何超過邊界的內容會被裁切。

如果你不宣告xy屬性,它們預設是0。如果你不宣告heightwidth屬性,<svg>會是父SVG寬度和高度的100%。

另外,宣告使用者座標系而不是預設的也會影響內部<svg>的內容。

<svg>內的元素百分比值的宣告會根據<svg>計算,而不是外層<svg>。例如,下面的程式碼會導致內層SVG等於400單位,裡面的長方形是200個單位:

 

如果最外層<svg>的寬度為100%(例如,如果它在一個文件中內聯或者你想要它可以流動),內層SVG會擴充套件拉伸來保持寬度為外層SVG的一半-這是強制的。

巢狀SVG在給SVG畫布中的元素增加靈活性和擴充套件性時尤其有用。我們知道,使用viewBox值和preserveAspectRatio,我們已經可以建立響應式SVG。最外層<svg>的寬度可以設定成100%來確保它擴充套件拉伸到它的容器(或頁面)擴充套件或拉伸。然後通過使用viewBox值和 preserveAspectRatio,我們可以保證SVG畫布可以自適應viewport中的改變(最外層svg)。我在CSSConf演講的幻燈片中寫到了關於響應式SVG的內容。你可以在這裡檢視這個技術。

然而,當我們像這樣建立一個響應式SVG,整個畫布以及所有繪製在上面的元素都會有反應並且同時改變。但有時候,你只想讓圖形中的一個元素變為響應式,並且保持其他東西“固定”在一個位置和/或尺寸。這時候巢狀svg就很有用。

svg元素有獨立於它父元素的座標系,它可以有獨立的viewBoxpreserveAspectRatio屬性,你可以任意修改裡面內容的尺寸和位置。

所以,要讓一個元素更加靈活,我們可以把它包裹在<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圖形的程式碼如下:

這個時候,注意在巢狀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"來定位。程式碼如下:

每個svg層/viewport等於最外層svg寬高的100%。所以我們基本有了三個副本。每層包含一個元素-上部分殼,下部分殼,或小雞。三層的viewBox是相同的,只有preserveAspectRatio不同。

當然,在這個例子裡,一開始的圖形中小雞隱藏在蛋裡,隨著螢幕變小才顯示出來。然而,你可以做一些不一樣的:你可以開始在小螢幕上建立一個圖形,然後在大螢幕上顯示一些東西;即當svg變寬時才有更多垂直空間來展示元素。

你可以更有創造性,根據不同螢幕尺寸來顯示和隱藏元素-使用媒體查詢-把新元素通過特定方式定位來達到特定的效果。想象力是無窮的。

同時注意巢狀svg不需要和容器svg有相同的寬高;你可以宣告寬高並且限制svg內容,超出邊界裁切-這都取決於你想要達到什麼效果。

使用巢狀SVG使元素流動

在保持寬高比的情況下定位元素,我們可以使用巢狀svg只允許特定元素流動-可以不保持這些特定元素的寬高比。

例如,如果你只想SVG中的一個元素流動,你可以把它包含在一個svg中,並且使用preserveAspectRatio="none"來讓這個元素擴充套件始終撐滿這個視窗的寬,並且保持寬高比和像我們在之前例子中做的一樣定位其他元素。

Jake Archibald建立了一個簡單實用的巢狀SVG使用案例:一個簡單的UI可以包含定位在最外層svg角落的元素,並且保持寬高比,UI的中間部分浮動並且根據svg寬度改變進行拉伸。你可以在這裡檢視。確保你在開發工具裡檢查程式碼來選取和想象不同viewbox和svg使用的效果。

其他建立新視窗的方法

svg不是唯一能在SVG中建立新視窗的元素。在下面部分,我們會討論使用其他SVG元素建立新視窗的方法。

使用<use>ing <symbol>建立一個新的視窗

symbol元素會定義新視窗,無論它什麼時候被use元素例項化。

symbol元素的使用可以參考use元素中的xlink:href屬性:

上面值中的問號表示這些值也許沒有宣告-如果xy沒有宣告,預設值為0,也不需要宣告寬高。

看到了吧,當你use一個symbol元素,然後使用開發工具檢查DOM,你不會看到use標籤中symbol的內容。因為use的內容在shadow tree裡被渲染,如果你在開發工具中允許shadow DOM顯示你就能看到。

symbol被使用時,它被深度克隆到生成的shadow tree中,例外是symbolsvg替換。這個生成的svg總是有明確的寬高。如果寬高的值在use元素上,這些值會被轉換生成svg。如果屬性寬和/或高沒有宣告,生成的svg元素會使用這些值的100%。

因為我們在DOM中使用了svg,並且因為這個svg實際上包含在外層svg中,我們遇到的巢狀svg的狀況和我們在之前一章討論到的並沒有多少不一樣-巢狀的svg形成了一個新的viewport。巢狀svgviewBox是在symbol元素上宣告的viewBox。(symbol元素接受viewBox元素值。更多資訊,閱讀這篇文章:Structuring, Grouping, and Referencing in SVG – The , , and Elements)

所以我們現在有了一個新的viewport,尺寸和位置可以使用元素(x,ywidthheight)宣告,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元素。

<image>元素接收許多屬性,其中一些屬性-和這篇文章有關的-是xy位置屬性,widthheight屬性以及preserveAspectratio

通常,SVG檔案會包含一個根<svg>元素;這個元素也許宣告位置和尺寸,另外也許有viewBoxpreserveAspectratio值。

當一個image元素代表SVG圖片檔案,根svg的xywidthheight屬性被忽略。除非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,widthheight定義的視窗的左上角對齊。

如果preserveAspectRatio的值是“none”那麼圖片的寬高比不會保持不變。圖片會自適應,柵格的左上角和座標系(x,y)完全對齊,柵格的右下角和座標系(x+widthy+height)完全對齊。

使用<iframe>建立新視窗

代表SVG檔案的iframe元素建立新座標系的情況類似於上述解釋的image元素的情況。iframe元素也可以有x,y,widthheight屬性,除了它自身的preserveAspectratio之外。

使用<foreignObject>建立新視窗

foreignObject元素建立一個新的viewport來渲染這個元素的內容。

foreignObject標籤允許你把非SVG內容新增到SVG檔案中。通常,foreignObject的內容被認為不同於名稱空間。例如,你可以把一些HTML放到SVG元素的中間。

foreignObject接收屬性包括xyheightwidth,用來定位物件和調整尺寸,建立用於呈現它裡面所引用的內容的範圍。

有需要關於foreignObject元素的要說因為它給內容建立了新的viewport。如果你感興趣,可以檢視MDN entry或者在The Nitty Gritty Blog上檢視Christian Schaeffer建立的實際使用例子

結束語

建立新的viewports和座標系-像上述提到的一樣通過巢狀svg和其他元素-允許你控制SVG的部分內容而通過其他方式你可能沒法一樣控制。

在寫這片文章以及思考例子和使用情況的整個過程中,我一直在思考巢狀SVG如何讓我們在處理SVG時能更好控制並有更靈活的方式。自適應SVG可以通過簡潔的程式碼建立,在SVG中可以建立獨立於其他元素的流動元素,用來模擬CSS border images來在高分屏上定義背景。

你是否已經在SVG中使用巢狀視窗來建立有趣的例子了呢?你能否相處更多有創意的例子呢?

這篇文章總結了“理解SVG座標系和變換”這個系列。下一步,我們會討論動畫,甚至更多!敬請期待,感謝你的閱讀!

相關文章