理解SVG坐標系和變換(第二部分)transform
(本文轉自w3cplus,這里僅修正了部分個人認為翻譯不恰當之處;下面是譯文鏈接,英文原文鏈接在結尾處提供)
- 理解SVG坐標系和變換(第一部分)-viewport,viewBox,和preserveAspectRatio
- 理解SVG坐標系和變換(第二部分)-transform屬性
- 理解SVG坐標系和變換(第三部分)-建立新視口
SVG元素可以通過縮放,移動,傾斜和旋轉來變換-類似HTML元素使用CSS transform來變換。然而,當涉及到坐標系時這些變換所產生的影響必然有一定差別。在這篇文章中我們討論SVG的transform屬性和CSS屬性,包括如何使用,以及你必須知道的關于SVG坐標系變換的知識。
這是我寫的SVG坐標系統和變換部分的第二篇。在第一篇中,包括了任何要理解SVG坐標系統基礎的需要知道的內容;更具體的是, SVG viewport, viewBox 和 preserveAspectRatio 屬性。
這一部分我建議你先閱讀第一篇,如果沒有,確保你在閱讀這篇之前已經讀了第一篇。
transform屬性值
tranform屬性用來對一個元素指定一個或多個變換。它將<transform-list>作為一個值,該值被定義為轉換定義的列表,按照提供的順序應用。每個變換定義由空格或逗號隔開。給元素添加變換看起來如下:
有效地SVG變換有:旋轉(rotation), 縮放(scaling), 移動(translation), 和傾斜(skewing)。transform屬性中使用的變換函數類似于CSS中transform屬性使用的CSS變換函數,除了參數不同。
注意下列的函數語法定義只在transform屬性中有效。查看section about transforming SVGs with CSS properties獲取關于CSS變換屬性中使用的語法信息。
Matrix
你可以使用matrix()函數在SVG元素上添加一個或多個變換。matrix變換語法如下:
matrix(<a> <b> <c> <d> <e> <f>)
上述聲明通過一個有6個值的變換矩陣聲明一個變換。matrix(a,b,c,d,e,f)等同于添加變換matrix[a b c d e f]。
如果你不精通數學,最好不要用這個函數。對于那些精通的人,你可以在這里閱讀更多關于它背后的數學內容。因此這個函數很少使用-我將忽略來討論其他變換函數。
Translation
要移動SVG元素,你可以用translate()函數。translate函數的語法如下:
translate(<tx> [<ty>])
translate()函數輸入一個或兩個值得來聲明水平和豎直移動值。tx代表x軸上的translation值;ty代表y軸上的translation值。
ty值是可選的,如果省略,默認值為0。tx和ty值可以通過空格或者逗號分隔,它們在函數中不代表任何單位-它們默認等于當前用戶坐標系單位。
下面的例子把一個元素向右移動100個用戶單位,向下移動300個用戶單位。
<circlecx="0" cy="0" r="100" transform="translate(100 300)" />
上述代碼如果以translate(100, 300)用逗號來分隔值的形式聲明一樣有效。
Scaling
你可以通過使用scale()函數變換來向上或者向下縮放來改變SVG元素的尺寸。scale變換的語法是:
scale(<sx> [<sy>])
scale()函數輸入一個或兩個值來聲明水平和豎直縮放值。sx代表沿x軸的縮放值,用來水平延長或者拉伸元素;sy代表沿y軸縮放值,用來垂直延長或者縮放元素。
sy值是可選的,如果省略默認值等于sx。sx和sy可以用空格或者逗號分隔,它們是無單位值。
下面例子把一個元素的尺寸根據最初的尺寸放大兩倍:
<rect width="150" height="100" transform="scale(2)" x="0" y="0" />
下列例子把一個元素縮放到最初寬度的兩倍,并且把高度壓縮到最初的一半:
<rect width="150" height="100" transform="scale(2 0.5)" x="0" y="0" />
上述例子使用逗號分隔的值例如scale(2, .5)仍然有效。
這里需要注意當SVG元素縮放時,整個坐標系被縮放,導致元素在視窗中重新定位,現在不用擔心這些,我們會在下一節中討論細節。
Skew
SVG元素也可以被傾斜,要傾斜一個元素,你可以使用一個或多個傾斜函數skewX 和 skewY。
skewX(<skew-angle>)
skewY(<skew-angle>)
函數skewX聲明一個沿x軸的傾斜;函數skewY聲明一個沿y軸的傾斜。
傾斜角度聲明是無單位角度的默認是度。
注意傾斜一個元素可能會導致元素在視窗中重新定位。在下一節中有更多細節。
Rotation
你可以使用rotate()函數來旋轉SVG元素。這個函數的語法如下:
rotate(<rotate-angle> [<cx> <cy>])
rotate()函數對于給定的點和旋轉角度值執行旋轉。不像CSS3中的旋轉變換,不能聲明除degress之外的單位。角度值默認無單位,默認單位是度。
可選的cx和cy值代表無單位的旋轉中心點。如果沒有設置cx和cy,旋轉點是當前用戶坐標系的原點(查看第一部分如果你不知道用戶坐標系是什么。)
在函數rotate()中聲明旋轉中心點一個快捷方式類似于CSS中設置transform: rotate()和transform-origin。SVG中默認的旋轉中心是當前使用的用戶坐標系的左上角,這樣也許你無法創建想要的旋轉效果,你可以在rotate()中聲明一個新的中心點。如果你知道元素在SVG畫布中的尺寸和定位,你可以把它的中心設置為旋轉中心。
下面的例子是以當前用戶坐標系中的(50,50)點為中心進行旋轉一組元素:
<g id="parrot" transform="rotate(45 50 50)" x="0" y="0">
<!-- elements making up a parrot shape -->
</g>
然而,如果你想要一個元素圍繞它的中心旋轉,你也許想要像CSS中一樣聲明中心為50% 50%;不幸的是,在rotate()函數中這樣做是不允許的-你必須用絕對坐標。但是,您可以使用CSS transform-origin屬性以及CSS transform屬性來實現這一點。本文稍后將對此進行詳細介紹。
坐標系變化
現在我們已經討論了所有可能的SVG變換函數,我們深入挖掘視覺部分和對SVG元素添加每個變換的效果。這是SVG變換最重要的部分。因此它們被稱為“坐標系統變換"而不僅僅是“元素變換”。
在這個規范中,transform屬性被定義為在應用它的元素上建立新用戶空間(當前坐標系統)的兩個屬性之一——viewBox屬性是創建新用戶空間的兩個屬性中的第二個。那么這到底意味著什么呢?
①transform屬性在它所應用的元素上建立一個新的用戶空間(當前坐標系統)。
這個行為類似于在HTML元素上添加CSS變換-HTML元素坐標系發生了變換,當你把變換組合使用時最明顯。雖然在很多方面很相似,HTML和SVG的變換還是有一些不同。
主要的不同是坐標系。HTML元素的坐標系建立在元素自身之上。然而,在SVG中,元素的坐標系最初(譯者注:這里是指元素被變換之前的坐標系)是當前坐標系或使用中的用戶空間。
當你在一個SVG元素上添加transform屬性,元素獲取當前使用的用戶坐標系的一個“副本”。你可以當做給發生變換的元素創建一個新“層”,新層上是當前用戶坐標系的副本(the viewBox)。
然后,元素新的當前坐標系被在transform屬性中聲明的變換函數改變,因此導致元素自身的變換。這看起來好像是元素在變換后的坐標系中重新繪制。
要理解如何添加SVG變換,讓我們從可視化的部分開始。下面圖片是我們要研究的SVG畫布。
鸚鵡和小狗使我們要變換的元素(組)。
<svg width="800" height="800" viewBox="0 0 800 600">
<g id="parrot">
<!-- shapes and paths forming the parrot -->
</g>
<g id="dog">
<!-- shapes and paths forming the dog -->
</g>
</svg>
灰色坐標是通過viewBox建立的畫布的初始坐標系。為了方便起見,我將不改變初始坐標系-我用一個和viewport相同尺寸的viewBox,如你在上述代碼中看到的一樣。
When you apply the transform attribute to an SVG element, that element gets a "copy" of the current user coordinate system in use.(當您將transform屬性應用到SVG元素時,該元素將獲得正在使用的當前用戶坐標系統的“副本”)
現在我們建立了畫布和初始用戶空間,讓我們開始變換元素。首先讓我們把鸚鵡向右移動150單位,向下移動200個單位。
當然,鸚鵡是由若干路徑和形狀組成的。只要把transform屬性添加到包含它們的組上就行了;這會對整個形狀和路徑添加變換,鸚鵡會作為一個整體進行變換。查看 article on structuring and grouping SVGs獲取更多信息。
<svg width="800" height="800" viewBox="0 0 800 600">
<g id="parrot" transform="translate(150 200)">
<!-- shapes and paths forming the parrot -->
</g>
<!-- ... -->
</svg>
下面圖片展示了上述變換后的結果。鸚鵡的半透明版本是變換前的初始位置。
SVG中的變換和HTML元素上CSS中的一樣簡單直觀。我們之前提到在元素上添加transform屬性時會在元素上創建一個新的當前用戶坐標系。下面圖片顯示了初始坐標系的“副本”,它在鸚鵡元素發生變換時被建立。注意觀察鸚鵡當前坐標系是如何變換的。
這里需要注意的非常重要的一點是建立在元素上的新的當前坐標系是初始用戶坐標系的復制,在里面元素的位置得以保持。這意味著它不是建立在元素邊界盒上,或者新的當前坐標系的尺寸受制于元素的尺寸。這就是HTML和SVG坐標系之間的區別。
建立在變換元素上的新當前坐標系不是建立在元素邊界盒上,或者新的當前坐標系的尺寸受制于元素的尺寸。
The new current coordinate system established on a transformed element is not established on the element's bounding box, nor is its size restricted to the size of the element.(在轉換后的元素上建立的新當前坐標系不是在元素的邊框上建立的,其大小也不限于元素的大小。)
我們把小狗變換到畫布的右下方時會更加明顯。試想我們想要把小狗向右移動50單位,向下移動50單位。這就是狗的最初的坐標以及新的當前坐標系(也因為狗改變)會如何顯示。注意小狗的新的坐標系統的原點不在狗邊界盒子的左上角。另外注意狗和它新的坐標系看起來它們好像移動到畫布新的一層上。
現在我們試一試其他事情。不再移動,試著縮放。我們將鸚鵡放大到兩倍尺寸:
<svg width="800" height="800" viewBox="0 0 800 600">
<g id="parrot" transform="scale(2)">
<!-- shapes and paths forming the parrot -->
</g>
<!-- ... -->
</svg>
放縮SVG元素和放縮HTML元素的結果不一樣。縮放后SVG元素的在視窗中的位置隨著縮放改變。下面圖片展示了把鸚鵡放大到兩倍時的結果。注意初始位置和尺寸,以及最終位置和尺寸。
從上面圖片中我們可以注意到不只鸚鵡的尺寸(寬和高)變成了兩倍,鸚鵡的坐標(x和y)也乘以了縮放因子(這里是兩倍)。
這個結果的原因我們之前已經提到了:元素當前坐標系發生變化,鸚鵡在新系統中繪制。所以,在這個例子中,當前坐標系被縮放。這個效果類似于使用viewBox = "0 0 400 300",等于“放大”了坐標系,因此把里面的內容放大到雙倍尺寸(如果你還沒有讀過請查看這個系列的第一部分)。
所以,如果我們把坐標系變換形象化來展現當前變換系統中的鸚鵡,我們會得到以下結果:
鸚鵡的新的當前坐標系統被縮放,同時“放大”鸚鵡。注意,在它當前的坐標系中,鸚鵡沒有重新定位-只有縮放坐標系統才會導致它在視窗中重定位。鸚鵡在新的縮放后的系統中按初始的x和y坐標被重繪。
讓我們嘗使用不同因子在兩個方向上縮放鸚鵡。如果我們添加transform="scale(2 0.5)縮放鸚鵡,我們把寬度變為兩倍高度為原來的一半。效果和添加viewBox="0 0 400 1200"類似。
注意一下鸚鵡在傾斜后的坐標系中的位置,并且把它和初始系統(半透明的鸚鵡)中的位置做比較:x和y位置坐標保持不變。
在SVG中傾斜元素也導致元素被“移動”,因為它當前的坐標系統被傾斜了。
試想我們使用skewX函數沿x軸給一只狗增加一個傾斜變化。我們在垂直方向上把狗傾斜了25度。
<svg width="800" height="800" viewBox="0 0 800 600">
<!-- ... -->
<g id="dog" transform="skewX(25)">
<!-- shapes and paths forming the dog -->
</g>
</svg>
下列圖片展示了對小狗添加傾斜變換的結果。
注意到狗的位置對比初始位置也改變了,因為它的坐標系也被傾斜了。
下面的圖片展示了同樣角度的情況下使用skewY()而不是skewX傾斜狗的情況:
最后,讓我們嘗試旋轉鸚鵡。旋轉默認的中心是當前用戶坐標系的左上角。新的建立在旋轉元素上的當前系統也被旋轉了。在下面的例子中,我們將把鸚鵡旋轉45度。旋轉方向為順時針。
<svg width="800" height="800" viewBox="0 0 800 600">
<g id="parrot" transform="rotate(45)">
<!-- shapes and paths forming the parrot -->
</g>
<!-- ... -->
</svg>
添加上述變換的結果如下:
你很可能想要圍繞默認坐標系原點之外的點來旋轉一個元素。在transform屬性中使用rotate()函數,你可以聲明這個點。試想在這個例子中我們想按照它自己的中心旋轉這個鸚鵡。根據鸚鵡的寬、高以及位置,我精確計算出它的中心在(150,170)。這個鸚鵡可以圍著它的中心旋轉。
<svg width="800" height="800" viewBox="0 0 800 600">
<g id="parrot" transform="rotate(45 150 170)">
<!-- shapes and paths forming the parrot -->
</g>
<!-- ... -->
</svg>
在這個時候,這只鸚鵡會被旋轉并且看起來如下:
我們說變換添加在坐標系上,因此,元素最終被影響并且發生變換。那么究竟如何改變旋轉中心工作在坐標系的原點(0,0)的點呢?
當你改變中心或者旋轉時,坐標系被變換或者旋轉特定角度,然后再次根據聲明的旋轉中心產生特定變換。在這個例子中:
<gid="parrot"transform="rotate(45 150 170)">
被瀏覽器當成一系列的移動和旋轉等同于:
<gid="parrot"transform="translate(150 170) rotate(45) translate(-150 -170)">
當前坐標系變換到你想要的中心店。然后旋轉聲明的角度。最終系統被負值變換。上述添加到系統的變換如下:
在我們進行下一部分討論嵌套和組合變換前,我想請大家注意建立在變換元素上的當前用戶坐標系是獨立于建立在其他變換元素之上的其他坐標系的。下列圖片展示了建立在狗和鸚鵡上的兩個坐標系,以及它們之間是如何保持獨立的。
另外注意每個當前坐標系仍然處于在外層容器中使用viewBox屬性建立的畫布的主要坐標系中。任何添加到viewBox上的變換會影響整個畫布以及所有里面的元素,不管它們是否建立了自己的坐標系。
例如,以下是把整個畫布的用戶空間從viewBox="0 0 800 600"改成 viewBox="0 0 600 450"的結果。整個畫布被縮放,保持任何添加到獨立元素上的變換。
嵌套和組合變換
很多時候你可能想要在一個元素上添加多個變換。添加多個變換意味著“組合”變換。
當變換組合時,最重要的是意識到,和HTML元素變換一樣,當這個系統發生了之前的變換后在添加下一個變換到坐標系中。
例如,如果你要在一個元素上添加旋轉,接下來移動,移動變換會根據新的坐標系統,而不是初始的沒有旋轉時的系統。
下面了例子就是做了這件事。我們先添加旋轉,然后沿x軸使用transform="rotate(45 150 170) translate(200)"把鸚鵡移動200個單位。
取決于最終的位置和變換,你可以根據需要組合變換。總是記住坐標系。
注意當你傾斜一個元素-以及它的坐標系統-坐標系統不再是最初的那個,坐標系不再會按照最初的來計算-它將會是傾斜后的坐標系。簡單來說,這意味著坐標系原點不再是90度的角,新的坐標會根據新的角度來計算。
當變換元素的子元素也需要變換時會發生變換嵌套。添加到子元素上的變換會累積父元素上添加的變換和它本身的變換。
[對于嵌套轉換,]應用于子元素的轉換將是應用于其祖先元素和應用于其祖先元素的轉換的累加。
所以,效果上來說,嵌套變化類似于組合:唯一區別是不像在一個元素上添加一系列的變化,它自動從父元素上獲得變換,最后執行添加在它自身的變換,就像我們在上面添加的變換一樣-一個接一個。
這對于你想要根據另外一個元素變換一個元素時尤其有用。例如,試想你想要給小狗的尾巴設定一個動畫。這個尾巴是#dog組的后代。
<svg width="800" height="800" viewBox="0 0 800 600">
<!-- ... -->
<g id="dog" transform="translate(..)">
<!-- shapes and paths forming the dog -->
<g id="head">
<!-- .. -->
</g>
<g id="body" transform="rotate(.. .. ..)">
<path id="tail" d="..." transform="rotate(..)">
<!-- animateTransform here -->
</path>
<g id="legs">
<!-- ... -->
</g>
</g>
</g>
</svg>
試想我們變換dog組;圍繞某一點把它的身體旋轉一定角度,然后我們想要再把尾巴旋轉一定角度。
當尾巴被旋轉后,它從祖先(#body)身上“繼承”了變換坐標系,也從祖先(#dog)身上繼承了變換坐標系,然后旋轉(和#body組一樣的旋轉)然后在發生自身的旋轉。這里添加的一系列變換的效果類似于我們之前在上述組合變換例子中解釋的。
所以,你看,在#tail上嵌套變換實際上和組合變換有一樣的效果。
使用CSS屬性變換SVGs
在SVG2中,transform屬性簡稱transform屬性;因為SVG變換已經被引入CSS3變換規范中。后者結合了SVG變化,CSS2 2D變換和CSS 3D變換規范,并且把類似transform-origin 和 3D transformations引入了SVG。
聲明在CSS變換規范中的CSS變換屬性可以被添加到SVG元素上。然而,transform屬性函數值需要遵循CSS規范中的語法聲明:函數參數必須逗號隔開-空格隔開是不允許的,但是你可以在逗號前后引用一兩個空格;rotate()函數不接受<cx>和<cy>值-旋轉中心使用transform-origin屬性聲明。另外,CSS變換函數接受角度和坐標單位,例如角度的rad(radians)和坐標的px,em等。
使用CSS來旋轉一個SVG元素看起來如下:
#parrot {
transform-origin: 50% 50%; /* center of rotation is set to the center of the element */
transform: rotate(45deg);
}
SVG元素也可以使用CSS 3D變換在三維空間中變換。依然要注意坐標系,然而,不同于建立在HTML元素上的坐標系。這意味著3D旋轉看起來也不同除非改變旋轉中心。
#SVGel {
transform: perspective(800px) rotate3d(1, 1, 0, 45deg);
}
因為通過CSS來變換SVG元素非常類似于通過CSS來變換HTML元素,-語法層面-在這篇文章中我將跳過這個部分。
另外,在寫這篇文章的時候,在一些瀏覽器中實現一些特性是不可能的。因為瀏覽器支持改變很快,我建議你實驗一下這些屬性來決定哪些可以工作哪些不可以,決定什么現在可以用什么不可以。
注意一旦CSS變換可以完全實現在SVG上,我依然建議你使用CSS變換函數語法即使你用transform屬性的形式添加變換。也就是說,上面提到的transform屬性函數的語法還是有效的。
動畫transform
SVG變換可以變成動畫,就像CSS變換一樣。如果你使用CSS transform屬性來產生SVG變換,你可以像在HTML元素上創建CSS變換動畫一樣使用CSS動畫把這些變換變成動畫。
SVG transform屬性可以用SVG元素來做成動畫。元素是SVG中三個用來給不同的SVG屬性設置動畫的元素之一。
<animateTransform>元素的詳細信息超出了本文的范圍。請繼續關注我將撰寫的一篇關于SVG動畫元素(包括<animateTransform>)的文章。
最后的話
學習SVGs一開始可能非常困惑,如果對于坐標系變換里的內容不是很清楚,尤其是如果你帶著CSS HTML變換的背景知識,自然而然希望SVG元素和HTML元素的變換一樣。
然而,一旦你意識到它們的工作方式,你能更好得控制SVG畫布,并且輕易操縱元素。
這一系列的最后部分,我將討論嵌套SVGs和建立新的viewports和viewboxes。敬請關注!
本文根據SaraSoueidan的《Understanding SVG Coordinate Systems and Transformations (Part 2) — The transform Attribute》一文所譯,整個譯文帶有我們自己的理解與思想,如果譯得不好或有不對之處還請同行朋友指點。如需轉載此譯文,需注明英文出處http://sarasoueidan.com/blog/svg-transformations/。