一、HTML transform和SVG transform
SVG中自帶transform
屬性,沒錯,是屬性,例如:
1 2 3 |
<svg width="200" height="150"> <rect x="30" y="30" width="120" height="90" transform="rotate(45)"></rect> </svg> |
普通的HTML元素沒有transform
屬性,但是支援CSS3的transform
, 好奇的小夥伴可能會疑問了,CSS3中的transform
變換,跟SVG中的transform
是什麼關係呢?
恩,有點類似於謝霆鋒和陳冠希之間的關係,有些小複雜。
OK, 先說說相似之處吧。
一些基本的變換型別是一樣的,包括:位移translate
, 旋轉rotate
, 縮放scale
, 斜切skew
以及直接矩陣matrix
. 但只侷限於2D層面的變換。SVG似乎只支援二維變換(若有不對,歡迎指正),且類似translateX
, rotateX
也都是不支援的。
下面就是不一樣的地方了:
1. CSS3 transform
一般用在普通元素上,雖然也可以應用在SVG元素上,但是IE瀏覽器(IE edge未測試)卻不支援SVG元素;
1 2 3 4 |
rect { /* IE說:你這是弄啥來? */ transform:rotate(45deg); } |
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
代替),當多個引數值的時候,可以使用逗號,
或者直接空格分隔,但是不能包含單位,例如下面這種寫法直接翹辮子:
1 |
transform="translate(30px 12px)" |
下面這種無單位寫法才可以:
1 2 |
transform="translate(30 12)" transform="translate(30, 12)" |
另外,和CSS3的transform
一樣,SVG中的translate
位移也是支援多宣告累加的。例如:
1 |
transform="translate(30 12) translate(30 12)" |
等同於:
1 |
transform="translate(60 24)" |
需要注意的是,倆個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
,例如:
1 |
transform="rotate(45)" |
具體語法為: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,我們稍微修改下,就能讓矩形圍繞自己旋轉了,見下:
1 2 3 |
<rect x="30" y="30" width="120" height="90" rx="10" ry="10" fill="#a0b3d6" transform="rotate(45, 90 75)"></rect> <!-- 90 = 30 + 120/2 --> <!-- 75 = 30 + 90/2 --> |
您可以狠狠地點選這裡:SVG元素也圍繞自身中心點旋轉demo
使用原理圖表示就是下面這樣(左HTML旋轉,右SVG元素偏移旋轉):
同樣的,rotate
旋轉可以多個值並存,例如下面的CSS和attribute用法:
1 2 |
transform: rotate(45deg) rotate(-45deg); transform="rotate(45) rotate(-45) |
然而,需要注意的是,SVG屬性的transform
宣告的中心變換座標是不能共享的。
因此,雖然transform="rotate(45, 90 75)"
是中心點旋轉,但是,後面再新增其他東西,例如:rotate(-45)
則偏移值忽略,終中心點還是SVG的左上角(0,0)
位置,好慘!
例如原來的45度旋轉,再加個-45度反向旋轉:
1 |
<rect x="30" y="30" width="120" height="90" rx="10" ry="10" fill="#a0b3d6" transform="rotate(45, 90 75) rotate(-45)"></rect> |
結果位置回去了?才怪呢!
CSS的是又回去了,但是SVG的確是掛在月球上了。究其原因是rotate(-45)
又是相對SVG左上角變換的啦!
您可以狠狠地點選這裡:SVG連續旋轉demo
雖然乍看上去,好像SVG的座標系統有些怪怪的,但是,實際上,在有些需求場景下,SVG這種看似獨立的偏移系統更容易實現一些功能。
比方說,我們希望某個SVG元素先以右下角為中心旋轉90度,然後再以右上角為中心旋轉90度,該怎麼處理?
對於SVG transform
,我們直接面向需求寫數值就可以了。假設我們的SVG元素的高寬是120*90, 左上角座標是(30,30), 則,顯然,右下角座標是(150,120), 右上角座標是(150,30),於是,我們的transform
值就很簡單:
1 |
transform="rotate(90, 150 120) rotate(90 150 30)" |
參見下面的示意圖(示意圖的座標與上面略有出入,但不影響原理示意):
但是,如果我們使用之前容易理解的CSS3來實現,反而就複雜了,因為CSS3中的transform的變換點只能一次性指定,如果要實現不同變換點的旋轉效果,只能通過translate
再次偏移,例如,實現上面的右下角再右上角90度旋轉,則要這樣:
1 2 3 4 5 6 |
transform-origin: right bottom; /* 或者 100% 100% */ transform: rotate(90deg) translate(0, -100%) /* 從右下到右上 */ rotate(90deg) translate(0, 100%); |
Gif示意下就是:
顯然要麻煩很多。可見,兩種座標系統沒有絕對的優劣。
您可以狠狠地點選這裡:右下再右上旋轉90度demo
上圖為兩種變換的最終效果,雖然最終的效果是一樣的,但是,從理解上而言,這回,是SVG的transform
反而更容易理解。還是那句話,辯證看問題,凡事無絕對。
四、SVG transform scale縮放
SVG中的縮放的語法就比較單純了,就scale(sx[, sy])
, sx
表示橫座標縮放比例,sy
表示縱座標縮放比例。其中sy
是可預設的,如果缺失,表示使用和sx
一樣的值,也就是等比例縮放。其中,sx
和sy
兩個引數可以逗號分隔,也可以使用空格分隔。這和CSS3中的使用有所不同,兩外,SVG transform
屬性值縮放是不支援scaleX
, scaleY
這些鬼的。
同樣的,CSS控制的transform和SVG元素屬性控制的transform的座標系統是不一樣的。一個預設元素中心(下圖左),一個是SVG畫布左上角(下圖右),於是(from css-tricks):
因此,當我們對SVG元素scale縮放時候,發現位置也有出乎我們意料,就應該知道是怎麼回事了。
rotate
旋轉雖然也是迥異座標,但是其引數自帶偏移引數,我們我們移個花接個木,還是可以得到我們想要的結果。但是,scale
縮放這裡,就要悲慘很多了,沒有自帶偏移引數,於是,當我們要實現SVG元素居中縮放的效果,還需要使用translate
手動偏移。
怎麼個手動偏移法呢?即使先translate其中心點位置到元素的中心座標位置,然後縮放,然後座標再反方向還原回去。例如,某元素中心點座標是(95, 75)
, 垂直縮放1.5倍的效果則是:
1 |
transform="translate(95 75) scale(1, 1.5) translate(-95 -75)" |
您可以狠狠地點選這裡:CSS transform和SVG transform scale縮放demo
對應的CSS程式碼就簡單多了,直接:
1 2 3 |
.scale { transform: scale(1, 1.5); } |
然後最終效果都是一樣的:
使用Gif原理示意如下:
五、SVG transform skew斜切
先來了解下CSS中的斜切,斜切,如果單純切一個方向,我們可以看成把矩形變成了平行四邊形,其總面積不變化。
以純X軸變換舉例,skewX(-45deg)
則下面這樣子(灰色方塊為原始位置):
skewX(45deg)
則下面這樣子:
對於SVG的skew斜切變換,表現效果原理是一樣的。但是,使用的語法卻相當有意思。
在前面的一些變換中,例如位移、縮放之類是不支援translateX
, scaleX
這種CSS常見用法的,但是這裡的skew
卻有點讓人哭笑不得:不支援skew(x[, y])
這種語法,而只能是skewX
或者skewY
.
別問我為什麼?我只是大自然的搬運工,我不生產語法。
因此,沒有:
1 |
<del datetime="2015-10-10T12:49:32+00:00">transform="skew(45, 0)"</del> |
只有:
1 |
transform="skewX(45)" |
同樣的,由於變換中心點的差異,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步曲:
1 |
translate(140 105) rotate(45) translate(-140 -105) → translate(140 105) rotate(45) |
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(矩陣)”,一脈相承的。
我也是初學者,出錯在所難免,歡迎指正!
感謝閱讀,歡迎交流!