我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第7篇文章。
transform
作为CSS
世界中最重要的属性之一,它的出现让原本枯燥单调的HTML
变得生动了起来。
那么,为了图形展示而生的SVG
当然不会屈居人后,要知道,SVG
中的元素是自带transform
属性的,但SVG transform
与 CSS transform
之间有着千丝万缕又截然不同的关系。
不过,那片瑰丽的前端世界,既然CSS transform
可往,SVG transform
亦可往。
SVG transform translate(位移)
如图所示,左侧CSS
的translate
是相对自己的中心点
移动,而右侧SVG
是相对于视窗的左上角
进行移动,虽然两者的相对位置不一样,但是,对于单纯地位移来讲,无论你相对于哪个点位置,实际偏移的位置都是一样的,因此,从表现上讲,两者最终的位置看上去还是一样的。
但从本质上来讲,两者区别很大,SVG
自带的transform
进行translate
变换时,仅支持translate(tx[ ty])
这种用法(缺省使用0
代替),当变换多个参数值的时候,可以使用,
或者空格
分隔,且不能包含单位,只能为纯数值,而且支持2D层面的属性,CSS中的translateX(tx)
, translateY(ty)
以及translate(tx[, ty])
,translateZ(tz)
并不支持。
另外,SVG
中的translate
位移和CSS
相同,都是支持多声明累加的,例如:
transform="translate(30 20) translate(30 20)"
等价于
transform="translate(60 40)"
需要注意的是,两个translate
中间不要混有其他的transform
变换。否则,最终的位移就不是简单的相加了,至于为什么后面会提到。
SVG transform rotate(旋转)
上面的translate
变换,从最终效果上似乎CSS
和SVG
的区别不大。但是,从rotate
旋转变换开始,区别就愈发明显了。
图示的45°
旋转(左HTML
元素,右SVG
元素),之所以差别如此之大,还是源于相对的中心点不同。
另外,和CSS
中旋转rotate(angle)
需要带单位不同(例如eg(度), turn(圈), grad(百分度)),SVG只能用数值来表示:
transform="rotate(45)"
具体语法为:rotate(angle[ x y])
,大家注意到没有,这里有个[ x y]
, []
表示这个可选参数。也就是说,SVG
中的rotate
旋转比CSS
的rotate
多了一个可选参数,那这个可选参数[ x y]
表示什么意思呢?
这个可选参数y
是用来偏移transform
变换中心点的,所以通过对变换中心点的偏移修正,我们也能让SVG
元素围绕自身的中心点旋转了。
<rect x="30" y="30" width="120" height="90" rx="10" ry="10"
fill="#a0b3d6" transform="rotate(45, 90 75)">
</rect>
与translate
一样,rotate
旋转可以多个值并存:
transform="rotate(45) rotate(-45)"
不过,与CSS
不同的是,SVG
属性的transform
声明的中心变换坐标是不能共享的,什么意思呢?
就是说第一次旋转我们设置了中心点
后,第二次旋转时中心点再次回到了左上角
,例如:
<rect x="30" y="30" width="120" height="90" rx="10" ry="10"
fill="#a0b3d6" transform="rotate(45, 90 75) rotate(-45)">
</rect>
如果是CSS
的话,转45°
后再反向转-45°
,就会回到原位才对,但是对SVG
来说:
就变成了这样,究其原因还是因为“万恶”的左上角
。
虽然乍看上去,好像SVG
的坐标系统有些怪怪的,但是,实际上,在有些需求场景下,SVG
这种看似独立的偏移系统更容易实现一些功能。
比方说,我们希望某个SVG
元素先以右下角为中心旋转90°
,然后再以右上角为中心旋转90°
,该怎么处理?
对于SVG
的transform
,我们直接面向需求写数值就可以了。假设我们的SVG
元素的高宽是120*90
, 左上角坐标是(30,30)
, 则,显然,右下角坐标是(150,120)
, 右上角坐标是(150,30)
,于是,我们的transform
值就很简单:
transform="rotate(90, 150 120) rotate(90 150 30)"
但是,如果我们使用之前容易理解的CSS3
来实现,反而就复杂了,因为CSS3
中的transform
的变换点只能一次性指定,如果要实现不同变换点的旋转效果,只能通过translate
再次偏移,例如,实现上面的右下角再右上角90°
旋转,则要这样:
transform-origin: right bottom; /* 或者 100% 100% */
transform:
rotate(90deg)
translate(0, -100%) /* 从右下到右上 */
rotate(90deg)
translate(0, 100%);
单纯从这个案例来看,显然CSS
的方案要麻烦很多。可见,无论SVG
的奇葩左上角还是CSS
的便捷中心点,这两种坐标系统没有绝对的优劣,具体的如何抉择需要是需求而定。
SVG transform scale(缩放)
SVG
中的scale
缩放语法相比于前两个就太单纯了scale(sx[, sy])
, sx
表示横坐标缩放比例,sy
表示纵坐标缩放比例。其中sy
是可缺省的,如果缺失,表示使用和sx
一样的值,也就是等比例缩放。其中,sx
和sy
两个参数可以用,
或空格
分隔。这和CSS3
中的使用有所不同,另外,SVG transform
属性值缩放是不支持scaleX
, scaleY
这些属性的。
老生常谈的相对位置依旧是最大的不同,左为HTML
,右为SVG
:
可以看到,由于相对位置是左上角,导致SVG scale
缩放后位置已经发生了偏移,所以如果我们想要实现居中缩放的效果需要再配合上translate
才可以,例如:
现在,大家应该知道CSS
的scale
有多香了吧。
SVG transform skew(斜切)
SVG
中skew
的语法相当有意思,在前面的一些变换中,例如位移、缩放之类是不支持translateX
, scaleX
这种CSS
常见用法的,但是这里的skew
却有点让人哭笑不得:不支持skew(x[, y])
这种语法,而只能是skewX
或者skewY
。
所以,SVG
斜切的语法只有:
transform="skewX(45)"
同样的,由于相对位置的差异,CSS
实现的变换和SVG
属性变换往往最后的位置是不一样的:
不仅如此,如果元素的中心点是SVG
的左上角,则skewX(α1) skewX(α2)
的最终位置和skewX(α1 + α2)
是不一样的(注:上面说的位移、旋转和缩放不会这样子):
因为斜切的角度和元素偏移大小并不是线性的,比方说,从70°
到80°
和80°
到90°
之间的位移大小,虽然都是相差10°,但是坐标偏移显然不是一个档次的。因此,分开多次连续斜切最终的坐标偏移要比一次性偏移来得小。
好的,SVG
中的四种变换我们就已经都说完了,现在让我们来总结一下:
区别 | SVG transform | CSS transform |
---|---|---|
相对位置 | 左上角 | 中心点 |
3D变换 | 不支持 | 支持 |
包含单位 | 不支持 | 支持 |
多参数空格分隔 | 支持 | 不支持 |
中心点共享 | 不支持 | 支持 |
translateX | 不支持 | 支持 |
translateY | 不支持 | 支持 |
scaleX | 不支持 | 支持 |
scaleY | 不支持 | 支持 |
rotateX | 不支持 | 支持 |
rotateY | 不支持 | 支持 |
skew(x, y) | 不支持 | 支持 |
SVG transform变换处理的思路
通过上面的案例,我们已经了解了scale
缩放、skew
斜切这些SVG
变换有多别扭,要是想如CSS transform-origin:50% 50%
一样的中心点变换那般方便简洁,有没有什么好的办法呢?
当然有,具体有两个思路:
- 原始中心位置即
SVG
左上角:
原先我们想实现一个围绕中心点的45°
旋转,需要3
步:
translate(140 105) rotate(45) translate(-140 -105)
现在,通过将元素初始中心点就设置在视窗的左上角,原本的3
步就简化为了2
步:
translate(140 105) rotate(45)
- 调整
viewBox
,其实这个思路与第1个思路的本质是一致的,只是实现方式不同而已:
我们可以通过调整viewBox
将(0,0)
坐标设置为视窗中心,再让元素的中心点为(0,0)
,这样就只需要直接旋转就好了。
是不是思路一下就清晰了很多,如果这两种思路依旧觉得很麻烦,到底有没有类似CSS transform
那样方便易懂的实现方案呢?
有的,类似CSS transform
的方案就是使用CSS transform
它啊,还记得我们之前提到过,在SVG
也算是HTML
大家庭的一份子,所以SVG
大部分属性都是和CSS
互通有无的,现在咱们就来试试,走着。
CSS transform 操作SVG
<svg width="200" height="200" viewBox="0 0 100 100">
<path d="M 50 1 99 50 50 99 1 50 Z" fill="none" stroke="black" />
<circle cx="50" cy="50" r="49" fill="none" stroke="black" />
</svg>
这是一个普普通通的SVG
,现在我们想通过CSS transform
来操作path
标签来旋转45°
。
path {
transform: rotate(45deg);
}
可以看到,path
确实旋转了45°
,但整个path
大部分都已经超出了整个SVG
的视区范围,难道又是万恶的左上角?
没关系,现在可是CSS
的地盘,不会惯着SVG transform
那套规则,直接改个transform-origin
试试:
path {
transform: rotate(45deg);
transform-origin: 50% 50%;
}
果然,SVG transform
虽然霸道,但是在人家CSS
的场子也不敢随便造次,现在相对位置乖乖的变成了中心点,旋转也不再只支持数值也可以包含单位了,再试试其他限制:
中心点是否共享
path {
transform: rotate(30deg) rotate(15deg);
transform-origin: 50% 50%;
}
答案:支持。
是否可以3D变换
path {
transform: rotate3d(0, 1, 1, 45deg);
transform-origin: 50% 50%;
}
答案:支持。
是否支持 rotateX 或 rotateY
path {
transform: rotateX(45deg);
transform-origin: 50% 50%;
}
path {
transform: rotateY(45deg);
transform-origin: 50% 50%;
}
答案:支持。
全都可以,虽然没有全部试验一番,但是管中规豹,目前除了左上角的魔咒尚未打破之外,其余规则全部继承CSS transform
,现在我们可以得出一个结论。
使用
CSS transform
操作SVG
元素与操作普通HTML
元素,除了初始相对位置不同之外,其余并无二致。
CSS transform操作的区别 | SVG | HTML |
---|---|---|
初始相对位置 | 左上角 | 中心点 |
SVG transform的应用
说了这么多理论,那具体有啥用呢?别急,这不就来了嘛:
translate(位移)
<svg width="200" height="200"viewBox="0 0 100 100">
<rect x="5" y="5" width="40" height="40" fill="green" />
<rect x="5" y="5" width="40" height="40" fill="blue"
transform="translate(50)" />
<rect x="5" y="5" width="40" height="40" fill="red"
transform="translate(0 50)" />
<rect x="5" y="5" width="40" height="40" fill="yellow"
transform="translate(50,50)" />
</svg>
scale(缩放)
<svg width="200" height="200" viewBox="-50 -50 100 100">
<!-- uniform scale -->
<circle cx="0" cy="0" r="10" fill="red"
transform="scale(4)" />
<!-- vertical scale -->
<circle cx="0" cy="0" r="10" fill="yellow"
transform="scale(1,4)" />
<!-- horizontal scale -->
<circle cx="0" cy="0" r="10" fill="pink"
transform="scale(4,1)" />
<!-- No scale -->
<circle cx="0" cy="0" r="10" fill="black" />
</svg>
rotate(旋转)
<svg width="200" height="200" viewBox="-100 -100 200 200">
<g fill="none" stroke="red">
<rect id="myRect" x="-50" y="-50" width="100" height="100" />
<use href="#myRect" transform="rotate(5)" />
<use href="#myRect" transform="rotate(10)"/>
<use href="#myRect" transform="rotate(15)"/>
<use href="#myRect" transform="rotate(20)"/>
<use href="#myRect" transform="rotate(25)"/>
<use href="#myRect" transform="rotate(30)"/>
<use href="#myRect" transform="rotate(35)"/>
<use href="#myRect" transform="rotate(40)"/>
<use href="#myRect" transform="rotate(45)"/>
<use href="#myRect" transform="rotate(50)"/>
<use href="#myRect" transform="rotate(55)"/>
<use href="#myRect" transform="rotate(60)"/>
<use href="#myRect" transform="rotate(65)"/>
<use href="#myRect" transform="rotate(70)"/>
<use href="#myRect" transform="rotate(75)"/>
<use href="#myRect" transform="rotate(80)"/>
<use href="#myRect" transform="rotate(85)"/>
<use href="#myRect" transform="rotate(90)"/>
</g>
</svg>
skew(斜切)
<svg width="200" height="200" viewBox="-100 -100 200 200">
<g fill="red">
<rect x="-11.25" y="-50" width="25" height="100" transform="skewX(30)" />
<rect x="-11.25" y="-50" width="25" height="100" transform="skewX(-30)" />
</g>
</svg>
好了,以上这些都只是抛砖引玉,大家只要谨记SVG
中万恶的左上角,无论SVG transform
还是CSS transform
,规矩再多不也是任君采撷嘛。