SVG动画
这里首先推荐几个学习资源,在学习过程中,从以下教程收获了很多
SVG 动画开发实战 - 小蘿蔔丁的专栏 - 掘金 (juejin.cn)
SVG相关 << 张鑫旭-鑫空间-鑫生活 (zhangxinxu.com)
CSS 技术揭秘与实战通关 - Chokcoco - 掘金小册 (juejin.cn)
Web 动画之旅 - 大漠_w3cpluscom - 掘金小册 (juejin.cn)
本次主要通过一个小例子,讲解一个SVG旋转动画和变换原点问题。最后对于这个问题剖析,进而引入svg中viewport与viewBox
一、SVG中的旋转动画
在SVG中,可以直接用css动画来控制,但是旋转的旋转中心需要注意。 这里直接拿一张图片作为例子,实现一个简单的动画。素材地址: iconfont-房车
下面是最终的实现效果
话不多说,直接开干。 首先分析一下,这次我们的实现可以分为两部分车主体的颠簸动画以及前后轮子的旋转动画。
原图片的分组不太符合我们的需要,我们需要将图片分为三部分,车子主题和两个轮子,便于后续应用动画。这里直接使用Adobe Illustrator
软件,分别选中前轮和后轮,并进行组合。最后导出即可。
可以看到这里已经为三部分(三个g标签的内容)起好了类名
xml
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 422 240" xml:space="preserve" class="car">
<style type="text/css">
.st0{fill:#1A0B25;}
.st1{fill:#7C6E7B;}
.st2{fill:none;stroke:#7B7B7B;}
.st3{fill:#893163;}
.st4{fill:#C82D49;}
.st5{fill:#ED7475;}
.st6{fill:#E83D4E;}
.st7{fill:#D58C90;}
.st8{fill:#B886BA;}
.st9{fill:#F7BCA3;}
.st10{fill:#6AB4CE;}
.st11{fill:#E0E12B;}
.st12{fill:none;stroke:#7B7B7B;stroke-width:2;}
</style>
<g class="back-wheel wheel">
<path class="st0" d="M147.5,207.2c0,17.9-14.5,32.5-32.5,32.5s-32.5-14.5-32.5-32.5s14.5-32.5,32.5-32.5S147.5,189.3,147.5,207.2z"
/>
<path class="st1" d="M131.3,207.2c0,9-7.3,16.2-16.2,16.2s-16.2-7.3-16.2-16.2c0-9,7.3-16.2,16.2-16.2S131.3,198.3,131.3,207.2z"/>
<path class="st2" d="M82.6,207.2H115"/>
<path class="st2" d="M86.9,223.4l28.1-16.2"/>
<path class="st2" d="M98.8,235.3l16.2-28.1"/>
<path class="st2" d="M115,239.7v-32.5"/>
<path class="st2" d="M131.3,235.3L115,207.2"/>
<path class="st2" d="M143.1,223.4L115,207.2"/>
<path class="st2" d="M147.5,207.2H115"/>
<path class="st2" d="M143.1,191L115,207.2"/>
<path class="st2" d="M131.3,179.1L115,207.2"/>
<path class="st2" d="M115,174.8v32.5"/>
<path class="st2" d="M98.8,179.1l16.2,28.1"/>
<path class="st2" d="M86.9,191l28.1,16.2"/>
</g>
<g class="front-wheel wheel">
<path class="st0" d="M371.6,207.2c0,17.9-14.5,32.5-32.5,32.5s-32.5-14.5-32.5-32.5s14.5-32.5,32.5-32.5S371.6,189.3,371.6,207.2z"
/>
<path class="st1" d="M355.4,207.2c0,9-7.3,16.2-16.2,16.2c-9,0-16.2-7.3-16.2-16.2c0-9,7.3-16.2,16.2-16.2
C348.1,191,355.4,198.3,355.4,207.2z"/>
<path class="st2" d="M306.7,207.2h32.5"/>
<path class="st2" d="M311.1,223.4l28.1-16.2"/>
<path class="st2" d="M323,235.3l16.2-28.1"/>
<path class="st2" d="M339.2,239.7v-32.5"/>
<path class="st2" d="M355.4,235.3l-16.2-28.1"/>
<path class="st2" d="M367.3,223.4l-28.1-16.2"/>
<path class="st2" d="M371.6,207.2h-32.5"/>
<path class="st2" d="M367.3,191l-28.1,16.2"/>
<path class="st2" d="M355.4,179.1l-16.2,28.1"/>
<path class="st2" d="M339.2,174.8v32.5"/>
<path class="st2" d="M323,179.1l16.2,28.1"/>
<path class="st2" d="M311.1,191l28.1,16.2"/>
</g>
<g class="car-body">
<path class="st3" d="M219.7,49.5H63.2l-3.1,6.2h8.1v6.2h2.5v-6.2h138.5v6.2h2.5v-6.2h8.1V49.5z"/>
<path class="st4" d="M24.5,126.8L0,175.9l15.7,31.3h58.8c0-22.4,18.2-40.6,40.6-40.6s40.6,18.2,40.6,40.6h143
c0-22.4,18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6h10l31.2-62.5c1.9-3.8,1.7-8.4-0.5-12c-2.3-3.6-6.2-5.9-10.5-5.9H24.5z"/>
<path class="st5" d="M297,61.9H57l-32.5,64.9h304.9L297,61.9z"/>
<path class="st6" d="M139.3,68.1v106.6c8.5,6.4,14.2,15.8,15.8,26.3h58.4V68L139.3,68.1z"/>
<path class="st6" d="M219.7,68.1h74.2v133h-74.2V68.1z"/>
<path class="st7" d="M84.1,0h49.5v49.5H84.1V0z"/>
<path class="st8" d="M133.5,15.5h64.9v34h-64.9V15.5z"/>
<path class="st9" d="M225.9,133h18.5v6.2h-18.5V133z"/>
<path class="st9" d="M145.5,133h18.6v6.2h-18.6V133z"/>
<path class="st3" d="M3.3,201v6.2h71.2c0-4.2,0.7-8.4,1.9-12.4H9.5C6.1,194.8,3.3,197.6,3.3,201z"/>
<path class="st3" d="M155.6,207.2h143c0-4.2,0.6-8.4,1.9-12.4H153.7C155,198.9,155.6,203,155.6,207.2z"/>
<path class="st3" d="M402.1,194.9h-24.3c1.3,4,1.9,8.2,1.9,12.4h28.5V201C408.3,197.6,405.5,194.9,402.1,194.9z"/>
<path class="st10" d="M300.1,68.1L300.1,68.1v52.6h26.3L300.1,68.1z"/>
<path class="st10" d="M225.9,74.2h61.8v46.4h-61.8V74.2z"/>
<path class="st10" d="M145.5,74.2h61.8v46.4h-61.8V74.2z"/>
<path class="st10" d="M53.9,68.1l-26.3,52.6h105.5V68.1H53.9z"/>
<path class="st11" d="M310.6,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.1,0-3.9-1.7-3.9-3.9v-1.9
C306.7,147.1,308.5,145.4,310.6,145.4z"/>
<path class="st11" d="M329,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9H329c-2.2,0-3.9-1.7-3.9-3.9v-1.9
C325.1,147.1,326.9,145.4,329,145.4z"/>
<path class="st11" d="M347.4,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.2,0-3.9-1.7-3.9-3.9v-1.9
C343.5,147.1,345.3,145.4,347.4,145.4z"/>
<path class="st11" d="M365.8,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.1,0-3.9-1.7-3.9-3.9v-1.9
C361.9,147.1,363.7,145.4,365.8,145.4z"/>
<path class="st11" d="M384.2,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.1,0-3.9-1.7-3.9-3.9v-1.9
C380.3,147.1,382.1,145.4,384.2,145.4z"/>
<path class="st11" d="M402.6,145.4h1.9c2.2,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.1,0-3.9-1.7-3.9-3.9v-1.9
C398.7,147.1,400.5,145.4,402.6,145.4z"/>
<path class="st12" d="M150.3,136.1h11.3"/>
<path class="st12" d="M231.9,136.1h10.3"/>
</g>
</svg>
1.方式一:直接使用SVG
如果直接使用的话,我们只需要将上面代码粘贴到html中,然后编写对应的css。但这样也有弊端,因为svg代码很长,后面会介绍使用image标签引入的方式。
css
.car {
width: 500px;
height: 400px;
}
.car-body {
animation: carBounce 1s linear infinite;
}
.wheel {
transform-box: fill-box;
transform-origin: center;
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
@keyframes carBounce {
0% {
transform: translateY(0);
}
20% {
transform: translateY(-10px);
}
40% {
transform: translateY(0);
}
60% {
transform: translateY(-5px);
}
80% {
transform: translateY(0);
}
100% {
transform: translateY(0);
}
}
这里需要注意旋转时的旋转中心,transform-origin
相对于 svg
元素的视口框(viewBox
),而不是当前选中的元素自身 即,默认svg的元素旋转中心在左上角的坐标原点,若是为车轮设置transform-origin: center;
,则变成了整个svg画布的几何中心,而不是车轮的几何中心。为此我们需要设置transform-box: fill-box;
1.方式二:使用image标签嵌入svg
看到上面如此长的svg代码内嵌在网页中,倘若我们再写一些其他组件逻辑,总还是觉得不太美观。我们这样解决。
首先将一张图片分为三张svg。
后轮
xml
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 422 240" style="enable-background:new 0 0 422 240;" xml:space="preserve">
<style type="text/css">
.st0 {
fill: #1a0b25;
}
.st1 {
fill: #7c6e7b;
}
.st2 {
fill: none;
stroke: #7b7b7b;
}
</style>
<g >
<path class="st0" d="M147.5,207.2c0,17.9-14.5,32.5-32.5,32.5s-32.5-14.5-32.5-32.5s14.5-32.5,32.5-32.5S147.5,189.3,147.5,207.2z"
/>
<path class="st1" d="M131.3,207.2c0,9-7.3,16.2-16.2,16.2s-16.2-7.3-16.2-16.2c0-9,7.3-16.2,16.2-16.2S131.3,198.3,131.3,207.2z"/>
<path class="st2" d="M82.6,207.2H115"/>
<path class="st2" d="M86.9,223.4l28.1-16.2"/>
<path class="st2" d="M98.8,235.3l16.2-28.1"/>
<path class="st2" d="M115,239.7v-32.5"/>
<path class="st2" d="M131.3,235.3L115,207.2"/>
<path class="st2" d="M143.1,223.4L115,207.2"/>
<path class="st2" d="M147.5,207.2H115"/>
<path class="st2" d="M143.1,191L115,207.2"/>
<path class="st2" d="M131.3,179.1L115,207.2"/>
<path class="st2" d="M115,174.8v32.5"/>
<path class="st2" d="M98.8,179.1l16.2,28.1"/>
<path class="st2" d="M86.9,191l28.1,16.2"/>
</g>
</svg>
前轮
xml
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 422 240" style="enable-background:new 0 0 422 240;" xml:space="preserve">
<style type="text/css">
.st0 {
fill: #1a0b25;
}
.st1 {
fill: #7c6e7b;
}
.st2 {
fill: none;
stroke: #7b7b7b;
}
</style>
<g>
<path class="st0" d="M371.6,207.2c0,17.9-14.5,32.5-32.5,32.5s-32.5-14.5-32.5-32.5s14.5-32.5,32.5-32.5S371.6,189.3,371.6,207.2z"
/>
<path class="st1" d="M355.4,207.2c0,9-7.3,16.2-16.2,16.2c-9,0-16.2-7.3-16.2-16.2c0-9,7.3-16.2,16.2-16.2
C348.1,191,355.4,198.3,355.4,207.2z"/>
<path class="st2" d="M306.7,207.2h32.5"/>
<path class="st2" d="M311.1,223.4l28.1-16.2"/>
<path class="st2" d="M323,235.3l16.2-28.1"/>
<path class="st2" d="M339.2,239.7v-32.5"/>
<path class="st2" d="M355.4,235.3l-16.2-28.1"/>
<path class="st2" d="M367.3,223.4l-28.1-16.2"/>
<path class="st2" d="M371.6,207.2h-32.5"/>
<path class="st2" d="M367.3,191l-28.1,16.2"/>
<path class="st2" d="M355.4,179.1l-16.2,28.1"/>
<path class="st2" d="M339.2,174.8v32.5"/>
<path class="st2" d="M323,179.1l16.2,28.1"/>
<path class="st2" d="M311.1,191l28.1,16.2"/>
</g>
</svg>
车子主体
xml
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 422 240" style="enable-background:new 0 0 422 240;" xml:space="preserve">
<style type="text/css">
.st3 {
fill: #893163;
}
.st4 {
fill: #c82d49;
}
.st5 {
fill: #ed7475;
}
.st6 {
fill: #e83d4e;
}
.st7 {
fill: #d58c90;
}
.st8 {
fill: #b886ba;
}
.st9 {
fill: #f7bca3;
}
.st10 {
fill: #6ab4ce;
}
.st11 {
fill: #e0e12b;
}
.st12 {
fill: none;
stroke: #7b7b7b;
stroke-width: 2;
}
</style>
<g>
<path class="st3" d="M219.7,49.5H63.2l-3.1,6.2h8.1v6.2h2.5v-6.2h138.5v6.2h2.5v-6.2h8.1V49.5z"/>
<path class="st4" d="M24.5,126.8L0,175.9l15.7,31.3h58.8c0-22.4,18.2-40.6,40.6-40.6s40.6,18.2,40.6,40.6h143
c0-22.4,18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6h10l31.2-62.5c1.9-3.8,1.7-8.4-0.5-12c-2.3-3.6-6.2-5.9-10.5-5.9H24.5z"/>
<path class="st5" d="M297,61.9H57l-32.5,64.9h304.9L297,61.9z"/>
<g>
<path class="st6" d="M139.3,68.1v106.6c8.5,6.4,14.2,15.8,15.8,26.3h58.4V68L139.3,68.1z"/>
</g>
<g>
<path class="st6" d="M219.7,68.1h74.2v133h-74.2V68.1z"/>
</g>
<path class="st7" d="M84.1,0h49.5v49.5H84.1V0z"/>
<path class="st8" d="M133.5,15.5h64.9v34h-64.9V15.5z"/>
<path class="st9" d="M225.9,133h18.5v6.2h-18.5V133z"/>
<path class="st9" d="M145.5,133h18.6v6.2h-18.6V133z"/>
<path class="st3" d="M3.3,201v6.2h71.2c0-4.2,0.7-8.4,1.9-12.4H9.5C6.1,194.8,3.3,197.6,3.3,201z"/>
<path class="st3" d="M155.6,207.2h143c0-4.2,0.6-8.4,1.9-12.4H153.7C155,198.9,155.6,203,155.6,207.2z"/>
<path class="st3" d="M402.1,194.9h-24.3c1.3,4,1.9,8.2,1.9,12.4h28.5V201C408.3,197.6,405.5,194.9,402.1,194.9z"/>
<path class="st10" d="M300.1,68.1L300.1,68.1v52.6h26.3L300.1,68.1z"/>
<path class="st10" d="M225.9,74.2h61.8v46.4h-61.8V74.2z"/>
<path class="st10" d="M145.5,74.2h61.8v46.4h-61.8V74.2z"/>
<path class="st10" d="M53.9,68.1l-26.3,52.6h105.5V68.1H53.9z"/>
<path class="st11" d="M310.6,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.1,0-3.9-1.7-3.9-3.9v-1.9
C306.7,147.1,308.5,145.4,310.6,145.4z"/>
<path class="st11" d="M329,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9H329c-2.2,0-3.9-1.7-3.9-3.9v-1.9
C325.1,147.1,326.9,145.4,329,145.4z"/>
<path class="st11" d="M347.4,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.2,0-3.9-1.7-3.9-3.9v-1.9
C343.5,147.1,345.3,145.4,347.4,145.4z"/>
<path class="st11" d="M365.8,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.1,0-3.9-1.7-3.9-3.9v-1.9
C361.9,147.1,363.7,145.4,365.8,145.4z"/>
<path class="st11" d="M384.2,145.4h1.9c2.1,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.1,0-3.9-1.7-3.9-3.9v-1.9
C380.3,147.1,382.1,145.4,384.2,145.4z"/>
<path class="st11" d="M402.6,145.4h1.9c2.2,0,3.9,1.7,3.9,3.9v1.9c0,2.1-1.7,3.9-3.9,3.9h-1.9c-2.1,0-3.9-1.7-3.9-3.9v-1.9
C398.7,147.1,400.5,145.4,402.6,145.4z"/>
<path class="st12" d="M150.3,136.1h11.3"/>
<path class="st12" d="M231.9,136.1h10.3"/>
</g>
</svg>
然后我们可以使用在svg标签中,使用image标签,并通过href属性引入外部图片
html
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 422 240"
class="car"
>
<image width="422" height="240" href="./back-wheel.svg" class="back-wheel wheel"></image>
<image width="422" height="240" href="./front-wheel.svg" class="front-wheel wheel"></image>
<image width="422" height="240" href="./car-body.svg" class="car-body"></image>
</svg>
使用这种方式,设置旋转中心就是另一种方式了。
第一步,设置svg的viewBox属性。需要注意svg与分离后三张svg的viewBox需要设置为相同值。
第二步,为image标签设置宽高属性,和svg的viewbox宽高相同即可(支持数值或相对单位,100%也可以)
第三步,通过Adobe Illustrator
或其他软件查找前后车轮的中心坐标
第四步,设置元素的transform-origin
css
.car {
width: 300px;
height: 200px;
}
.car-body {
animation: carBounce 1s linear infinite;
}
.wheel {
transform-origin: center;
animation: rotate 2s linear infinite;
}
.back-wheel {
transform-origin: 115px 207.2px;
}
.front-wheel {
transform-origin: 339.2px 207.2px;
}
@keyframes rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
@keyframes carBounce {
0% {
transform: translateY(0);
}
20% {
transform: translateY(-10px);
}
40% {
transform: translateY(0);
}
60% {
transform: translateY(-5px);
}
80% {
transform: translateY(0);
}
100% {
transform: translateY(0);
}
}
这里我产生了个疑问,为元素设置了固定的变换原点transform-origin: 115px 207.2px;
,倘若图片放大缩小岂不是要计算,重新修改。但测试了一下,发现其实并不需要。具体原因请看下面的分析。
二、viewport与viewBox
理解SVG viewport,viewBox,preserveAspectRatio缩放 << 张鑫旭-鑫空间-鑫生活 (zhangxinxu.com)
1.viewport
通过width和height属性设置或者通过css设置的即为viewport的大小。
这里需要知道svg的width和height属性支持css中的各种相对或绝对单位(px,%,em,rem等),如果是纯数字,默认效果也为px。
viewBox属性
张鑫旭大佬的演示动画清楚地展示恶劣viewport与viewBox的关系
SVG viewBox属性原理分步演示 >> 张鑫旭-鑫空间-鑫生活 (zhangxinxu.com)
三、开始分析
回到第一部分的问题。为元素设置了固定的变换原点transform-origin: 115px 207.2px;
,上面提到过,svg中的transform-origin
是基于viewBox的。这里的设置的偏移也是相对于viewBox的,因此我们可以随意设置svg的宽高而无需再次修改变换原点的位置。 我们也可以从控制台观察
SVG元素(整个车)的盒模型信息 与 image元素(前轮)的盒模型信息
这里可以揭晓了,svg标签中的其他标签设置宽高百分比时,是基于父元素的viewBox的宽度的,当我们改变svg的宽高,实际上只改变了viewport的宽高,对viewBox的大小没有影响,只是影响viewBox的缩放而已。而子元素的宽高相对于父元素的viewBox,因此也没有变化。