理解SVG座標系和變換:視窗,viewBox和preserveAspectRatio

發表於2015-09-23

SVG元素不像HTML元素一樣由CSS盒模型管理。這使得我們可以更加靈活定位和變換這些元素-也許一眼看上去不太直觀。然而,一旦你理解了SVG座標系和變換,操縱SVG會非常簡單並且很有意義。本篇文章中我們將討論控制SVG座標系的最重要的三個屬性:viewport, viewBox, 和 preserveAspectRatio

這是本系列三篇文章中的第一篇,這篇文章討論SVG中的座標系和變換。

為了使文中的內容和解釋更形象化,我建立了一個互動演示,你可以任意改變viewBox 和 preserveAspectRatio的值。

線上案例

這個例子只是主要內容的一小部分,所以看完請回來繼續閱讀這篇文章

SVG畫布

canvas是繪製SVG內容的一塊空間或區域。理論上,畫布在所有維度上都是無限的。所以SVG可以是任意尺寸。然而,SVG通過有限區域展現在螢幕上,這個區域叫做viewport。SVG中超出視窗邊界的區域會被裁切並且隱藏。

視窗

視窗是一塊SVG可見的區域。你可以把視窗當做一個窗戶,透過這個窗戶可以看到特定的景象,景象也許完整,也許只有一部分。

SVG的視窗類似訪問當前頁面的瀏覽器視窗。網頁可以是任何尺寸;它可以大於視窗寬度,並且在大多數情況下都比視窗高度要高。然而,每個時刻只有一部分網頁內容是透過視窗可見的。

整個SVG畫布可見還是部分可見取決於這個canvas的尺寸以及preserveAspectRatio屬性值。你現在不需要擔心這些;我們之後會討論更多的細節。

你可以在最外層<svg>元素上使用widthheight屬性宣告視窗尺寸。

在SVG中,值可以帶單位也不可以不帶。一個不帶單位的值可以在使用者空間中通過使用者單位宣告。如果值通過使用者單位宣告,那麼這個值的數值被認為和px單位的數值一樣。這意味著上述例子將被渲染為800px*600px的視窗。

你也可以使用單位來宣告值。SVG支援的長度單位有:emexpxptpccmmmin和百分比。

一旦你設定最外層SVG元素的寬高,瀏覽器會建立初始視窗座標系和初始使用者座標系。

初始座標系

初始視窗座標系是一個建立在視窗上的座標系。原點(0,0)在視窗的左上角,X軸正向指向右,Y軸正向指向下,初始座標系中的一個單位等於視窗中的一個”畫素”。這個座標系統類似於通過CSS盒模型在HTML元素上建立的座標系。

初始使用者座標系是建立在SVG畫布上的座標系。這個座標系一開始和視窗座標系完全一樣-它自己的原點位於視窗左上角,x軸正向指向右,y軸正向指向下。使用viewBox屬性,初始使用者座標系統-也稱當前座標系,或使用中的使用者空間-可以變成與視窗座標系不一樣的座標系。我們在一下節中討論如何改變座標系。

到現在為止,我們還沒有宣告viewBox屬性值。SVG畫布的使用者座標系統和視窗座標系統完全一樣。

下圖中,視窗座標系的”標尺”是灰色的,使用者座標系(viewBox)的是藍色的。由於它們在這個時候完全相同,所以兩個座標系統重合了。

上面SVG中的鸚鵡的外框邊界是200個單位(這個例子中是200個畫素)寬和300個單位高。鸚鵡基於初始座標系在畫布中繪製。

新使用者空間(即,新當前座標系)也可以通過在容器元素或圖形元素上使用transform屬性來宣告變換。我們將在這篇文章的第二部分討論關於變換的內容,更多細節在第三部分和最後部分中討論。

viewBox

我喜歡把viewBox理解為“真實”座標系。首先,它是用來把SVG圖形繪製到畫布上的座標系。這個座標系可以大於視窗也可以小於視窗,在視窗中可以整體可見或部分可見。

在之前的章節裡,這個座標系-使用者座標系-和視窗座標系完全一樣。因為我們沒有把它宣告成其他座標系。這就是為什麼所有的定位和繪製看起來是基於視窗座標系的。因為我們一旦建立視窗座標系(使用widthheight),瀏覽器預設建立一個完全相同的使用者座標系。

你可以使用viewBox屬性宣告自己的使用者座標系。如果你選擇的使用者座標系統和視窗座標系統寬高比(高比寬)相同,它會延伸來適應整個視窗區域(一分鐘內我們就來講個例子)。然而,如果你的使用者座標系寬高比不同,你可以用preserveAspectRatio屬性來宣告整個系統在視窗內是否可見,你也可以用它來宣告在視窗中如何定位。我們會在下個章節裡討論這一情況的細節和例子。在這一章裡,我們只討論viewBox的寬高比符合視窗的情況-在這些例子中,preserveAspectRatio不產生影響。

在我們討論這些例子前,我們回顧一下viewBox的語法。

viewBox語法

viewBox屬性接收四個引數值,包括:<min-x>, <min-y>, width 和 height

<min-x> 和 <min-y> 值決定viewBox的左上角,widthheight決定視窗的寬高。這裡要注意視窗的寬高不一定和父<svg>元素的寬高一樣。<width><height>值為負數是不合法的。值為0的話會禁止元素的渲染。

注意視窗的寬度也可以在CSS中設定為任何值。例如:設定width:100%會讓SVG視窗在文件中自適應。無論viewBox的值是多少,它會對映為外層SVG元素計算出的寬度值。

設定viewBox的例子如下:

如果你之前在其他地方看到過viewBox,你也許會看到一些解釋說你可以用viewBox屬性通過縮放或者變化使SVG圖形變換。這是真的。我將深入探究並且告訴你甚至可以使用viewBox來切割SVG圖形。

理解viewBox和視窗之間差異最好的方法是親身觀察。所以讓我們看一些例子。我們將從viewBox和viewport的寬高比相同的例子開始,所以我們還不需要深入瞭解preserveAspectRatio

與viewport寬高比相同的viewBox

我們從一個簡單的例子開始。這個例子中的viewBox的尺寸是視窗尺寸的一半。在這個例子中我們不改變viewBox的原點,所以<min-x><min-y>都設定成0。viewBox的寬高是viewport寬高的一半。這意味著我們保持寬高比。

所以,viewBox="0 0 400 300"到底有什麼用呢?

  • 它宣告瞭一個特定的區域,canvas橫跨左上角的點(0,0)到點(400,300)
  • SVG影像被這個區域裁切
  • 區域被拉伸(類似縮放效果)來充滿整個視窗。
  • 使用者座標系被對映到視窗座標系-在這種情況下-一個使用者單位等於兩個視窗單位。

下面的圖片展示了在我們例子中把上面的viewBox應用到<svg> 畫布中的效果。灰色單位代表視窗座標系,藍色座標系代表viewBox建立的使用者座標系。

任何在SVG畫布中畫的內容都會被對應到新的使用者座標系中。

我喜歡像Google地圖一樣通過viewBox把SVG畫布形象化。在Google地圖中你可以在特定區域縮放;這個區域是唯一可見的,並且在瀏覽器視窗中按比例增加。然而,你知道地圖的剩餘部分還在那裡,但是不可見因為它超出視窗的邊界-被裁切了。

現在讓我們試著改變<min-x><min-y>的值。都設定為100。你可以設定成任何你想要的值。寬高比還是和視窗的寬高比一樣。

新增viewBox="100 100 200 150"的效果和之前例子中一樣都是裁切的效果。圖形被裁切然後拉伸來充滿整個視窗區域。

再一次,使用者座標系被對映到視窗座標系-200使用者單位對映為800視窗單位因此每個使用者單位等於四個視窗單位。結果像你看到的那樣是放大的效果。

另外注意,在這個時候,為<min-x><min-y>宣告非0的值對圖形有變換的效果;更加特別的是,SVG 畫布看起來向上拉伸100個單位,向左拉伸100個單位(transform="translate(-100 -100)")。

的確,作為規範說明,viewBox屬性的影響在於使用者代理自動新增適當的變換矩陣來把使用者空間中具體的矩形對映到指定區域的邊界(通常是視窗)”

這是一個很棒的說明我們之前已經提到的內容的方法:圖形被裁切然後被縮放以適應視窗。這個說明隨後增加了一個註釋:“在一些情況下使用者代理在縮放變換之外需要增加一個移動變換。例如,在最外層的svg元素上,如果viewBox屬性對<min-x><min-y>宣告非0值得那麼就需要移動變換。”

為了更好演示移動變換,讓我們試著給<min-x><min-y>新增-100。移動效果類似transform="translate(100 100)";這意味著圖形會在切割和縮放後移動到右下方。回顧倒數第二個裁切尺寸為400*300的例子,新增新的無效<min-x><min-y>值,新的程式碼如下:

給圖形新增上述viewBox transformation的結果如下圖所示:

注意,與transform屬性不同,因為viewBox自動新增的tranfomation不會影響有vewBox屬性的元素的x,y,寬和高等屬性。因此,在上述例子中展示的帶有width,heightviewBox屬性的svg元素,widthheight屬性代表新增viewBox 變換之前的座標系中的值。在上述例子中你可以看到初始(灰色)viewport座標系甚至在<svg>上使用了viewBox屬性後仍然沒有影響。

另一方面,像tranform屬性一樣,它給所有其他屬性和後代元素建立了一個新的座標系。你還可以看到在上述例子中,使用者座標系是新建立的-它不是保持像初始使用者座標系和使用viewBox前的視窗座標系一樣。任何<svg>後代會在這個的使用者座標系中定位和確定尺寸,而不是初始座標系。

最後一個viewBox的例子和前一個類似,但是它不是切割畫布,我們將在viewport裡擴充套件它並看它如何影響圖形。我們將宣告一個寬高比視窗大的viewBox,並依然保持viewport的寬高比。我們在下一章裡討論不同的寬高比。

在這個例子中,我們將viewBox的尺寸設為viewport的1.5倍。

現在使用者座標系會被放大到1200*900。它會被對映到視窗座標系,使用者座標系中的每一個單位水平方向上等於視窗座標系中的viewport-width / viewBox-width,豎直方向上等於viewport-height / viewBox-height。這意味著,在這種情況下,每一個使用者座標系中的x-units等於viewport座標系中的0.66x-units,每個使用者y-unit對映成0.66的viewport y-units。

當然,理解這些最好的方法是把結果視覺化。viewBox被縮放到適應下圖所示的viewport。因為圖形在畫布裡基於新的使用者座標系繪製的,而不是視窗座標系,它看起來比視窗小。

到目前為止,我們所有的例子的寬高比都和視窗一致。但是如果viewBox中宣告的寬高比和視窗中的不一樣會發生什麼呢?例如,試想我們把視窗的尺寸設為1000*500。寬高比不再和視窗的一樣。在例子中使用viewBox="0 0 1000 500"的結果如下圖:

使用者座標系。因此圖形在視窗中定位:

  • 整個viewBox適應視窗。
  • 保持viewBox的寬高比。viewBox沒有被拉伸來覆蓋視窗區域。
  • viewBox在視窗中水平垂直居中。

這是預設表現。那用什麼控制表現呢?如果我們想改變視窗中viewBox的位置呢?這就需要用到preserveAspectRatio屬性了。

preserveAspectRatio屬性

preserveAspectRatio屬性強制統一縮放比來保持圖形的寬高比。

如果你用不同於視窗的寬高比定義使用者座標系,如果像我們在之前的例子中看到的那樣瀏覽器拉伸viewBox來適應視窗,寬高比的不同會導致圖形在某些方向上扭曲。所以如果上一個例子中的viewBox被拉伸以在所有方向上適應視窗,圖形看起來如下:

當給viewBox設定0 0 200 300的值時扭曲顯而易見(顯然這很不理想),這個值小於視窗尺寸。我故意選擇這個尺寸從而讓viewBox匹配鸚鵡邊界盒子的尺寸。如果瀏覽器拉伸影像來適應整個視窗,看起來會像下面這樣:

preserveAspectRatio屬性讓你可以在保持寬高比的情況下強制統一viewBox的縮放比,並且如果不想用預設居中你可以宣告viewBox在視窗中的位置。

preserveAspectRatio語法

preserveAspectRatio的官方語法是:

它在任何建立新viewport的元素上都有效(我們會在這個系列的下一部分討論這個問題)。

defer宣告是可選的,並且只有當你在<image>上新增preserveAspectRatio才被用到。用在任何其他元素上時它都會被忽略。<images>本身不在這篇文章的討論範圍,我們暫時跳過defer這個選項。

align引數宣告是否強制統一放縮,如果是,對齊方法會在viewBox的寬高比不符合viewport的寬高比的情況下生效。

如果align值設為none,例如:

圖形不在保持寬高比而會縮放來適應視窗,像我們在上面兩個例子中看到的那樣。

其他所有preserveAspectRatio值都在保持viewBox的寬高比的情況下強制拉伸,並且指定在視窗內如何對齊viewBox。我們會簡短介紹align的值。

最後一個屬性,meetOrSlice也是可選的,預設值為meet。這個屬性宣告整個viewBox在視窗中是否可見。如果是,它和align引數通過一個或多個空格分隔。例如:

這些值第一眼看起來也許很陌生。為了讓它們更易於理解和熟悉,你可以把meetOrSlice的值類比於background-sizecontaincover值;它們非常類似。meet類似於containslice類似於cover。下面是每個值的定義和含義:

meet(預設值)

基於以下兩條準側儘可能縮放元素:

  • 保持寬高比
  • 整個viewBox在視窗中可見

在這個情況下,如果圖形的寬高比不符合視窗,一些視窗會超出viewBox的邊界(即viewBox繪製的區域會小於視窗)。(在viewBox一節檢視最後的例子。)在這個情況下,viewBox的邊界被包含在viewport中使得邊界滿足。

這個值類似於background-size: contain。背景圖片在保持寬高比的情況下儘可能縮放並確保它適合背景繪製區域。如果背景的長寬比和應用的元素的長寬比不一樣,部分背景繪製區域會沒有背景圖片覆蓋。

slice

在保持寬高比的情況下,縮放圖形直到viewBox覆蓋了整個視窗區域。viewBox被縮放到正好覆蓋視窗區域(在兩個維度上),但是它不會縮放任何超出這個範圍的部分。換而言之,它縮放到viewBox的寬高可以正好完全覆蓋視窗。

在這種情況下,如果viewBox的寬高比不適合視窗,一部分viewBox會擴充套件超過視窗邊界(即,viewBox繪製的區域會比視窗大)。這會導致部分viewBox被切片。

你可以把這個類比為background-size: cover。在背景圖片的情況中,圖片在保持本身寬高比(如何)的情況下縮放到寬高可以完全覆蓋背景定位區域的最小尺寸。

所以,meetOrSlice被用來宣告viewBox是否會被完全包含在視窗中,或者它是否應該儘可能縮放來覆蓋整個視窗,甚至意味著部分的viewBox會被“slice”。

例如,如果我們宣告viewBox的尺寸為200*300,並且使用了meetslice值,保持align值為瀏覽器預設,每個值的結果會看起來如下:

align引數使用9個值中的一個或者為none。任何除none之外的值都用來保持寬高比縮放圖片,並且還用來在視窗中對齊viewBox

當使用百分比值時,align值類似於background-position。你可以把viewBox當做背景影像。通過align定位和background-position的不同在於,不同於通過一個與視窗相關的點來宣告一個特定的viewBox值,它把具體的viewBox“軸”和對應的視窗的“軸”對齊。

為了理解每個align值的含義,我們將首先介紹每一個“軸”。

還記得viewBox<min-x><min-y>值嗎?我們將使用它們來定義viewBox中的”min-x”和”min-y”軸。另外,我們將定義兩個軸“max-x”和”max-y“,各自通過<min-x> + <width> 和 <min-y> + <height>來定位。最後,我們定義兩個軸”mid-x”和”mid-y”,根據<min-x> + (<width>/2) 和 <min-y> + (<height>/2)來定位。

這樣做是不是讓事情更復雜了呢?如果是這樣,讓我們看一下下面的圖片來看一下每個軸代表了什麼。在這張圖片中,<min-x>和 <min-y>值都設定為0。viewBox被設定為viewBox = "0 0 300 300"

上面圖片中的灰色虛線代表視窗的mid-xmid-y軸。我們將對它們賦一些值來對齊viewBoxmid-xmid-y軸。對於視窗,min-x的值等於0min-y值也等於0max-x值等於viewBox的寬度,max-y的值等於高度,mid-xmid-y代表了寬度和高度的中間值。

對齊的取值包括:

none

不強制統一縮放。如果必要的話,在不統一(即不保持寬高比)的情況下縮放給定元素的影像內容直到元素的邊界盒完全匹配是視窗矩形。

換句話說,如果有必要的話viewBox被拉伸或縮放來完全適應整個視窗,不管寬高比。圖形也許會扭曲。

(注意:如果<align>的值是none,可選的<meetOrSlice>值無效。)

xMinYMin

  • 強制統一縮放
  • 視窗X軸的最小值對齊元素viewBox<min-x>
  • 視窗Y軸的最小值對齊元素viewBox的<min-y>
  • 把這個類比為backrgound-position: 0% 0%;

xMinYMid

  • 強制統一縮放。
  • 視窗X軸的最小值對齊元素viewBox<min-x>
  • 視窗Y軸的中間值來對齊元素的viewBox的中間值。
  • 把這個類比為backrgound-position: 0% 50%;

xMinYMax

  • 強制統一縮放。
  • 視窗X軸的最小值對齊元素viewBox<min-x>
  • 視窗X軸的最大值對齊元素的viewBox<min-y>+<height>
  • 把這個類比為backrgound-position: 0% 100%;

xMidYMin

  • 強制統一縮放。
  • 視窗X軸的中間值對齊元素的viewBox的X軸中間值。
  • 視窗Y軸的中間值對齊元素的viewBox的 <min-y>
  • 把這個類比為backrgound-position: 50% 0%;

xMidYMid (預設值)

  • 強制統一縮放。
  • 視窗X軸的中間值對齊元素的viewBox的X軸中間值。
  • 視窗Y軸的中間值對齊元素的viewBox的Y軸中間值。
  • 把這個類比為backrgound-position: 50% 50%;

xMidYMax

  • 強制統一縮放。
  • 視窗X軸的中間值對齊元素的viewBox的X軸中間值。
  • 視窗Y軸的最大值對齊元素的viewBox<min-y>+<height>
  • 把這個類比為backrgound-position: 50% 100%;

xMaxYMin

  • 強制統一縮放。
  • 視窗X軸的最大值對齊元素的viewBox的 <min-x>+<width>
  • 視窗Y軸的最小值對齊元素的viewBox<min-y>
  • 把這個類比為backrgound-position: 100% 0%;

xMaxYMid

  • 強制統一縮放。
  • 視窗X軸的最大值對齊元素的viewBox的 <min-x>+<width>
  • 視窗Y軸的中間值對齊元素的viewBox的Y軸中間值。
  • 把這個類比為backrgound-position: 100% 50%;

xMaxYMax

  • 強制統一縮放。
  • 視窗X軸的最大值對齊元素的viewBox的 <min-x>+<width>
  • 視窗Y軸的最大值對齊元素的viewBox的 <min-y>+<height>
  • 把這個類比為backrgound-position: 100% 100%;

所以,通過使用preserveAspectRatio屬性的alignmeetOrSlice值,你可以宣告是否統一縮放viewBox,是否和視窗對齊,在視窗中是否整個可見。

有時候,取決於viewBox的尺寸,一些值可能會導致相似的結果,例如在早先viewBox="0 0 200 300"的例子中,一些對齊完全用了不同的align值。這時候就要設定meetOrSlice的值為meet來保證viewBox包含在viewport內。

如果我們把meetOrSlice的值改成slice,不同的值我們將得到不同的結果。注意viewBox是如何拉伸來覆蓋整個視窗的。x軸被拉伸到用200單位來覆蓋視窗800單位。為了達到這個目的,並且保持viewBox的寬高比,y軸在底部被“裁切”,但是你可以想象它在視窗中高度上的延伸。

當然,不同的viewBox值看起來不同於我們這裡用的200*300。為了保持簡潔,我們不再列舉更多的例子,你可以看我建立的一些互動演示來幫助你更好地形象化理解viewBoxpreserveAspectRatio在不同值下的效果。你可以在一下節中檢視互動演示例子的連結。

但是在這之前,我想要提醒你注意如果<min-x> 和 <min-y>值改變,那麼mid-xmid-ymax-x, 和 max-y的值也會發生改變。你可以在互動演示中改變這些值來檢視軸以及相關聯的viewBox的對齊方式的改變。

下面圖片展示了定位軸的位置為viewBox = "100 0 200 300"時的效果。和之前用一樣的例子,但是我們把<min-x>的值設為100而不是之前的0。你可以設定成任何你想要的值。注意min-xmid-x, 和 max-x軸是如何變化的。這裡使用的preserveAspectRatio值為預設的xMinYMin meet,意味著mid-*軸和視窗軸的中間對齊。

互動演示

要理解viewport, viewBox, 以及不同的preserveAspectRatio值是如何工作的最好方法是視覺化的演示。

出於這個目的,我建立了一個簡單的互動演示,你可以改變這些屬性的值來檢視新值導致的結果。

線上案例

我希望這篇文章在幫助你理解SVG viewport, viewBox, 和 preserveAspectRatio 內容時有作用。如果你想要了解更多關於SVG座標系的內容,例如巢狀座標系,建立一個新的座標系以及SVG中的變換,繼續閱讀這一系列接下來的部分。感謝你的閱讀!

相關文章