SVG 动画指南 (SMIL)

Avatar of Sara Soueidan
Sara Soueidan

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

2015 年 12 月更新:在此更新时,SMIL 似乎正在逐渐消亡。Sarah Drasner 提供了一份指南,介绍如何替换其中一些功能。

以下是 Sara Soueidan 的一篇客座文章。Sara 擅长深入挖掘 Web 功能,并为我们其他人解释清楚。在这里,她深入探讨了 SMIL(以及相关内容),以及直接构建到 SVG 中的动画语法,并为我们提供了这份史诗般的指南。

目录

概述

SVG 图形可以使用 动画元素 进行动画处理。动画元素最初是在 SMIL 动画规范中定义的;这些元素包括

  • <animate></animate> – 允许您在一段时间内对标量属性进行动画处理。
  • <set></set> – 是 animate 的一种简便速记,对于将动画值分配给非数字属性非常有用,例如 visibility 属性。
  • <animatemotion></animatemotion> – 使元素沿着运动路径移动。
  • <animatecolor></animatecolor> – 会随着时间推移修改特定属性的色彩值。请注意,该元素已被弃用,建议使用 animate 元素来针对可以接受色彩值的属性。尽管它仍然存在于 SVG 1.1 规范中,但明确指出它已被弃用;并且在 SVG 2 规范中已完全删除。

除了 SMIL 规范中定义的动画元素外,SVG 还包含与 SMIL 动画规范兼容的扩展;这些扩展包括扩展元素功能的属性和附加动画元素。SVG 扩展包括

  • <animatetransform></animatetransform> – 允许您随着时间推移对 SVG 的某个变换属性进行动画处理,例如 transform 属性。
  • path (属性) – 允许在 animateMotion 元素的 path 属性中指定 SVG 路径数据语法的任何功能(SMIL 动画只允许在 path 属性中使用 SVG 路径数据语法的子集)。我们将在下一节中详细讨论 animateMotion
  • <mpath></mpath> – 与 animateMotion 元素结合使用,用于引用要用作运动路径的运动路径。mpath 元素包含在 animateMotion 元素内,位于结束标签之前。
  • keypoints (属性) – 用作 animateMotion 的属性,用于精确控制运动路径动画的速度。
  • rotate (属性) – 用作 animateMotion 的属性,用于控制是否自动旋转对象,使其 x 轴指向与运动路径的方向切线向量相同的方向(或相反方向)。此属性是使沿着路径运动按照预期的方式工作的关键。有关更多信息,请参阅 animateMotion 部分。

SVG 动画在本质上可能类似于 CSS 动画和过渡。会创建关键帧,事物会移动,颜色会变化,等等。但是,它们可以实现一些 CSS 动画无法实现的功能,我们将在下面介绍。

为什么要使用 SVG 动画?

SVG 可以使用 CSS 进行 样式设置和动画处理(幻灯片)。基本上,可以应用于 HTML 元素的任何变换或过渡动画也可以应用于 SVG 元素。但有些 SVG 属性不能通过 CSS 进行动画处理,而可以通过 SVG 进行动画处理。例如,SVG 路径附带一组 数据(一个 d="" 属性),用于定义该路径的形状。这些数据可以通过 SMIL 进行修改和动画处理,但不能通过 CSS 进行修改。这是因为 SVG 元素由一组称为 SVG 演示属性 的属性来描述。其中一些属性可以使用 CSS 进行设置、修改和动画处理,而另一些则不能。

因此,许多动画和效果目前无法使用 CSS 实现。可以使用 JavaScript 或 SMIL 派生的声明性 SVG 动画来弥补 CSS SVG 动画的不足。

如果您更喜欢使用 JavaScript,我建议使用 Dmitry Baranovsky 的 snap.svg,它被称为“SVG 的 jQuery”。以下是 示例集合

或者,如果您更喜欢更具声明性的动画方法,可以使用 SVG 动画元素,如本指南中所述!

与 JS 动画相比,SMIL 另一个优势是 JS 动画在 SVG 嵌入为 img 或用作 CSS 中的 background-image 时不起作用。SMIL 动画在这两种情况下都起作用(或应该起作用,具体取决于浏览器支持)。在我看来,这是一个很大的优势。您可能会发现自己因为这个原因而选择 SMIL 而不是其他选项。本文是一份指南,可以帮助您立即开始使用 SMIL。

浏览器支持和回退

对 SMIL 动画的浏览器支持相当不错。它们在除 Internet Explorer 和 Opera Mini 之外的所有浏览器中都能正常工作。有关浏览器支持的详细概述,您可以参考 Can I Use 上的兼容性表

如果您需要为 SMIL 动画提供回退,可以使用 Modernizr 在运行时测试浏览器支持。如果 SMIL 不受支持,则可以提供某种回退(JavaScript 动画、备用体验等)。

使用 xlink:href 指定动画目标

无论您选择哪种动画元素,都需要指定由该元素定义的动画目标。

要指定目标,可以使用 xlink:href 属性。该属性接受对作为此动画目标的元素的 URI 引用,因此该元素将随着时间推移进行修改。目标元素必须是当前 SVG 文档片段的一部分。

<rect id="cool_shape" ...="">
  <animate xlink:href="#cool_shape" ...=""></animate>
</rect>

如果您之前遇到过 SVG 动画元素,您可能已经看到它们嵌套在它们要进行动画处理的元素中。根据规范,这也可以实现。

如果未提供 xlink:href 属性,则目标元素将是当前动画元素的直接父元素。

<rect id="cool_shape" ...="">
  <animate ...=""></animate>
</rect>

因此,如果您想将动画“封装”到其应用的元素中,您可以这样做。如果您想将动画保存在文档中的其他位置,您也可以这样做,并使用 xlink:href 指定每个动画的目标。两种方式都可以正常工作。

使用 attributeNameattributeType 指定动画目标属性

所有动画元素还共享另一个属性:attributeNameattributeName 属性用于指定要进行动画处理的属性的名称。

例如,如果您想对圆心在 x 轴上的位置进行动画处理,则可以通过将 cx 指定为 attributeName 属性的值来实现。

attributeName 只接受一个值,而不是一个值列表,因此,您一次只能对一个属性进行动画处理。如果您想对多个属性进行动画处理,则需要为该元素定义多个动画。我希望这一点有所不同,我认为 CSS 在这一点上比 SMIL 更有优势。但另一方面,由于其他动画属性可能具有值(我们将在下一节中介绍),因此一次只定义一个属性名称是合理的,否则其他属性值将过于复杂而难以使用。

当您指定属性名称时,可以添加一个 XMLNS(XML 命名空间的缩写)前缀来指示该属性的命名空间。也可以使用 attributeType 属性指定命名空间。例如,有些属性是 CSS 命名空间的一部分(这意味着该属性也可以作为 CSS 属性找到),而另一些属性则是纯 XML 属性。您可以从 这里 找到展示这些属性的表格。该表格中的属性并非所有 SVG 属性,而是那些可以使用 CSS 设置的属性。其中一些属性已经作为 CSS 属性存在。

如果 attributeType 的值没有明确设置或设置为 auto,则浏览器必须首先在 CSS 属性列表中搜索匹配的属性名称,如果未找到,则在元素的默认 XML 命名空间中搜索。

例如,以下代码段会对 SVG 矩形的 opacity 属性进行动画。由于 opacity 属性也可用作 CSS 属性,因此将 attributeType 设置为 CSS 命名空间

<rect>
  <animate
    attributetype="CSS"
    attributename="opacity" 
    from="1" 
    to="0" 
    dur="5s" 
    repeatcount="indefinite">
  </animate>
</rect>

在以下的示例中,我们将介绍其他动画属性。除非另有说明,否则所有动画属性对所有动画元素都通用。

在一段时间内将元素的属性从一个值动画到另一个值,并指定结束状态:frombytodurfill

让我们从将一个圆圈从一个位置移动到另一个位置开始。我们将通过更改其 cx 属性的值(该属性指定其圆心的 x 坐标)来实现。

我们将使用元素来实现这一点。该元素用于一次动画一个属性。通常使用对接受数值和颜色的属性进行动画。要获取可以进行动画的属性列表,请参阅 此表格.

为了在一段时间内将一个值更改为另一个值,使用 fromtodur 属性。除了这些属性之外,您还需要使用 begin 属性指定动画何时开始。

<circle id="my-circle" r="30" cx="50" cy="50" fill="orange">
  <animate xlink:href="#my-circle" attributename="cx" from="50" to="450" dur="1s" begin="click" fill="freeze"></animate>
</circle>

在上面的示例中,我们定义了一个圆圈,然后对该圆圈进行了动画。圆圈的圆心从 x 轴上初始位置的 50 个单位移动到 450 个单位。

begin 值设置为 click。这意味着单击圆圈时,圆圈将移动。您也可以将此值设置为时间值。例如,begin="0s" 将在页面加载后立即启动动画。您可以通过设置一个正的时间值来 延迟动画。例如,begin="2s" 在加载后两秒启动动画。

begin 更有趣的地方在于,您可以定义 click + 1s 之类的值,以便在 元素被单击后一秒 启动动画!此外,您可以使用其他允许您同步动画的值,而无需计算其他动画的持续时间和延迟。我们稍后会详细介绍。

dur 属性类似于 CSS 中的 animation-duration 等效属性。

fromto 属性类似于动画的 @keyframe 块中 CSS 的 fromto 关键帧。

@keyframes moveCircle {
  from { /* start value */ }
  to { /* end value */ }
}

fill 属性(不幸的是与定义元素填充颜色的 fill 属性同名)类似于 animation-fill-mode 属性,该属性指定动画结束后元素是否应恢复到其初始状态。SVG 中的值与 CSS 中的值类似,只是使用了不同的名称。

  • freeze:动画效果被定义为在活动持续时间的最后一个值处 冻结 效果值。动画效果在文档持续时间的剩余时间内被“冻结”(或直到动画重新启动)。
  • remove:动画效果在动画的活动持续时间结束后将被移除(不再应用)。在动画的活动结束之后,动画不再影响目标(除非动画重新启动)。

尝试在实时演示中更改这些值,以查看动画是如何受到影响的。

by 属性用于指定动画的相对偏移量。顾名思义,您可以使用它来指定动画的 前进 程度。by 的效果几乎只在您以离散步骤跨越动画持续时间时可见,类似于它在 CSS 的 steps() 函数中工作的方式。SVG 中 CSS steps() 函数的等效函数是 calcMode="discrete"。我们将在本文稍后的部分介绍 calcMode 属性。

by 的效果更明显的情况是,当您只指定 to 属性时。例如,如果您在本文后面将要介绍的 set 元素中使用它,就会出现这种情况。

最后但并非最不重要的一点是,by 在您使用加法和累积动画时也很有用。我们将在本文稍后的部分介绍。

使用 restart 重新启动动画

在动画处于活动状态时阻止动画重新启动可能很有用。为此,SVG 提供了 restart 属性。您可以将此属性设置为三个可能值之一。

  • always:动画可以随时重新启动。这是默认值。
  • whenNotActive:动画只能在非活动状态(即活动结束之后)重新启动。在动画的活动持续时间内尝试重新启动动画将被忽略。
  • never:元素在父时间容器的当前简单持续时间的剩余时间内无法重新启动。(在 SVG 的情况下,由于父时间容器是 SVG 文档片段,因此动画在文档持续时间的剩余时间内无法重新启动。)

命名动画和同步动画

假设我们要对圆圈的位置 颜色进行动画,以便颜色的变化在移动动画结束时发生。我们可以通过将颜色变化动画的 begin 值设置为移动动画的 duration 来实现这一点;这正是我们通常在 CSS 中的做法。

然而,SMIL 具有一个很好的事件处理功能。我们之前提到过,begin 属性接受 click + 5s 之类的值。此值称为“事件值”,在本例中由事件引用后跟一个“时钟值”组成。这里有趣的是第二部分的命名:“时钟值”。为什么不是简单的“时间值”?答案是,您可以使用像“10min”或“01:33”这样的 时钟值,它们等效于“1 分钟 33 秒”,甚至“02:30:03”(2 小时 30 分钟 3 秒)。在撰写本文时,任何浏览器都尚未完全实现 时钟值。

因此,如果我们回到之前的演示并使用 click + 01:30,如果浏览器开始支持它,则动画将在单击圆圈后 1 分钟 30 秒后启动。

它可以接受的另一种类型的值是另一个动画的 ID 后跟一个事件引用。如果您有两个(或更多)动画(无论它们是否应用于同一个元素!),并且您想要同步它们,以便其中一个动画相对于另一个动画开始,则可以做到这一点,而无需知道另一个动画的持续时间。

例如,在下一个演示中,蓝色矩形在圆圈动画开始后 1 秒开始移动。这是通过为每个动画提供一个 ID,然后在以下代码中使用该 ID 和 begin 事件来实现的

<circle id="orange-circle" r="30" cx="50" cy="50" fill="orange">
  <rect id="blue-rectangle" width="50" height="50" x="25" y="200" fill="#0099cc"></rect>
  <animate 
    xlink:href="#orange-circle" 
    attributename="cx" 
    from="50" 
    to="450" 
    dur="5s" 
    begin="click" 
    fill="freeze" 
    id="circ-anim">
  </animate>
  <animate 
    xlink:href="#blue-rectangle" 
    attributename="x" 
    from="50" 
    to="425" 
    dur="5s" 
    begin="circ-anim.begin + 1s" 
    fill="freeze" 
    id="rect-anim">
  </animate>
</circle>

begin="circ-anim.begin + 1s" 部分告诉浏览器在圆圈动画开始后 1 秒开始矩形的动画。您可以查看实时演示。

您也可以在圆圈动画结束之后开始矩形动画,方法是使用 end 事件。

<animate 
  xlink:href="#blue-rectangle" 
  attributename="x" 
  from="50" 
  to="425" 
  dur="5s"
  begin="circ-anim.end" 
  fill="freeze" 
  id="rect-anim">
</animate>

您甚至可以 圆圈动画结束 之前 开始它。

<animate
  xlink:href="#blue-rectangle"
  attributename="x" 
  from="50" 
  to="425" 
  dur="5s" 
  begin="circ-anim.end - 3s" 
  fill="freeze" 
  id="rect-anim">
</animate>

使用 repeatCount 重复动画

如果要运行动画不止一次,可以使用 repeatCount 属性来实现。您可以指定要重复的次数,或者使用 indefinite 关键字来让它无限重复。因此,如果我们要重复圆圈动画两次,代码如下所示。

<animate
  xlink:href="#orange-circle"
  attributename="cx" 
  from="50" 
  to="450" 
  dur="5s" 
  begin="click" 
  repeatcount="2"
  fill="freeze" 
  id="circ-anim">
</animate>

您可以在这里查看实时演示。在演示中,我将圆圈的重复次数设置为 2,正方形的重复次数设置为 indefinite

请注意动画是如何从初始的from值重新开始的,而不是从动画结束时达到的值。不幸的是,SMIL 没有包含一种像 CSS 动画一样在起始值和结束值之间来回切换的方法。在 CSS 中,animation-direction 属性指定动画是否应该在某些或所有循环或迭代中反向播放。animation-direction: alternate 值意味着奇数次循环迭代以正常方向播放,偶数次循环迭代以反向播放。这意味着第一个循环将从头到尾播放,然后第二个循环将从尾到头播放,然后第三个循环将从头到尾播放,依此类推。

在 SMIL 中,要做到这一点,您需要使用 JavaScript 明确地更改 fromto 属性的值。Big Bite Creative 的 Jon McPartland 写了一篇文章,解释了他如何为一个菜单图标动画做到这一点。

另一种解决方法是将结束值指定为中间值,然后将结束值设置为与初始值相同。例如,您可以将动画设置为从某个值开始,from,并以与 to 相同的值结束,但您将指定您本来设置的最终值,作为 fromto 之间的中间值。
在 CSS 中,我们可以使用类似下面的代码来实现这一点

@keyframes example {
  from, to {
    left: 0;
  }

  50% {
    left: 300px;
  }
}

SMIL 中的等效代码是使用 values 属性,我们稍后会解释。

也就是说,上述解决方法可能对您有用,也可能对您没有用,具体取决于您想要实现的动画类型,以及您是否正在链接动画、重复动画或进行累加动画。

以下是一个由 Miles Elam 提供的漂亮、简单的无限动画,使用了一些延迟的开始时间

使用 repeatDur 限制重复时间

如果动画长时间恢复,将元素设置为无限重复可能会让人感到烦人或不友好。因此,将重复时间限制在一定时间段内,并在文档开始后一段时间停止重复可能是一个好主意。这被称为演示时间

演示时间表示相对于给定文档片段的文档开始的时间轴中的位置。它是使用 repeatDur 属性指定的。它的语法类似于时钟值,但不是相对于另一个动画事件或交互事件,而是相对于文档的开始。

例如,以下代码片段将在文档开始后 1 分钟 30 秒停止动画的重复

<animate 
  xlink:href="#orange-circle" 
  attributename="cx" 
  from="50" 
  to="450" 
  dur="2s" 
  begin="0s" 
  repeatcount="indefinite" 
  repeatdur="01:30" 
  fill="freeze" 
  id="circ-anim">
</animate>

这是一个现场演示

根据重复次数同步动画

现在让我们回到两个动画之间同步的主题。实际上,在 SMIL 中,您可以同步动画,以便一个动画根据另一个动画的重复次数开始。例如,您可以在另一个动画的第 n 次重复后开始一个动画,加上或减去您可能想要添加的时间量。
以下示例在圆形的动画第二次重复时开始矩形的动画

<animate 
  xlink:href="#blue-rectangle"
  attributename="x" 
  from="50" 
  to="425" 
  dur="5s" 
  begin="circ-anim.repeat(2)" 
  fill="freeze" 
  id="rect-anim">
</animate>

以下是一个现场演示,矩形的动画在圆形的动画第二次重复后 2 秒开始。

还有一个由 David Eisenberg 为SVG Essentials(第 2 版)制作的示例 点击这里查看

控制动画关键帧值:keyTimesvalues

在 CSS 中,我们可以指定在动画期间某个帧中我们希望动画属性取的值。例如,如果您正在动画化元素的左侧偏移量,而不是直接将其动画化从 0 到 300,您可以将其动画化,使其在某些帧中取某些值,如下所示

@keyframes example {
  0% {
    left: 0;
  }
  50% {
    left: 320px;
  }
  80% {
    left: 270px;
  }
  100% {
    left: 300px;
  }
}

0%20%80%100% 是动画的帧,每个帧块中的值是每个帧的值。上面描述的效果是元素弹起墙壁,然后弹回最终位置。

在 SMIL 中,您可以以类似的方式控制每个帧的值,但语法却大不相同。

要指定关键帧,您使用 keyTimes 属性。然后,要为每个帧指定动画属性的值,您使用 values 属性。SMIL 中的命名约定非常方便。

如果我们回到移动的圆圈,并使用与 CSS 关键帧中类似的值,代码将如下所示

<animate
  xlink:href="#orange-circle" 
  attributename="cx" 
  from="50" 
  to="450" 
  dur="2s" 
  begin="click" 
  values="50; 490; 350; 450" 
  keytimes="0; 0.5; 0.8; 1" 
  fill="freeze" 
  id="circ-anim">
</animate>

我们在这里做了什么?

首先要注意的是,关键帧时间和中间值是作为列表指定的。keyTimes 属性是一个分号分隔的时值列表,用于控制动画的节奏。列表中的每个时间对应于 values 属性列表中的一个值,并定义该值何时在动画函数中使用。keyTimes 列表中的每个时值都指定为 0 到 1(包含)之间的浮点数,表示动画元素简单持续时间的比例偏移量。因此,关键时间类似于 CSS 中的那些,不同之处在于,您不是将它们指定为百分比,而是指定为分数。

以下是上述代码的现场演示。单击圆圈开始动画。

请注意,如果使用值列表,动画将在动画过程中按顺序应用这些值。如果指定了 values 列表,则任何 fromtoby 属性值都将被忽略。

在此,还要提一下,您可以在没有 keyTimes 属性的情况下使用 values 属性——这些值会自动均匀地分布在时间范围内(对于除 paced 之外的所有 calcMode 值(见下一节)。

使用自定义缓动控制动画节奏:calcModekeySplines

我将再次进行 CSS-SMIL 对比,因为如果您已经熟悉 CSS 动画,那么 SMIL 的语法和概念会更容易理解。

在 CSS 中,您可以选择更改默认的均匀动画节奏,并指定一个自定义缓动函数来控制动画,使用 animation-timing-function 属性。缓动函数可以是几个预定义的关键字之一,或者是一个 三次贝塞尔 函数。后者可以使用像 这个 由 Lea Verou 提供的工具来创建。

在 SMIL 中,动画节奏是使用 calcMode 属性指定的。除了 animateMotion(我们将在本文后面讨论),所有动画元素的默认动画节奏都是 linear。除了 linear 值之外,您还可以将该值设置为:discretepacedspline

  • discrete 指定动画函数将从一个值跳到下一个值,没有任何插值。这类似于 CSS 中的 steps() 函数。
  • paced 类似于 linear,只是它会忽略 keyTimes 定义的任何中间进度时间。它计算出后续值之间的距离,并将时间相应地划分。如果您的值都是线性顺序的,您将不会注意到区别。但如果它们来回移动,或者它们是颜色(被视为三维向量值),您一定会看到中间值。这是 Amelia Bellamy-Royds 提供的一个演示,展示了迄今为止提到的三个 calcMode 值之间的区别。
  • calcMode 接受的第四个值是 spline。它根据由三次贝塞尔样条曲线定义的时间函数,在 values 列表中的一个值到下一个值之间插值。样条曲线上的点在 keyTimes 属性中定义,每个区间的控制点在 keySplines 属性中定义。

您可能已经注意到上一句话中的新属性:keySplines 属性。那么,keySplines 属性的作用是什么呢?

再次回到 CSS 等效项。

在 CSS 中,您可以在每个关键帧内部指定动画节奏,而不是为整个动画指定一个动画节奏。这使您能够更好地控制每个关键帧动画的执行方式。使用此功能的一个示例是创建一个弹跳球效果。该效果的关键帧可能如下所示

@keyframes bounce {
  0% {
    top: 0;
    animation-timing-function: ease-in;
  }
  15% {
    top: 200px;
    animation-timing-function: ease-out;
  }
  30% {
    top: 70px;
    animation-timing-function: ease-in;
  }
  45% {
    top: 200px;
    animation-timing-function: ease-out;
  }
  60% {
    top: 120px;
    animation-timing-function: ease-in;
  }
  75% {
    top: 200px;
    animation-timing-function: ease-out;
  }
  90% {
    top: 170px;
    animation-timing-function: ease-in;
  }
  100% {
    top: 200px;
    animation-timing-function: ease-out;
  }
}

除了关键字缓动函数之外,我们还可以使用相应的三次贝塞尔函数

  • ease-in = cubic-bezier(0.47, 0, 0.745, 0.715)
  • ease-out = cubic-bezier(0.39, 0.575, 0.565, 1)

让我们首先为橙色圆圈指定关键时间和values 列表,使其经历相同的弹跳效果

<animate 
  xlink:href="#orange-circle" 
  attributename="cy" 
  from="50" 
  to="250" 
  dur="3s" 
  begin="click" 
  values="50; 250; 120;250; 170; 250; 210; 250" 
  keytimes="0; 0.15; 0.3; 0.45; 0.6; 0.75; 0.9; 1" 
  fill="freeze" 
  id="circ-anim">
</animate>

动画将在点击时开始,并在到达结束值时冻结。接下来,为了指定每个关键帧的节奏,我们将添加 keySplines 属性。

keySplines 属性接受与 keyTimes 列表关联的一组贝塞尔 **控制点**,定义一个控制间隔节奏的三次贝塞尔函数。属性值是一个用分号分隔的控制点描述列表。每个控制点描述是一组四个值:x1 y1 x2 y2,描述一个时间段的贝塞尔控制点。所有值必须在 0 到 1 的范围内,并且除非 calcMode 设置为 spline,否则该属性将被忽略。

keySplines 不使用三次贝塞尔函数作为值,而是采用用于绘制曲线的两个控制点的坐标。这些控制点可以在 Lea 的工具中截取的以下屏幕截图中看到。该截图还显示了每个点的坐标,每个点都用与自身相同的颜色着色。对于 keySplines 属性,我们将使用这些值来定义关键帧动画的节奏。

SMIL 允许这些值用逗号(带或不带空格)或空格分隔。定义关联段的 keyTimes 值是贝塞尔“锚点”,而 keySplines 值是控制点。因此,控制点集的数量必须比 keyTimes 少 *一个*。

如果我们回到弹跳球的例子,ease-inease-out 函数的控制点坐标显示在下面的图像中。

因此,要将其转换为 SVG 动画元素,我们将获得以下代码。

<animate 
  xlink:href="#orange-circle" 
  attributename="cy" 
  from="50" 
  to="250" 
  dur="3s" 
  begin="click" 
  values="50; 250; 120;250; 170; 250; 210; 250" 
  keytimes="0; 0.15; 0.3; 0.45; 0.6; 0.75; 0.9; 1" 
  keysplines="
    .42 0 1 1;
    0 0 .59 1;
    .42 0 1 1;
    0 0 .59 1;
    .42 0 1 1;
    0 0 .59 1;
    .42 0 1 1;
    0 0 .59 1;" 
  fill="freeze" 
  id="circ-anim">
</animate>

这是一个实时演示。

如果你只想为整个动画指定一个整体缓动函数,而不需要任何中间值,你仍然需要使用 keyTimes 属性指定关键帧,但你只需要指定开始和结束关键帧,即 0; 1,而不需要任何中间

叠加与累积动画:additiveaccumulate

有时,定义一个从前一个动画结束的地方开始的动画或一个使用前一个动画的累积总和作为值来进行的动画非常有用。为此,SVG 有两个方便命名的属性:additiveaccumulate

假设你有一个元素,你想让它的宽度“增长”,或者一条线,你想让它的长度增加,或者一个元素,你想让它逐步从一个位置移动到另一个位置,在不同的步骤中完成。此功能对于重复动画特别有用。

就像任何其他动画一样,你将指定 fromto 值。但是,当你将 additive 设置为 sum 时,它们的每个值都将 **相对于动画属性的原始值**。

所以,回到我们的圆形。对于我们的圆形,cx 的初始位置为 50。当您设置 from="0" to="100" 时,零实际上是原始的 50,而 100 实际上是 50 + 100;换句话说,它实际上有点像 “from="50" to="150"“。

通过这样做,我们将获得以下结果。

这就是 additive 属性的作用。它只是指定 fromto 值是否应该相对于当前值。该属性只接受两个值之一:sumreplace。后者是默认值,它基本上意味着 fromto 值将替换当前/原始值,这可能会导致动画开始前出现奇怪的跳跃。(尝试在上面的示例中将 sum 替换为 replace 以更好地进行比较。)

但是,如果我们希望将值相加,以便第二次重复从上一次重复的结束值开始呢?这就是 accumulate 属性发挥作用的地方。

accumulate 属性控制动画是否累积。默认值为 none,这意味着例如当动画重复时,它将从头开始。但是,你可以将其设置为 sum,它指定除了第一次之外的每次重复迭代都将建立在上一次迭代的最后一个值之上。

因此,如果我们回到之前的动画并指定 accumulate="sum",我们将获得以下更理想的结果。

请注意,如果目标属性值不支持加法,或者动画元素不重复,则 accumulate 属性将被忽略。如果动画函数仅使用 to 属性指定,它也将被忽略。

使用 end 指定动画的结束时间

除了指定动画何时开始之外,你还可以使用 end 属性指定动画何时结束。例如,你可以将动画设置为无限重复,然后在另一个元素开始动画时使其停止。end 属性接受与 begin 值类似的值。你可以指定绝对或相对时间值/偏移量、重复值、事件值等。

例如,在以下演示中,橙色圆圈在 30 秒内缓慢移动到画布的另一侧。绿色圆圈也会动画,但只有在点击它时才会动画。橙色圆圈的动画将在绿色圆圈的动画开始时结束。点击绿色圆圈以查看橙色圆圈停止。

当然,对于应用于同一元素的两个动画,也可以实现相同类型的动画同步。例如,假设我们设置圆圈的颜色以无限重复地从一个值更改为另一个值。然后,当元素被点击时,它会移动到另一侧。现在我们将设置它,以便颜色动画在元素被点击并触发移动动画时停止。

使用多个 beginend 值定义动画间隔

实际上,beginend 属性都接受 **用分号分隔的列表**。begin 属性中的每个值都将对应于 end 属性中的一个值,从而形成活动和非活动动画间隔。

你可以将其视为类似于一辆移动的汽车,汽车的轮胎会在一段时间内处于活动状态,然后处于非活动状态,具体取决于汽车是否在移动。你甚至可以通过对汽车应用两个动画来创建动画汽车效果:一个动画平移汽车或使其沿着路径移动,该路径也是一个叠加和累积动画,另一个动画以与平移同步的间隔旋转汽车的轮胎。

以下演示示例指定了多个开始和结束时间(即间隔),其中矩形根据定义的间隔旋转,相应地从活动变为非活动。(如果你错过了动画,请重新运行演示。)

请注意,在上面的示例中,我使用 元素绕其中心旋转矩形。我们将在下面的下一节中详细讨论此元素。

还要注意,即使你将 repeatCount 设置为 indefinite,它也会被 end 值覆盖,并且 *不会* 无限重复。

使用 minmax 限制元素的活动持续时间

就像你可以限制动画的重复时间一样,你甚至可以限制动画的 **活动持续时间**。minmax 属性分别指定活动持续时间的最小值和最大值。它们为我们提供了一种控制元素活动持续时间的下限和上限的方法。这两个属性都接受时钟值作为值。

对于 min,它指定活动持续时间的最小值的长度,以元素活动时间测量。值必须大于或等于 0,这是默认值,并且不会对活动持续时间造成任何约束。

对于 max,时钟值指定活动持续时间的最大值的长度,以元素活动时间测量。值也必须大于 0。max 的默认值为 indefinite。这不会对活动持续时间造成任何约束。

如果同时指定 minmax 属性,则 max 值必须大于或等于 min 值。如果不满足此要求,则这两个属性都会被忽略。

但是,什么定义了元素的 **活动持续时间**?我们之前提到了重复持续时间,除了“简单持续时间”之外,它指的是不带任何重复的动画持续时间(使用 dur 指定),那么它们如何协同工作?哪个会覆盖哪个?然后,end 属性会覆盖并简单地结束动画吗?

发生的方式是,浏览器将 *首先* 根据 durrepeatCountrepeatDurend 值计算活动持续时间。*然后*,它将计算出的持续时间与指定的 minmax 值进行比较。如果结果在边界内,则此第一个计算出的持续时间值是正确的,并且不会更改。否则,可能会出现两种情况

  • 如果第一个计算的持续时间大于max值,则元素的活动持续时间定义为等于max值。
  • 如果第一个计算的持续时间小于min值,则元素的活动持续时间将变为等于min值,并且元素的行为如下:
    • 如果元素的重复持续时间(或元素不重复的简单持续时间)大于min,则元素会以正常方式播放(min约束的)活动持续时间。
    • 否则,元素会以正常方式播放其重复持续时间(或元素不重复的简单持续时间),然后根据fill属性的值冻结或不显示。

这让我们回到了浏览器如何实际计算活动持续时间的问题。为了简洁起见,这里不详细说明。但在规范中有一个非常全面的表格,其中显示了durrepeatCountrepeatDurend属性的不同组合,然后根据每种组合显示活动持续时间。您可以查看表格,并从规范的这一部分了解更多信息。

最后,如果元素定义为在其父元素之前开始(例如,使用简单的负偏移值),则最小持续时间是从计算的开始时间而不是观察到的开始时间开始计算。这意味着min值可能没有观察到的效果。

示例:路径变形

SMIL(但不在 CSS 中)可以动画化的属性之一是 SVG 的d属性(代表数据)。d属性包含定义要绘制的形状轮廓的数据。路径数据由一系列命令和坐标组成,这些命令和坐标告诉浏览器在哪里以及如何绘制构成最终路径的点、弧线和线。

动画化此属性使我们能够变形 SVG 路径并创建形状补间效果。但是,为了能够变形形状,起始形状、结束形状和任何中间形状都需要具有完全相同的顶点/点数,并且它们需要以相同的顺序出现。如果顶点数不匹配,动画将无法正常工作。原因是形状改变实际上是通过移动顶点和插值其位置来实现的,因此如果一个顶点缺失或不匹配,路径将不再被插值。

要动画化 SVG 路径,您需要将attributeName指定为d,然后设置fromto值来指定起始形状和结束形状,并且可以使用values属性来指定您希望形状在两者之间经过的任何中间值。

为了简洁起见,这里不详细说明如何执行此操作。您可以阅读Noah Blon 的这篇优秀文章,其中他解释了他是如何使用 SMIL 创建了一种形状补间式加载动画的。Noah 文章的实时演示是这个:

这是一个 Felix Hornoiu 提供的另一个变形示例:

您甚至可以变形用作剪切蒙版的路径的值!Heather Buchel 提供了这样一个示例:

沿任意路径动画化:<animateMotion> 元素

<animateMotion> 元素是我最喜欢的 SMIL 动画元素。您可以使用它来沿路径移动元素。您可以使用两种方式之一(我们将在下面介绍)来指定运动路径,然后设置元素使其沿着该路径移动。

<animateMotion> 元素接受前面提到的相同属性,另外还有三个属性:keyPointsrotatepath。此外,在calcMode属性方面,还有一个区别,即对于<animateMotion>,默认值为paced,而不是linear

使用path属性指定运动路径

path属性用于指定运动路径。它以与path元素上的d属性相同的格式表示,并以相同的方式解释。运动路径动画的效果是在被引用对象的当前变换矩阵上添加一个补充变换矩阵,这会导致沿当前用户坐标系的 x 轴和 y 轴进行平移,平移量由随时间计算的 X 和 Y 值决定。换句话说,指定的路径是相对于元素的当前位置计算的,通过使用路径数据将元素变换到路径位置。

对于我们的圆形,我们将使其沿着如下所示的路径动画化:

使圆形沿着此路径移动所需的代码如下:

<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"></animatemotion>

这里要重点说明的一点是路径数据中的坐标。路径从移动(M)到坐标为(0, 0)的点开始,然后开始绘制曲线(c)到另一个点。需要注意的是,(0, 0)点实际上是圆形的位置,无论它在哪里——不是坐标系的左上角。正如我们在上面提到的,path属性中的坐标是相对于元素的当前位置的!

上面代码的结果是:

如果您要从(0, 0)以外的点开始指定路径,则圆形会突然跳动由起点指定的量。例如,假设您在 Illustrator 中绘制了一条路径,然后导出该路径数据以用作运动路径(这是我第一次执行此操作时所做的操作);导出的路径可能如下所示:

<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"></path>

在这种情况下,路径的起点是(100.4, 102.2)。如果我们使用此数据作为运动路径,则圆形将向右跳动约 100 个单位,向下跳动约 102 个单位,然后开始相对于新位置沿着路径进行运动。因此,在为动画准备运动路径时,请务必牢记这一点。

如果使用,属性frombytovalues将指定当前画布上的一个形状,该形状表示运动路径。

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

您还可以使用另一种方式来指定运动路径。您可以使用<mpath>元素引用外部路径,而不是使用相对的path属性。<mpath><animateMotion>元素的子元素)将使用xlink:href属性引用外部路径。

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

运动路径可以在文档中的任何位置定义;它甚至可以完全在<animateMotion>元素中定义,而根本不渲染在画布上。在下面的示例中,路径被渲染了,因为在大多数情况下,您可能希望显示元素沿着其移动的路径。

请注意,根据规范:

形状的各种 (x, y) 点为被引用对象在 CTM 上提供了补充变换矩阵,该矩阵会导致沿当前用户坐标系的 x 轴和 y 轴进行平移,平移量由随时间计算的形状的 (x, y) 值决定。因此,被引用对象会随着时间的推移而平移,平移量为运动路径相对于当前用户坐标系原点的偏移量。补充变换将应用于目标元素的transform属性或由于目标元素上的animateTransform元素而对该属性的任何动画所导致的任何变换之上。

同样,圆形的位置将被路径数据中的坐标“乘以”或“变换”。

在下面的示例中,我们有一个位于画布中间的路径。圆形位于路径的起点。但是,当应用运动路径时,圆形不会从其当前位置开始运动。查看演示以获得更好的解释。单击圆形以对其进行动画化。

请注意,圆形确实沿着路径的相同形状移动,但位于不同的位置?这是因为圆形的位置被路径数据的值变换了。

解决此问题的一种方法是,让圆形从(0, 0)开始定位,这样,当使用路径数据对其进行变换时,它将按预期开始并继续进行。

另一种方法是应用一个变换来“重置”圆形的坐标,使其在应用路径之前计算为零。

下面是上面演示的修改版本,它使用封闭路径并无限期地重复运动动画。

<animateMotion> 的覆盖规则

由于<animateMotion>有多种方式可以做同样的事情,因此只有拥有覆盖规则来指定哪些值会覆盖其他值才合乎逻辑。

<animateMotion> 的覆盖规则如下:

  • 关于运动路径的定义,<mpath>元素会覆盖path属性,path属性会覆盖valuesvalues会覆盖frombyto
  • 关于确定与keyTimes属性相对应的点,keyPoints属性会覆盖path,而path会覆盖values,而values又会覆盖frombyto

使用rotate设置元素沿运动路径的方向

在我们之前的示例中,我们沿着路径动画的元素碰巧是一个圆形。但是,如果我们动画的是具有特定方向的元素,比如一辆汽车图标呢?以下示例中的汽车图标由Freepik设计

在本例中,我用一个 ID 为“car” 的组替换了圆形,该组包含构成该组的元素。然后,为了避免上述沿着路径运动的问题,我将一个变换应用于汽车,通过特定的量来平移它,以便初始位置最终位于 (0, 0)。变换中的值实际上是汽车第一条路径开始绘制的点的坐标(在移动命令 M 之后)。

然后,汽车开始沿着运动路径移动。但是... 这就是运动的样子

汽车的方向是固定的,不会改变以匹配运动路径的方向。为了改变这一点,我们将使用rotate属性。

rotate属性可以取三个值之一

  • auto:表示随着时间的推移,对象旋转运动路径方向(即方向切线向量)的角度。
  • auto-reverse:表示随着时间的推移,对象旋转运动路径方向(即方向切线向量)的角度加上 180 度。
  • 数字:表示目标元素应用了恒定的旋转变换,旋转角度为指定的度数。

为了修正上述示例中汽车的方向,我们将从将旋转值设置为auto开始。最终我们将得到以下结果

如果你想让汽车在路径外移动,auto-reverse值可以解决这个问题。

这样看起来好多了,但是我们仍然存在一个问题:汽车看起来像是沿着路径向后移动!为了改变这一点,我们需要沿着 y 轴翻转汽车。这可以通过沿着该轴以“ -1 ”的因子缩放它来完成。因此,如果我们将变换应用于具有car ID 的g,汽车将如预期的那样向前移动。缩放变换将与我们之前应用的平移变换相连。

最终的演示如下

使用keyPoints控制动画沿着运动路径的距离

keyPoints属性提供了为每个指定的keyTimes值指定沿着运动路径的进度能力。如果指定了,keyPoints会导致keyTimes应用于keyPoints中的值,而不是values属性数组中指定的点或path属性上的点。

keyPoints采用以分号分隔的 0 到 1 之间的浮点值列表,并指示对象在由对应keyTimes值指定的时间点沿着运动路径移动多远。距离计算由浏览器的算法确定。列表中的每个进度值对应于keyTimes属性列表中的一个值。如果指定了keyPoints列表,则keyPoints列表中的值必须与keyTimes列表中的值一样多。

这里需要注意的一点是,为了使keyPoints生效,需要将calcMode的值设置为linear。它看起来也应该在逻辑上与步进动画一起使用,如果你的关键点来回移动,但它没有。

以下是 Amelia Bellamy-Royds(你应该去看看她的CodePen 个人资料)的示例,该示例使用keyPoints来模拟从预定义的偏移量开始沿着路径进行运动的行为,因为我们目前在 SMIL 中默认情况下没有这种能力。

沿着任意路径移动文本

沿着任意路径移动文本与沿着路径移动其他 SVG 元素不同。要动画化文本,你将不得不使用textPath元素,而不是animateMotion元素。

首先,让我们从将文本沿着路径定位开始。这可以通过在textPath元素内部嵌套一个text元素来完成。将沿着路径定位的文本将在textPath元素内部定义,而不是作为textPath元素的子元素。

然后,textPath将引用我们要使用的实际路径,就像我们在前面的示例中所做的那样。引用的路径也可以在画布上渲染,也可以在元素中定义。检查以下演示中的代码。

要沿着该路径动画化文本,我们将使用animate元素来动画化startOffset属性。

startOffset表示文本在路径上的偏移量。0% 是路径的起点;100% 表示路径的终点。因此,例如,如果偏移量设置为 50%,则文本将从路径的中点开始。我想你可以看到我们接下来要做什么。

通过动画化startOffset,我们将创建文本沿着路径移动的效果。检查以下演示中的代码。

动画化变换:<animatetransform></animatetransform>元素

animateTransform元素动画化目标元素上的变换属性,从而允许动画控制平移、缩放、旋转和/或倾斜。它接受与animate元素相同的属性,再加上一个额外的属性:type

type属性用于指定正在动画化的变换的类型。它可以取五个值之一:translatescalerotateskewXskewY

frombyto属性取一个值,该值使用与给定变换类型相同的语法表示

  • 对于type="translate",每个单独的值表示为<tx> [,<ty>]</ty></tx>
  • 对于type="scale",每个单独的值表示为<sx> [,<sy>]</sy></sx>
  • 对于type="rotate",每个单独的值表示为<rotate-angle> [<cx> <cy>]</cy></cx></rotate-angle>
  • 对于type="skewX"type="skewY",每个单独的值表示为<skew-angle></skew-angle>

如果你不熟悉 SVG transform属性函数的语法,为了本文的简洁性和由于语法细节以及它如何工作超出了本文的范围,我建议你在继续本指南之前阅读我之前写过的关于此主题的文章:“Understanding SVG Coordinate Systems and Transformations (Part 2): The transform Attribute”

回到之前的演示,我们在其中使用<animatetransform></animatetransform>元素旋转粉红色矩形。旋转的代码如下所示

<rect id="deepPink-rectangle" width="50" height="50" x="50" y="50" fill="deepPink">
  <animatetransform 
    xlink:href="#deepPink-rectangle" 
    attributename="transform" 
    attributetype="XML" 
    type="rotate" 
    from="0 75 75" 
    to="360 75 75" 
    dur="2s" 
    begin="0s" 
    repeatcount="indefinite" 
    fill="freeze"
  >
</rect>

fromto属性指定旋转的角度(起点和终点)以及旋转的中心。当然,在两者中,旋转的中心都保持不变。如果你没有指定中心,它将是 SVG 画布的左上角。上述代码的实时演示如下

这是一个由 Gabriel 制作的另一个有趣的示例,其中只有一个animateTransform

动画化单个变换很简单,但是,当包含多个变换时,事情会变得非常混乱和复杂,尤其是因为一个animateTransform可以覆盖另一个,因此你可能最终得到完全相反的结果,而不是添加和链接效果。此外还有 SVG 坐标系和变换的实际工作方式(参考之前提到的关于该主题的文章)。示例很多,超出了本文的范围。对于变换 SVG,我建议使用 CSS 变换。实现正在努力使后者与 SVG 完美配合,因此你可能根本不需要使用 SMIL 来动画化 SVG 中的变换。

<set></set>元素

set元素提供了一种简单的方法来设置指定持续时间的属性的值。它支持所有属性类型,包括那些无法合理插值的属性类型,例如字符串和布尔值。set元素是非累加的。不允许使用累加和累积属性,如果指定它们将被忽略。

由于set用于在特定时间设置元素一个特定值,因此它不接受之前提到的动画元素的所有属性。例如,它没有fromby属性,因为更改的值不会在一段时间内逐步更改。

对于set,您可以指定目标元素、属性名称和类型、to值,并使用以下参数控制动画时间:begindurendminmaxrestartrepeatCountrepeatDurfill

以下示例演示了在单击旋转矩形时将其颜色设置为蓝色。颜色将保持蓝色 3 秒,然后恢复为原始颜色。每次单击矩形时,都会触发set动画,并且颜色会更改 3 秒。

可以动画化的元素、属性和特性

并非所有 SVG 属性都可以动画化,并且并非所有可以动画化的属性都可以使用所有动画元素进行动画化。有关所有可动画化属性的完整列表以及显示哪些元素可以动画化哪些属性的表格,请参阅SVG 动画规范的这一部分

最后的话

SMIL 潜力巨大,我只是触及了表面,只涉及了它们在 SVG 中的工作原理的基础知识和技术细节。可以创建许多非常令人印象深刻的效果,尤其是涉及变形和变换形状的效果。天马行空吧!别忘了与社区分享你的作品;我们很想看看你做了什么。感谢您的阅读!

本文已根据评论中的讨论进行了更新。感谢你的意见,Amelia。=)