理解SVG transform座標變換

發表於2015-10-11

一、HTML transform和SVG transform

SVG中自帶transform屬性,沒錯,是屬性,例如:

普通的HTML元素沒有transform屬性,但是支援CSS3的transform, 好奇的小夥伴可能會疑問了,CSS3中的transform變換,跟SVG中的transform是什麼關係呢?

恩,有點類似於謝霆鋒和陳冠希之間的關係,有些小複雜。

OK, 先說說相似之處吧。
一些基本的變換型別是一樣的,包括:位移translate, 旋轉rotate, 縮放scale, 斜切skew以及直接矩陣matrix. 但只侷限於2D層面的變換。SVG似乎只支援二維變換(若有不對,歡迎指正),且類似translateXrotateX也都是不支援的。

下面就是不一樣的地方了:
1. CSS3 transform一般用在普通元素上,雖然也可以應用在SVG元素上,但是IE瀏覽器(IE edge未測試)卻不支援SVG元素;

2. HTML元素的CSS3 transform和SVG的transform座標系統大相徑庭;

平常我們使用transform其座標是相對於當前元素而言的,預設是元素的中心點變換,我們可以通過transform-origin屬性改變變換的中心點。而SVG中的transform的座標變換的是相對於畫布的左上角計算的,跟HTML的transform差別較大,理解上也更加麻煩。而本文就是徹底理清SVG中的transform到底是怎麼工作的。

3. 具體的語法細節有差異。SVG transform屬性語法有些自帶偏移。而CSS transform則更加純粹些。

//zxx: 據說CSS的transform和SVG的transform屬性即將合併。

二、SVG transform translate位移

我們先來看下最簡單最基本的translate位移變換,例如,我們偏移(295,115)大小的位置,HTML元素的偏移(下圖左)和SVG元素的偏移(下圖右)就會不一樣。一個是相對自己的中心點(下圖左),一個是SVG的左上角(下圖右)。

雖然兩者的相對位置不一樣,但是,對於單純地位移來講,無論你相對於那個點位置,實際偏移的位置都是一樣的,因此,從表現上講,兩者最終的位置看上去還是一樣的。

您可以狠狠地點選這裡:HTML translate和SVG translate比對demo

前面我們提到過,SVG元素也能使用CSS3的transform進行變換(非IE瀏覽器),但是隻能支援2D層面的幾個屬性,例如translateX(tx),translateY(ty)以及translate(tx[, ty])translateZ(tz)則並不支援。

如果我們使用SVG元素自帶的transform屬性進行變換,則僅支援translate(tx[ ty])這種用法(預設使用0代替),當多個引數值的時候,可以使用逗號,或者直接空格分隔,但是不能包含單位,例如下面這種寫法直接翹辮子:

下面這種無單位寫法才可以:

另外,和CSS3的transform一樣,SVG中的translate位移也是支援多宣告累加的。例如:

等同於:

需要注意的是,倆個translate中間不要混有其他的transform變換。否則,最終的位移就不是簡單的相加了。

三、SVG transform rotate旋轉

上面的位移變換,我們似乎沒看到明顯的不一樣。但是,從這裡的旋轉變換開始,就可以看出明顯的差異了。

下面圖示的是基本的45度旋轉(來自css-tricks)(左HTML元素,右SVG元素):

由於SVG元素的預設是SVG左上角為中心變換的,因此,矩形旋轉的幅度就有了過山車的感覺。

您可以狠狠地點選這裡:HTML rotate和SVG rotate比對demo

結果會發現,兩者位置大相徑庭了:

CSS transform中的rotate語法比較直白:rotate(angle),就一個angle引數,表示角度大小,不過必須要有單位,比如deg(度), turn(圈), grad(百分度 – 一種角的測量單位,定義為一個圓周角的1/400。常用於建築或土木工程的角度測量),甚至可以使用calc()計算,例如:calc(.25turn - 30deg).

但是,SVG中的屬性transform旋轉就沒有這麼多花頭,單位?哦,別逗了,毛線都沒有,直接光禿禿的數值,表示角度deg,例如:

具體語法為:rotate(angle[ x y]). 大家注意到沒有,這裡有個[ x y][]表示這個可選引數。也就是說,SVG中的rotate旋轉比CSS的rotate多了一個可選引數,那這個可選引數[ x y]表示什麼意思呢?

告訴你,是非常有用的東西。用來偏移transform變換中心點的。

為什麼說非常有用呢?SVG元素預設是基於左上角的,但是我們的旋轉元素往往都在SVG的中間區域,此時旋轉跨度太大,智商餘額不足的我們就腦補不過來,此時難免希望可以像CSS的transform變換一樣,圍繞元素的中心點變換。怎麼辦?

我們可以藉助CSS3 transform-origin修改SVG元素預設的變換中心點,然後配合CSS變換。但是,我們前面多次提到,IE瀏覽器的SVG元素不識別CSS中的transform. 所以,從相容性上講,CSS策略是不可行的。難道就沒有其他辦法了,有,就是這裡的可選引數[ x y],通過對變換中心點的偏移修正,我們也能讓SVG元素圍繞自身的中心點旋轉了。

所以,上面的demo,我們稍微修改下,就能讓矩形圍繞自己旋轉了,見下:

您可以狠狠地點選這裡:SVG元素也圍繞自身中心點旋轉demo

使用原理圖表示就是下面這樣(左HTML旋轉,右SVG元素偏移旋轉):

同樣的,rotate旋轉可以多個值並存,例如下面的CSS和attribute用法:

然而,需要注意的是,SVG屬性的transform宣告的中心變換座標是不能共享的。

因此,雖然transform="rotate(45, 90 75)"是中心點旋轉,但是,後面再新增其他東西,例如:rotate(-45)則偏移值忽略,終中心點還是SVG的左上角(0,0)位置,好慘!

例如原來的45度旋轉,再加個-45度反向旋轉:

結果位置回去了?才怪呢!

CSS的是又回去了,但是SVG的確是掛在月球上了。究其原因是rotate(-45)又是相對SVG左上角變換的啦!

您可以狠狠地點選這裡:SVG連續旋轉demo

雖然乍看上去,好像SVG的座標系統有些怪怪的,但是,實際上,在有些需求場景下,SVG這種看似獨立的偏移系統更容易實現一些功能。

比方說,我們希望某個SVG元素先以右下角為中心旋轉90度,然後再以右上角為中心旋轉90度,該怎麼處理?

對於SVG transform,我們直接面向需求寫數值就可以了。假設我們的SVG元素的高寬是120*90, 左上角座標是(30,30), 則,顯然,右下角座標是(150,120), 右上角座標是(150,30),於是,我們的transform值就很簡單:

參見下面的示意圖(示意圖的座標與上面略有出入,但不影響原理示意):

但是,如果我們使用之前容易理解的CSS3來實現,反而就複雜了,因為CSS3中的transform的變換點只能一次性指定,如果要實現不同變換點的旋轉效果,只能通過translate再次偏移,例如,實現上面的右下角再右上角90度旋轉,則要這樣:

Gif示意下就是:

顯然要麻煩很多。可見,兩種座標系統沒有絕對的優劣。

您可以狠狠地點選這裡:右下再右上旋轉90度demo

上圖為兩種變換的最終效果,雖然最終的效果是一樣的,但是,從理解上而言,這回,是SVG的transform反而更容易理解。還是那句話,辯證看問題,凡事無絕對。

四、SVG transform scale縮放

SVG中的縮放的語法就比較單純了,就scale(sx[, sy])sx表示橫座標縮放比例,sy表示縱座標縮放比例。其中sy是可預設的,如果缺失,表示使用和sx一樣的值,也就是等比例縮放。其中,sxsy兩個引數可以逗號分隔,也可以使用空格分隔。這和CSS3中的使用有所不同,兩外,SVG transform屬性值縮放是不支援scaleXscaleY這些鬼的。

同樣的,CSS控制的transform和SVG元素屬性控制的transform的座標系統是不一樣的。一個預設元素中心(下圖左),一個是SVG畫布左上角(下圖右),於是(from css-tricks):

因此,當我們對SVG元素scale縮放時候,發現位置也有出乎我們意料,就應該知道是怎麼回事了。

rotate旋轉雖然也是迥異座標,但是其引數自帶偏移引數,我們我們移個花接個木,還是可以得到我們想要的結果。但是,scale縮放這裡,就要悲慘很多了,沒有自帶偏移引數,於是,當我們要實現SVG元素居中縮放的效果,還需要使用translate手動偏移。

怎麼個手動偏移法呢?即使先translate其中心點位置到元素的中心座標位置,然後縮放,然後座標再反方向還原回去。例如,某元素中心點座標是(95, 75), 垂直縮放1.5倍的效果則是:

您可以狠狠地點選這裡:CSS transform和SVG transform scale縮放demo

對應的CSS程式碼就簡單多了,直接:

然後最終效果都是一樣的:

使用Gif原理示意如下:

五、SVG transform skew斜切

先來了解下CSS中的斜切,斜切,如果單純切一個方向,我們可以看成把矩形變成了平行四邊形,其總面積不變化。

以純X軸變換舉例,skewX(-45deg)則下面這樣子(灰色方塊為原始位置):

skewX(45deg)則下面這樣子:

對於SVG的skew斜切變換,表現效果原理是一樣的。但是,使用的語法卻相當有意思。

在前面的一些變換中,例如位移、縮放之類是不支援translateXscaleX這種CSS常見用法的,但是這裡的skew卻有點讓人哭笑不得:不支援skew(x[, y])這種語法,而只能是skewX或者skewY.

別問我為什麼?我只是大自然的搬運工,我不生產語法。

因此,沒有:

只有:

同樣的,由於變換中心點的差異,CSS實現的變換和SVG屬性變換往往最後的位置是不一樣的:

不僅如此,如果元素的中心點不是就是SVG的左上角,則skewX(α1) skewX(α2)的最終位置和skewX(α1 + α2)是不一樣的(位移和旋轉不會這樣子)。

您可以狠狠地點選這裡:CSS SVG transform skew斜切差異及連續斜切差異demo

正如demo所示,CSS的和SVG的位置差異很大:

SVG的連續變換和一次性變換的位置也是不一樣的:

可能有人要疑問,為何連續斜切變換和一次性變換位置會不一樣?其實原因很簡單,因為斜切的角度和元素偏移大小並不是線性的,比方說,從70到80度和80度到90度之間的位移大小(雖然都是10度的變化區間)是顯然不是一個檔次的。因此,分開多次連續斜切最終的座標偏移要比一次性偏移來得小。

最後,和縮放一樣,你想要讓SVG元素中心點斜切,可以先translate偏移,在skew變換。就不重複舉例演示了。

六、其他居中變換處理

像縮放、斜切這些SVG變換,想要如CSS transform-origin:50% 50%一樣的中心點變換效果,需要事先位移,我們有沒有其他辦法呢?

這裡有兩個思路可供大家參考下。

1. 原始中心位置乃SVG左上角
拿45度旋轉舉例,我們可以把元素預設就放在中心點和SVG左上角重合的位置上,參見下面的gif演示:

於是,我們原來的3步曲就變成了2步曲:

2. 通過viewBox調整
viewBox可以用來改變SVG畫布的視區,這個我之前專門撰文介紹過,是SVG學習必備被深入理解的基礎知識,參見:“理解SVG的viewport,viewBox,preserveAspectRatio”一文。

我們可以把元素預設掛在左上角,然後,通過viewBox做手腳,讓元素呈現的位置並不是真正的左上角,例如應用viewBox='-140 -105 280 210',則變化如下示意圖:

此時,我們只需要讓元素旋轉就可以了,無需額外的做translate位移,見下gif:

七、結束語

本文介紹的內容實際上都還是非常基本的。實際SVG應用的時候,可能是多個變換參雜在一起,所以,如果本文介紹的幾個基本變換都沒搞清楚,到時候,想必是想破腦袋都不明白怎麼元素跑這裡了,怎麼變成這樣了!

本文的這些知識點雖然基本,但是相當重要的。再加上,不同的變換方法的語法細節還不一樣。有的自帶偏移,有的需要手動偏移等等;不同變換的前後位置不同,甚至同一變換分開連續變換和一次性變換的結果都不一樣等等;都要求大家要細心耐心閱讀。

本文內容和結構參考自:Transforms on SVG Elements. 但要比原文要精煉很多,同時,每一個變換都有親自實踐認證,因此,從品質上講,可能還要略高一籌。

對了,矩陣matrix沒有細說過,這個可以參考我之前的文章:“理解CSS3 transform中的Matrix(矩陣)”,一脈相承的。

我也是初學者,出錯在所難免,歡迎指正!

感謝閱讀,歡迎交流!

相關文章