当前位置主页 > 资料库 > 前端教程 > SVG进阶 | SVG路径动画-SMIL

SVG进阶 | SVG路径动画-SMIL

06-26

在前面的两篇文章中我们介绍了SVG动画中的<animate>元素的使用方法。在SMIL动画中,我们可以使用<animateMotion>元素来制作路径动画效果。路径动画是指一个元素沿着指定的路径运动。

<animateMotion>元素接收的属性和<animate>元素相同,另外他还可以接收三个属性:keyPointsrotatepath。还有它们的calcMode属性有所不同:<animateMotion>元素的calcMode属性的默认值是paced,而不是linear

使用path属性来指定运动路径

path属性用于指定一条运动路径。它和<path>元素上的d属性的格式和含义基本相同。

下面来看一个例子,一个圆形将沿着下面的路径进行运动:

圆形沿这条路径运动的代码如下:

<animateMotion 
    xlink:href="#circle"
    dur="1s"
    begin="click"
    fill="freeze"
    path="M0,0c3.2-3.4,18.4-0.6,23.4-0.6c5.7,0.1,10.8,0.9,16.3,2.3    c13.5,3.5,26.1,9.6,38.5,16.2c12.3,6.5,21.3,16.8,31.9,25.4c10.8,8.7,21,18.3,31.7,26.9c9.3,7.4,20.9,11.5,31.4,16.7
    c13.7,6.8,26.8,9.7,41.8,9c21.4-1,40.8-3.7,61.3-10.4c10.9-3.5,18.9-11.3,28.5-17.8c5.4-3.7,10.4-6.7,14.8-11.5
    c1.9-2.1,3.7-5.5,6.5-6.5" />                              
                            

这条路径在开始绘制曲线之前,虚拟画笔被移动到坐标系的(0,0)位置。这里需要注意的是圆形的圆心在坐标系的(0,0)位置,而不是左上角位置。注意它们之间的细微差别。path属性的坐标系统是相对于元素当前位置的。

上面代码的结果如下,点击圆形查看路径动画效果:

如果你指定的路径不是从(0,0)开始,那么圆形会在开始运动之前突然跳动到你指定的位置之上。例如,假设你在AI软件中绘制了一条曲线,并将它导出为一个SVG路径数据。输出的路径会类似下面的样子:

<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M100.4,102.2c3.2-3.4,18.4-0.6,23.4-0.6c5.7,0.1,10.8,0.9,16.3,2.3
    c13.5,3.5,26.1,9.6,38.5,16.2c12.3,6.5,21.3,16.8,31.9,25.4c10.8,8.7,21,18.3,31.7,26.9c9.3,7.4,20.9,11.5,31.4,16.7
    c13.7,6.8,26.8,9.7,41.8,9c21.4-1,40.8-3.7,61.3-10.4c10.9-3.5,18.9-11.3,28.5-17.8c5.4-3.7,10.4-6.7,14.8-11.5
    c1.9-2.1,3.7-5.5,6.5-6.5"/>                              
                            

上面的代码中,路径的开始坐标为(100.4,102.2)。如果我们使用这条路径作为运动路径,那么圆形会在运动之前向右跳动约100个单位,向下跳动约102个单位。然后才开始沿着路径运动。

使用<mpath>元素来指定运动路径

我们还可以使用另一种方法来指定运动路径。除了使用path属性,我们可以使用<mpath>元素来引用一条外部的路径。<mpath>元素是<animateMotion>元素的子元素,它可以通过xlink:href属性来引用外部的路径。

<animateMotion xlink:href="#circle" dur="1s" begin="click" fill="freeze">
  <mpath xlink:href="#motionPath" />
</animateMotion>                              
                            

作为运动路径的<path>元素可以定义在文档的任何地方。甚至可以将它定义在<defs>元素中,并且不用将它渲染在画布上。

在下面的例子中,我们将运动路径绘制在画布上,一个圆形放置在路径的开始位置。但是,当点击圆形后,它不会沿着路径进行运动。点击下面的圆形看看效果:

<svg width="500" height="350" viewBox="0 0 550 350">
  <path id="motionPath-2" fill="none" stroke="#000000" stroke-miterlimit="10" d="M91.4,104.2c3.2-3.4,18.4-0.6,23.4-0.6c5.7,0.1,10.8,0.9,16.3,2.3
    c13.5,3.5,26.1,9.6,38.5,16.2c12.3,6.5,21.3,16.8,31.9,25.4c10.8,8.7,21,18.3,31.7,26.9c9.3,7.4,20.9,11.5,31.4,16.7
    c13.7,6.8,26.8,9.7,41.8,9c21.4-1,40.8-3.7,61.3-10.4c10.9-3.5,18.9-11.3,28.5-17.8c5.4-3.7,10.4-6.7,14.8-11.5
    c1.9-2.1,3.7-5.5,6.5-6.5"/>
  <circle id="circle-2" r="20" cx="100" cy="100" fill="tomato" />
   
  <animateMotion 
           xlink:href="#circle-2"
           dur="1s"
           begin="click"
           fill="freeze">
    <mpath xlink:href="#motionPath-2" />
  </animateMotion>
</svg>                              
                            

为什么会这样呢?这是因为圆形的位置被path路径上的数据转换了。注意路径的开始位置是M91.4,104.2,而不是(0,0)。

一个解决的办法是将圆形放置在(0,0)坐标位置。即<circle>元素的cxcy属性都为0。这样当使用路径的数据来转换它的时候,会得到正确的结果,圆形会沿路径进行运动。

另一个方法是使用transform属性来转换圆形的坐标系统,使它在运动之前被转换为0坐标。

下面的例子是上面那个例子的修正版本,同时做了一些修改:使用闭合路径,并使运行无限循环运动。

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="motionPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M202.4,58.3c-13.8,0.1-33.3,0.4-44.8,9.2
    c-14,10.7-26.2,29.2-31.9,45.6c-7.8,22.2-13.5,48-3.5,70.2c12.8,28.2,47.1,43.6,68.8,63.6c19.6,18.1,43.4,26.1,69.5,29.4
    c21.7,2.7,43.6,3.3,65.4,4.7c19.4,1.3,33.9-7.7,51.2-15.3c24.4-10.7,38.2-44,40.9-68.9c1.8-16.7,3.4-34.9-10.3-46.5
    c-9.5-8-22.6-8.1-33.2-14.1c-13.7-7.7-27.4-17.2-39.7-26.8c-5.4-4.2-10.4-8.8-15.8-12.9c-4.5-3.5-8.1-8.3-13.2-11
    c-6.2-3.3-14.3-5.4-20.9-8.2c-5-2.1-9.5-5.2-14.3-7.6c-6.5-3.3-12.1-7.4-19.3-8.9c-6-1.2-12.4-1.3-18.6-1.5
    C222.5,59,212.5,57.8,202.4,58.3"/>
  
  <circle id="circle" r="10" cx="0" cy="0" fill="tomato" />
   
  <animateMotion 
           xlink:href="#circle"
           dur="5s"
           begin="0s"
           fill="freeze"
           repeatCount="indefinite">
    <mpath xlink:href="#motionPath" />
  </animateMotion>
</svg>                              
                            

可以看到,我们将圆形的圆心修改为cx="0" cy="0"之后,得到了正确的结果。

<animateMotion>的覆盖规则

由于animateMotion有多种方式可以实现相同的效果,我们需要明确在同时使用这些元素的时候,哪些属性会被覆盖。

覆盖的规则如下:

  • 对于定义一个运动路径,mpath元素会覆盖path属性,也会覆盖values属性,还会覆盖frombyto属性。
  • 对于确定的点对应的keytimes属性,keytimes属性会覆盖path,还会覆盖frombyto属性。
通过rotate来设置元素沿路径运动的方向

在上面的例子中,一个圆形沿着一条封闭的路径不停的运动。如果运动的不是一个圆形,而是一个有一定运动方向的元素,比如说是一个小车图标,会发生什么事情呢?

在下面的例子中,我们就使用一个小车来代替圆形。这个小车有一个<g>元素组成。另外为了让小车沿着路径运动,我们还添加了一个transform属性,让它的初始位置在(0,0)位置。

代码如下:

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="motionPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M202.4,58.3c-13.8,0.1-33.3,0.4-44.8,9.2
    c-14,10.7-26.2,29.2-31.9,45.6c-7.8,22.2-13.5,48-3.5,70.2c12.8,28.2,47.1,43.6,68.8,63.6c19.6,18.1,43.4,26.1,69.5,29.4
    c21.7,2.7,43.6,3.3,65.4,4.7c19.4,1.3,33.9-7.7,51.2-15.3c24.4-10.7,38.2-44,40.9-68.9c1.8-16.7,3.4-34.9-10.3-46.5
    c-9.5-8-22.6-8.1-33.2-14.1c-13.7-7.7-27.4-17.2-39.7-26.8c-5.4-4.2-10.4-8.8-15.8-12.9c-4.5-3.5-8.1-8.3-13.2-11
    c-6.2-3.3-14.3-5.4-20.9-8.2c-5-2.1-9.5-5.2-14.3-7.6c-6.5-3.3-12.1-7.4-19.3-8.9c-6-1.2-12.4-1.3-18.6-1.5
    C222.5,59,212.5,57.8,202.4,58.3"/>
  <g id="car" transform="translate(-234.4, -182.8)">
      <path d="M234.4,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C240.8,185.6,238,182.8,234.4,182.8z"/>
      <circle cx="234.4" cy="189.2" r="2.8"/>
      <path d="M263,182.8c-3.5,0-6.4,2.9-6.4,6.4c0,3.5,2.9,6.4,6.4,6.4c3.5,0,6.4-2.9,6.4-6.4C269.4,185.6,266.6,182.8,263,182.8z"/>
      <circle cx="263" cy="189.2" r="2.8"/>
      <path d="M275,171.4c-2.8-0.7-5.2-3-6.3-5.1l-3.9-7.4c-1.1-2.1-3.9-3.8-6.3-3.8h-22.6c-2.4,0-5,1.8-5.7,4.1l-2.4,7
          c-0.2,0.9-1.8,5.5-5,5.5c-2.4,0-5,3.1-5,5.5v8.2c0,2.4,1.9,4.3,4.3,4.3h4.5c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8
          c4.3,0,7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h13.1c0-0.2,0-0.3,0-0.5c0-4.3,3.5-7.8,7.8-7.8s7.8,3.5,7.8,7.8c0,0.2,0,0.3,0,0.5h8.1
          c2.4,0,4.3-1.9,4.3-4.3v-6.5C283.2,172,277.3,172,275,171.4z"/>
      <path d="M241.8,170.3h-12.5c0.7-1.1,1.1-2.2,1.2-2.6l2-5.9c0.6-1.9,2.8-3.5,4.8-3.5h4.5V170.3z"/>
      <path d="M246.1,170.3v-12h10.4c2,0,4.4,1.5,5.3,3.3l3.3,6.3c0.4,0.8,1.1,1.7,2,2.4H246.1z"/>
  </g>
  <animateMotion 
           xlink:href="#car"
           dur="3s"
           begin="0s"
           fill="freeze"
           repeatCount="indefinite">
    <mpath xlink:href="#motionPath" />
  </animateMotion>
</svg>                              
                            

现在,小车沿着路径开始运动,但是看起来十分的怪异:

之所以看起来怪异,是因为小车的方向是固定的,不会根据曲线的方向而改变。为了修正它,我们可以使用rotate属性。

rotate属性有三个参数:

  • auto:元素自动根据运动路径的角度(曲线的切线方向)来改变它的运动方向。
  • auto-reverse:这是auto在曲线的切线方向上的镜像。
  • 一个数值:目标元素具有一个恒定的运动角度,这个角度由指定的数值决定。

为了修正上面例子的BUG,我们设置rotate="auto"

<animateMotion 
   xlink:href="#car"
   dur="3s"
   begin="0s"
   fill="freeze"
   repeatCount="indefinite"
   rotate="auto">                              
                            

现在,得到的结果如下面的样子:

如果你想让小车沿路径的外围运动,可以设置rotate="auto-reverse"

<animateMotion 
   xlink:href="#car-3"
   dur="3s"
   begin="0s"
   fill="freeze"
   repeatCount="indefinite"
   rotate="auto-reverse">                              
                            

现在,小车沿路径运动的效果看起来就比较正常了。但是还有一点小问题:小车是反方向沿路径进行运动的。要修正它,我们需要将小车沿Y轴翻转。我们可以通过在将小车在Y轴方向上缩放"-1"来实现翻转效果。

<g id="car" transform="scale (-1, 1) translate(-234.4, -182.8)">                              
                            

得到的结果如下面的样子:

文字路径动画

使文字在任意路径上运动和其它SVG元素的路径动画有所不同,它使用的是<animate>元素,而不是<animateMotion>元素。

首先我们需要将文字定位在路径上。这可以通过在<text>元素中嵌套>textPath>元素来实现。然后使用>textPath>元素来指向一条实际的路径作为运动路径。被引用的路径可以是画布上的一条路径,也可以定义在<defs>中。

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="myPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M91.4,104.2c3.2-3.4,18.4-0.6,23.4-0.6c5.7,0.1,10.8,0.9,16.3,2.3
    c13.5,3.5,26.1,9.6,38.5,16.2c12.3,6.5,21.3,16.8,31.9,25.4c10.8,8.7,21,18.3,31.7,26.9c9.3,7.4,20.9,11.5,31.4,16.7
    c13.7,6.8,26.8,9.7,41.8,9c21.4-1,40.8-3.7,61.3-10.4c10.9-3.5,18.9-11.3,28.5-17.8c5.4-3.7,10.4-6.7,14.8-11.5
    c1.9-2.1,3.7-5.5,6.5-6.5"/>
  <text>
    <textpath xlink:href="#myPath">
      Text laid out along a path.
    </textpath>
  </text>
</svg>                              
                            

得到的结果如下所示:

Text laid out along a path.

要制作文字的路径动画,我们将使用<animate>元素的startOffset来制作动画。

code>startOffset代表文字在路径上的偏移。0%表示路径的开始,100%代表路径的结束。如果设置为50%,文字将移动到路径的一半。

<svg width="500" height="350" viewBox="0 0 500 350">
  <path id="myPath" fill="none" stroke="#000000" stroke-miterlimit="10" d="M91.4,104.2c3.2-3.4,18.4-0.6,23.4-0.6c5.7,0.1,10.8,0.9,16.3,2.3
    c13.5,3.5,26.1,9.6,38.5,16.2c12.3,6.5,21.3,16.8,31.9,25.4c10.8,8.7,21,18.3,31.7,26.9c9.3,7.4,20.9,11.5,31.4,16.7
    c13.7,6.8,26.8,9.7,41.8,9c21.4-1,40.8-3.7,61.3-10.4c10.9-3.5,18.9-11.3,28.5-17.8c5.4-3.7,10.4-6.7,14.8-11.5
    c1.9-2.1,3.7-5.5,6.5-6.5"/>
  <text>
    <textpath xlink:href="#myPath">
      Text laid out along a path.
      
      <animate attributeName="startOffset" from="0%" to ="100%" begin="0s" dur="5s" repeatCount="indefinite" keyTimes="0;1" calcMode="spline" keySplines="0.1 0.2 .22 1"/>
    </textpath>
  </text>
</svg>                              
                            

上面的代码的返回结果是:

Text laid out along a path.
返回SVG教程目录
相关阅读
Previous:
上一篇:SVG进阶 | SVG动画-SMIL(二)
Next:
下一篇:SVG进阶 | SVG和Javascript脚本
返回顶部