SVG动画学习之路

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,因此也没有变化。

相关推荐
计算机学姐7 分钟前
基于nodejs+vue的宠物医院管理系统
前端·javascript·vue.js·mysql·npm·node.js·sass
余生H28 分钟前
前端大模型入门:使用Transformers.js手搓纯网页版RAG(二)- qwen1.5-0.5B - 纯前端不调接口
前端·javascript·人工智能·大语言模型·rag·端侧大模型·webml
你会发光哎u1 小时前
深入理解包管理工具
开发语言·前端·javascript·node.js
驻风丶1 小时前
el-tooltips设置文字超出省略才显示
前端·javascript·vue.js
nnlss1 小时前
nvm 安装node 报错
前端
酷盖机车男1 小时前
封装轮播图 (因为基于微博小程序,语法可能有些出入,如需使用需改标签)
前端·javascript·小程序·uni-app
世界和平�����2 小时前
openlayers中一些问题的解决方案
前端·javascript·vue.js
小菜yh2 小时前
后端人需知
java·前端·javascript·vue.js·设计模式
周万宁.FoBJ2 小时前
vue3 实现文本内容超过N行折叠并显示“...展开”组件
开发语言·前端·javascript
Jiaberrr2 小时前
uniapp视频禁止用户推拽进度条并保留进度条显示的解决方法——方案二
前端·javascript·uni-app·音视频