2017年3月20日

[技術分享] 理解 SVG 中的 Viewport 和 ViewBox-拖曳與縮放功能實做(上)



不同於以往將 SVG 視為一張圖案(ICON 或 LOGO)的概念,在這篇文章中,我們要試著將 SVG 視為一個畫布(Canvas),而我們可以透過滑鼠來直接對這個畫布像 Google Map 一樣進行拖曳和縮放。

要將 SVG 視為一個畫布,並實做出縮放或拖曳的功能,有許多對於 SVG 的基本概念是我們需要先瞭解的,就讓我們一步一步來瞭解。


註1:在這篇文章中我們會把 SVG 視為一個畫布(Canvas),針對<svg></svg>而不是去探究 SVG 中各個元素(例如,<circle></circle>,<rect></rect>)。
註2:在這篇文章中我們只考慮 viewport 和 viewBox 為等比例的情況。


實做出來的效果會像這樣:

建議可以在 jsfiddle 中檢視,使用滑鼠縮放時比較不會拖曳到視窗。

瞭解 SVG 中的 Viewport 和 ViewBox


在 SVG 的世界中,空間的概念可以分成 viewportviewBox 兩個部分。在這篇文章中,我會把它 Viewport 比喻作相框,ViewBox 比喻作相片。

Viewport 相對上比較好理解,就是相框的大小,也就是你的眼睛看得到的範圍,不管你的相片多大,你能看到的實際範圍就是相框的大小。在網頁中我們可以透過設定 viewport 來調整我們相框的大小。

ViweBox 則可以想成是這張照片的大小,如果相片的大小和相框(viewport)一樣大的時候,自然不會有什麼問題,你可以從相框中看到完整的相片。可是,如果相片(viewbox)比起相框來得大或來得小時,這時候就會比較麻煩些,你會需要多去控制這張相片應該要如何的排置在相框上,才能夠呈現出你想要呈現的東西,因此 ViewBox 中除了能夠控制的相片的大小之外,還能夠控制相片要如何擺放在相框中

讓我們來瞭解一下 viewport 和 viewbox 的概念:

當 viewBox 等於 viewport 的情況(初始化的情況)

首先,我們可以直接在 SVG 元素上定義 viewport 的寬高,或者你也可以用 CSS 定義,基本上這個 DOM 元素的寬高就是 viewport 的寬高,也就是你相框的寬高。接著我們可以在 SVG 中定義我們的圖案內容,在這裡先用一隻鳥當作示範。

<svg id="svgElement" width="800" height="400">
    <g><!--  鳥的圖形 --></g>
</svg>

這時候便會得到如下的一個 viewport:


再來我們可以設定 viewBox , viewBox 的設定可以寫在 SVG 的標籤上,設定屬性包含 <min-x> <min-y> <width> <height> 這四個屬性,也就是 viewBox = '<min-x> <min-y> <width> <height>'

我們剛剛有提到,ViewBox 中除了能夠控制的相片的大小之外,還能夠控制相片要如何擺放在相框中,其中 <min-x> 和 <min-y> 就是在控制相片要如何擺放在相框中,而 <width> 和 <height> 則是相片的大小。

預設的情況下(沒有特別去設定 viewBox 的情況下)viewBox 的大小會和 viewport 一樣 ,因此當我們把鳥的相片放入 svg 後,他會自動填滿整個相框,兩個的大小會是一樣的,程式碼會像這樣:
   

<svg id="svgElement" width="800" height="400" viewBox="0 0 800 400">
    <g><!--  鳥的圖形 --></g>
</svg>

相片會很完整的融入在相框當中


當 viewbox 小於 viewport 時:圖案會被放大

透過 viewBox 的設定,我們可以進一步放大或縮小我們的相片,這裡有一個原則是,viewBox 會自動盡可能去填滿成 viewport 的大小,說起來很抽象,假設一開始我的 viewport 為 800 x 400 ,現在我們可以透過 viewBox 的設定,使得我們 viewBox 的大小變成 400 x 200,當我們的相片大小設定的比相框還小時,它會在原本的相片上裁切一小塊區域(在這裡是 400 x 200),接著把它調整到填滿整個 viewport 的大小(800 x 400)。

分解過程像是這樣(綠色是 viewport,藍色是 viewBox)
(1)設定比 viewport 還要小的 viewBox:
我們在 800 x 400 的 viewport 中設定了 400 x 200 的 viewBox 。

(2)裁切:
這時候因為我們設定的尺寸比相片原本的大小還要小,所以相片會被裁切。

(3)填滿 viewport
最後 viewBox 會盡可能的填滿整個 viewport,造成放大的效果。

這時候就會產生一個很神奇的現象,因為我的相片尺寸(viewBox)設定的比原本相片的尺寸來的小,它會先把相片裁切成我們指定的大小,但是因為它會自動去填滿整個 viewport 的緣故,所以造成了相片被放大的效果。

程式碼寫起來像是這樣,設定為 viewBox="0 0 400 200"
     

<svg id="svgElement" width="800" height="400" viewBox="0 0 400 200">
    <g><!--  鳥的圖形 --></g>
</svg>

當 viewbox 大於 viewport 時:圖案會被縮小

類似的道理,當我們設定的 viewBox 大於 viewPort 時,它會先把這張照片的底圖放大(先放大成 1600 x 800),但是圖案大小不變,然後在盡可能的塞入 viewport 當中(這裡是 800 x 400):

設定 viewBox 為 1600 x 800,比原本的 viewport 大,照片的底圖會被放大,但是圖案大小不會變。

把 viewBox (1600 x 800)塞進 viewport (800 x 400)當中,造成圖案變小的情況。

所以,雖然 viewBox 的設定(1600 x 800)大於原始 viewBox 的大小(800 x 400),但是實際上,照片在實際顯示上卻會被縮小。
程式碼寫起來會像這樣子,viewBox="0 0 1600 800"

<svg id="svgElement" width="800" height="400" viewBox="0 0 1600 800">
    <g><!--  鳥的圖形 --></g>
</svg>

SVG ViewBox 位置的設定

在 viewBox 的設定中,包含了四個屬性值 viewBox = '<min-x> <min-y> <width> <height>',除了上面我們所說的可以設定照片的大小外,也可以裡用 <min-x> 和 <min-y> 這兩個屬性來設定相片的位置。

例如說當我設定 viewBox 的 min-x 為 150 時( viewBox = "150 0 800 400" ),viewBox 會向左移動 150 單位 :

將 viewBox 設定為 "150 0 800 400" 時,viewBox 會向左移動 150 單位。

實際上可以看到的圖案範圍變小。

我們也可以同時設定 min-x 和 min-y ,例如(viewBox = "-400 -200 800 400"),會得到這樣的效果:

將 viewBox 設定為 "-400 -200 800 400"

我們最後實際上可以看到的圖案內容。

viewBox 設定造成的影響


在 viewBox 的設定的四個屬性值中 viewBox = "min-x min-y width height",我們可以簡單理解成前兩項 min-xmin-y 控制的是位移(translate),可以達到左右移動的效果;widthheight 控制的則是縮放(scale)。

但是因為 viewBox 實際上影響的是 SVG 當中的座標系統(後面會說明 SVG 座標系統),所以會和你的直覺有寫相反,例如,當你設定 min-xmin-y 越大時,實際上看到的畫面會往左上方移動;同樣地,當你設定widthheight 越大時,實際上看到的畫面會往縮小。

這個部分需要你花一些時間實際感受一下,非常建議利用下段中由 Sara Soueidan 所提供的實做案例感受。

實際感受 SVG 的效果


Sara Soueidan 在她所撰寫文章中 Understanding SVG Coordinate Systems and Transformations (Part 1) — The viewport, viewBox, and preserveAspectRatio 提供了非常好的實做案例

建議先把 viewBox 設定成和 viewport 大小一樣(800 x 600),這是一般初始化狀況。

左上角的地方你可以去以用拖拉的方式去設定 viewBox 的值,以此感受 viewBox 的改變對於視覺上顯示的效果,另外,由於它的 viewport 是 (800 x 600) 所以建議你可以先把 viewBox 的長寬設為 800 x 600(也就是初始化時預設 viewport = viewBox 的狀況),接著再來實際操作看看 viewBox 的改變會有什麼樣的效果。

當 viewport 和 viewBox 的尺寸並不是等比例時:preserveAspectRatio


我們提到,當 viewBox 和 viewport 的尺寸大小不一樣時, viewBox 會盡可能的去填滿整個 viewport ,可是在上面文章的例子中,我們都把 viewBox 的尺寸大小設定的和 viewPort 是等比例的情況,如果是不同比例的話,又要用什麼樣的方式來對齊和填滿呢?

這時候我們就會需要用到 preserveAspectRatio這個屬性了。在這個屬性當中可以設定對齊的方式(align)還有要用什麼樣的方式填滿(meetOrSlice)。

在這篇文章中,我只打算探討 viewBox 和 viewport 為等比例的情況,我認為要瞭解 viewport 和 viewBox 的概念時,先以等比例的情況當作實例來練習是比較容易瞭解的,因為在等比例的情況下,preserveAspectRation 的值對於畫面的呈現是沒有任何影響的,如果對於 preserveAspectRatio 的屬性想要有更多的瞭解,可以參考這篇文章([譯] 理解 SVG 座標系統與 Transformations @ Andyyou),或進一步參考文章中最後面所列的參考文章。

深入瞭解 SVG 座標系


從上面我們可以看出當我們為 SVG 元素設定 viewBox 時,SVG 會有很多特別的效果,我們可以視整個 SVG 元素為一個畫布,對它進行縮放和移動,而實際上在 SVG 的世界中,我們要瞭解viewport 和 viewBox 實際上是處在兩個不同的座標系統中。

在 viewport 中,是我們過去所熟悉的,以瀏覽器中 DOM 為主的座標系統,通常把它稱作 viewport 座標系統(viewport coordinate system)canvas 座標系統(但是不要把它和 HTML 中的 Canvas 標籤搞混)。一般來說,在這個座標系統中,1px 就是 1px 大,這個座標系統是相對固定的。在後面的文章中,我會使用 viewport 座標系統這個詞。

在 viewBox 的座標系統中,就不是我們所習慣的情況了,我們一般把在 viewBox 的座標系統稱作 SVG 座標系統(SVG coordinate system)用戶座標系統(user coordinate system)(或 the current coordinate systemuser space in use)。在後面的文章中,我會使用 SVG 座標系統這個詞。

在這個座標系統中,值是不一定要有單位的,如果我們沒有給它單位時,預設它會以 viewport 的單位為單位(例如,px),然而,恐怖的特別的地方在於,當我們在設定 viewBox 時,如果對於這當圖片有縮放時,那麼它的 1 單位大小將不會再是 1px 。

認識不同的座標名稱


為了幫助我們更進一步的探討 SVG 座標系統和 viewport 座標系統,我們要來認識三種不同的座標名稱,分別是 offsetclientSVG Point

其中 offset 和 client 所取得的值都是屬於 viewport 座標系統中的座標值(也就是 1px 就是真實的 1px),兩者的差別在於 offset 是相對於 container 左上角的點,也就是以 container 左上角為(0, 0);而 client 則是相對於 window 左上角(瀏覽器視窗左上角)的點,以 window 左上角為(0, 0),越右 X 值越大,越下 Y 值越大。

offset 的座標值以 container 左上角為準;client 的座標值以瀏覽器視窗(window)左上角為準。

SVG 座標系統就比較特別一點了,它有專屬的座標點,可以透過 viewport 座標系的 client 座標加以轉換。

我們會在下一篇文章中說明如何取得這三個座標值,但目前你需要知道有這三種不同的座標名稱。

viewBox 等同於 viewport 時

我們用 Sara Soueidan 在所提供的互動案例來做更多說明,一開始的時候,我先把 viewBox 設成和 viewport 一樣大,在這種情況下 SVG 座標系統中的 1 單位大小會和 viewport 的一樣大,也就是 1px。留意藍色的尺標是 SVG 座標系統、灰色的尺標則是 viewport 座標系統:

當 viewBox 等於 viewport 時,viewBox 就等於 viewport 的 1px,也就是實際 1px 大。

同時我們標下鳥右下角的這個點,讓我們後面能夠更清楚 SVG 座標系統的變化。在一開始的時候,因為 viewport 等同於 viewBox 的緣故,所以 SVG 座標系統中這個點,和 viewport 座標系統中的 offset 座標點,都會是(200, 300):


當 viewBox 為 viewport 尺寸的一半時

接著,我們可以看到,當我把 viewBox 設為 viewport 的一半(viewBox = "0 0 400 300"),也就是讓鳥看起來變 2 倍大時,這時候 SVG 座標系統中的 1 單位,會變成 viewport 的 2 單位(這裡就是 2px)。

當 viewBox 為 viewport 的一半時,圖案會被裁切後放大,所以 SVG 座標系統中的 1 單位,會變成 viewport 的 2 單位大(2px)。

但要留意的是,雖然圖案被放大了兩倍,但是鳥右下角這點在藍色尺標的 SVG 座標系統中一樣是(200, 300),可是灰色尺標的 viewport 座標系統中的 offset 座標點會變成兩倍,也就是(400, 600)。

當 viewBox 為 viewport 尺寸的兩倍大時

同樣的道理,當我把 viewBox 設為 viewport 的 2 倍大(viewBox = "0 0 1600 800"),也就是讓鳥看起來變 2 倍大時,這時候 SVG 座標系統中的 1 單位,會變成 viewport 的 1/2 單位(這裡就是 0.5px)。

這時候你會看到,雖然藍色尺標的 SVG 座標系統中,右下角座標仍然是(200, 300),但是 viewport 座標系統中的 offset 座標點則變成了(100, 150)。



重點總結


在這篇文章中有幾個你應該留意的重點:
  • 知道有 viewport 和 SVG 這兩種不同的座標系統
  • 知道有 offset, client, SVGPoint 這三種不同的座標點
  • 知道有 offset 和 client 屬於 viewport 座標系統;SVGPoint 屬於 SVG 座標系統
  • 知道透過 viewBox 的設定可以達到縮放和移動的效果

參考資料


0 意見:

張貼留言