在前面的两篇文章中我们介绍了SVG动画中的<animate>
元素的使用方法。在SMIL动画中,我们可以使用<animateMotion>
元素来制作路径动画效果。路径动画是指一个元素沿着指定的路径运动。
<animateMotion>
元素接收的属性和<animate>
元素相同,另外他还可以接收三个属性:keyPoints
,rotate
和path
。还有它们的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>
元素的cx
和cy
属性都为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
有多种方式可以实现相同的效果,我们需要明确在同时使用这些元素的时候,哪些属性会被覆盖。
覆盖的规则如下:
通过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>
得到的结果如下所示:
要制作文字的路径动画,我们将使用<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>
上面的代码的返回结果是: