SVG 动画可以为你的矢量图形增添生气,使你的 Web 体验更具交互性和吸引力。你可以通过动画化 SVG 形状、描边和变换,创造出从弹跳的球到复杂的信息图表和运动图形的各种效果。
在本质上,SVG 动画涉及对 SVG 元素的不同方面进行动画处理,如形状、描边、填充和变换等。通过随时间操作这些属性,Web 设计师和开发人员可以实现各种效果,从简单的过渡到复杂的运动图形。
在 Web 中给 SVG 添加动画,常见的方式主要有:
<animate> 和 <animateTransform> 元素来创建SMIL动画@keyframes 和 animation 或 transition属性,你可以动画化 SVG 的属性,如描边(stroke)、填充(fill)和变换(transform)等SVG 动画标准和 SMIL 动画语言提供了一种强大的方法来赋予 SVG 元素生命。它涉及使用 <animate> 元素以及 SMIL 语法来定义动画属性及其值。这种方法允许精确控制时间和缓动函数,产生平滑而无缝的动画。然而,值得注意的是,SMIL 曾被列入 Chrome 的弃用计划(后恢复支持),目前主流浏览器都能运行它,但社区对其长期前景仍有疑虑,新项目建议优先考虑 CSS 或 WAAPI。
另一方面,CSS 动画(transition 和 animation)因其对 SVG 元素的动画效果而受到欢迎。它们提供了一种更灵活且广泛支持的方法。通过 CSS 动画,可以使用关键帧和各种 CSS 属性(如 @keyframes 、transition 和 animation)来为 SVG 属性添加动画效果。这使得更容易与现有的 CSS 集成,并提供对动画效果的广泛控制。 CSS 动画还可以无缝地与 HTML 和 SVG 元素一起使用,使其成为动画 SVG 图形的多功能选择。
除此之外,我们还可以使用 WAAPI 提供的丰富的 API 来动画化 SVG 元素的属性,从而给 SVG 添加动画效果。这一点与 CSS 动画化 SVG 属性非常相似。不同的是,WAAPI 提供了更多控制动画的 API,而且这些 API 都是源于浏览器动画引擎,在性能方面具有先天的优势。
这意味着,在 Web 上,可以使用 SMIL、CSS 和 WAAPI 给 SVG 元素添加动画效果。但在选择技术时,了解每种技术的优势和劣势非常重要。SMIL 动画提供了精确的控制,但浏览器支持有限;CSS 动画提供了更广泛的兼容性,但在控制动画播放方式有着一定的缺陷性;WAAPI 提供了更多制作和控制动画的 API,但需要一定的 JavaScript 知识。
总而言之,SVG 动画是一种令人惊叹的技术,将乏味的矢量图形转化为令人兴奋和交互式的体验。无论你使用带有 SMIL 的 SVG 动画标准还是 CSS 动画,还是 WAAPI 动画,都可以通过平滑的过渡、酷炫的效果和用户互动,为你的创作赋予生命。凭借其可伸缩性、文件小、定制灵活和无障碍兼容性,SVG 动画是使你的 Web 应用或网站在视觉上令人惊叹且用户友好的必备工具。随着 Web 的不断发展,SVG 动画将继续在数字世界中展现引人入胜和吸引人的体验!
众所周知,你可以使用 CSS 的 transition 对各种属性进行动画化来创建动态的视觉效果。虽然并非每个属性都是“可动画化的”,但许多属性可以使用 CSS 与 SVG 无缝过渡或动画。例如,通过使用 transform 、opacity 和 color 等属性,将静态 SVG 转换为具有动态效果的 SVG。
以常见的“汉堡菜单图标”为例:
这是一个微妙但有价值的动画,它引起用户的注意,告诉他们该图标可用于关闭菜单。现在,我们也用 SVG 和 CSS 的 transition 来制作一个类似的带有动画效果的“汉堡菜单图标”。当用户鼠标悬浮(或点击)图标时,它将转变为关闭图标。
先从 SVG 结构开始,将使用三个 SVG 的 <rect> 元素绘制“汉堡菜单图标”。第一个表示顶部条,第二个表示中间条,最后一个表示底部条:
<svg xmlns="http://www.w3.org/2000/svg" class="icon--hamburger" width="64" height="64" viewBox="0 0 48 48">
<rect class="hamburger__top" x="0" y="0" rx="4" ry="4" width="100%" height="8" />
<rect class="hamburger__middle" x="0" y="20" rx="4" ry="4" width="100%" height="8" />
<rect class="hamburger__bottom" x="0" y="40" rx="4" ry="4" width="100%" height="8" />
</svg>现在,可以使用 viewBox 属性建立一个坐标系统,提供默认的x、y、width 和 height。我们设置 width 和 height 属性以实现尺寸的灵活性,解决FOUS(无样式闪烁)。我们使用 x 和 y 坐标定位条形,使用 rx 和 ry 给 <rect> 绘制圆角,分配高度,并使用 100% 的宽度。我们通过设置等间距的 y 坐标来有策略地堆叠条形。
在 CSS 部分,我们定义自定义属性,如图标大小和条形高度:
:root {
--icon-size: 48;
--icon-bar-size: 8;
}我们的正方形 SVG 的边长为 64px。对于关闭图标动画,将变换原点设置为顶部和底部条的中心,以避免由于对角线长度而导致的故障。
沿 y 轴平移条形以保持居中,调整元素高度。确定所需的 y 轴平移距离并设置过渡持续时间:
:root {
--icon-size: 48;
--icon-bar-size: 8;
--icon-translation-size: calc((var(--icon-size) - var(--icon-bar-size)) / 2);
--icon-transition-duration: 300ms;
}接下来,配置过渡并将 ease-in-out 作为过渡函数应用于菜单条,以增强从内到外的过渡。
如前所述,将变换原点设置为中心是至关重要的。为此,我们必须为顶部和底部条的悬停状态设置 transform-origin 属性的值为 center:
.icon--hamburger {
rect {
transition: all var(--icon-transition-duration) ease-in-out;
}
&:hover {
:is(.hamburger__top, .hamburger__bottom) {
transform-origin: center;
}
}
}将顶部条(.hamburger__top)顺时针旋转 45deg 度,底部条(.hamburger__bottom)逆时针旋转 45deg度。在动画期间通过将中间条(.hamburger__middle)的不透明度(opacity)设置为 0 来隐藏它。可以将这些变换应用于 SVG 形状的悬停状态或可切换的类,为简单起见,可以在CSS中使用悬停伪类:
.icon--hamburger {
&:hover {
.hamburger__top {
transform: rotate(45deg) translate(0, calc(var(--icon-translation-size) * 1px));
}
.hamburger__bottom {
transform: rotate(-45deg) translate(0, calc(var(--icon-translation-size) * -1px));;
}
.hamburger__middle {
opacity: 0;
}
}
}在此基础上,还可以对 <rect> 元素的 fill 属性进行动画化:
:root {
--icon-color: #747d8c;
--icon-active-color: #2ed573;
}
.icon--hamburger {
color: var(--icon-color);
rect {
fill: currentColor
}
&:hover {
rect {
fill: var(--icon-active-color);
}
}
}你最终看到的效果如下:
除了使用 CSS 的 transition 给 SVG 添加动画之外,还可以通过使用 CSS 的 @keyframes 规则,定义一系列关键帧,这些关键帧将 SVG 元素转换为为引人入胜的动画。例如,下面这个加载动画,使用 SVG 和 CSS 一起来创建是相当简单的。
只需要使用 SVG 的 <circle> 元素绘一个带有透明度填充和 4px 描边的圆,然后使用 cx 和 cy 属性将其精确定位在 SVG 的中心位置。然后,考虑描边宽度(stroke-width)调整圆的半径 r ,具体的 SVG 代码如下:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="64" height="64" class="spinner">
<circle cx="24" cy="24" r="22" fill="transparent" stroke-width="4" />
</svg>使用CSS关键帧,将动画从 0deg 旋转到 360de度,创建一个完整的旋转。使用CSS设置SVG元素的 animation 属性,应用线性操作和无限循环,持续一秒:
:root {
--spinner-animation-speed: 1s;
}
@keyframes spin {
from {
rotate:0deg;
}
to {
rotate:360deg;
}
}
.spinner {
animation: spin var(--spinner-animation-speed) linear infinite;
}为了增强动画效果,使用 SVG 的 linearGradient 和 stop 元素引入渐变效果。在 linearGradient 元素中调整值,如 x1、x2、y1、y2、stop-color 和 stop-opacity,以尝试创建各种加载旋转器变体:
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 48 48" class="spinner">
<defs>
<linearGradient id="gradientFade" x1="20%" x2="50%" y1="60%" y2="100%">
<stop offset="0%" class="spinner-gradient-stop" />
<stop offset="25%" stop-opacity="75%" class="spinner-gradient-stop" />
<stop offset="50%" stop-opacity="50%" class="spinner-gradient-stop" />
<stop offset="75%" stop-opacity="25%" class="spinner-gradient-stop" />
<stop offset="100%" stop-opacity="5%" class="spinner-gradient-stop" />
</linearGradient>
</defs>
<g>
<circle cx="24" cy="24" r="22" fill="transparent" stroke="url(#gradientFade)" stroke-width="4" />
</g>
</svg>为每一个 <stop> 元素设置一个类,例如 .spinner-gradient-stop ,以便 CSS 设置样式,例如在 CSS 中覆盖 <stop> 元素的 stop-color 属性的值。最终效果的 CSS 代码如下:
@layer demo {
:root {
--spinner-animation-speed: 1s;
--spinner-color: #8854d0;
}
@keyframes spin {
from {
rotate: 0deg;
}
to {
rotate: 360deg;
}
}
.spinner {
fill: currentColor;
animation: spin var(--spinner-animation-speed) linear infinite;
.spinner-gradient-stop {
stop-color: var(--spinner-color);
}
}
}最终呈现给你的效果如下:
使用 SVG 还可以制作一些更微妙的动画效果,例如下面这个导航菜单的效果:
Demo 地址:https://codepen.io/milanraring/full/qBEPzKB (来源于 @Milan Raring)
制作一些强交互性的动画效果:
Demo 地址:https://codepen.io/agathaco/full/NyQgQX (来源于 @agathaco)
甚至还可以结合 SVG 的滤镜特性创作出一些更高级,更复杂和更微妙的动画效果,例如下面这个导航栏的效果:
Demo 地址:https://codepen.io/v_Bauer/full/WNroMOq (来源于 @Vadim Bauer)
上面所展示的动画看上去似乎比较复杂,但并没有大家想象的那么复杂,如果你对 SVG 和 CSS 动画相关的知识较为了解,创作这些动画还是手到擒来!我们在后面的课程上还会详细与大家一起探讨 CSS 如何给 SVG 添加动画。
简单地说,WAAPI 给 SVG 元素添加动画就像是 CSS 给 SVG 动画添加元素,只不过它是使用 JavaScript API 来完成。例如前面那个圆形加载动画效果,要是使用 WAAPI 的话,可以像下面这样添加动画:
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 48 48" class="spinner">
<defs>
<linearGradient id="gradientFade" x1="20%" x2="50%" y1="60%" y2="100%">
<stop offset="0%" class="spinner-gradient-stop" />
<stop offset="25%" stop-opacity="75%" class="spinner-gradient-stop" />
<stop offset="50%" stop-opacity="50%" class="spinner-gradient-stop" />
<stop offset="75%" stop-opacity="25%" class="spinner-gradient-stop" />
<stop offset="100%" stop-opacity="5%" class="spinner-gradient-stop" />
</linearGradient>
</defs>
<g>
<circle cx="24" cy="24" r="22" fill="transparent" stroke="url(#gradientFade)" stroke-width="4" />
</g>
</svg>@layer demo {
:root {
--spinner-color: #8854d0;
}
.spinner {
fill: currentColor;
.spinner-gradient-stop {
stop-color: var(--spinner-color);
}
}
}const aniEle = document.querySelector(".spinner");
const spin = [
{
rotate: "0deg",
offset: 0
},
{
rotate: "360deg",
}
];
const options = {
duration: 1000,
easing: "linear",
iterations: Infinity
};
aniEle.animate(spin, options);最终呈现给你的动画效果和 CSS 给 SVG 添加动画效果是一样的:
除了 CSS 和 WAAPI 对 SVG 进行动画之外,还可以使用 SMIL 对 SVG 进行动画处理。这也是本篇的主旨,接下来我们一起来聊聊 SMIL 动画。
SMIL(Synchronized Multimedia Integration Language)是 W3C 推荐的 XML 标记语言,用于描述多媒体演示。它定义了用于定时、布局、动画、视觉过渡和媒体嵌入的标记。SMIL 允许呈现诸如文本、图像、视频、链接到其他 SMIL 演示以及来自多个 Web 服务器的文件等媒体项。 SMIL 标记以 XML 编写,与 HTML 具有相似之处。
另外,SMIL 的设计目标是提供一种描述时间和空间的结构,使得多种媒体元素能够同步播放或呈现的方式。其主要特点和功能如下:
总体而言,SMIL 为开发者提供了一种强大的工具,用于创建时间和空间上同步的多媒体演示。尽管在 Web 中的广泛使用受到了一些限制,但它仍然在特定的场景和应用中发挥着重要作用。
SMIL Animation(Synchronized Multimedia Integration Language Animation)是一种使用 SMIL(Synchronized Multimedia Integration Language)标准创建的多媒体动画。SMIL Animation 允许 Web 开发者定义和控制在 Web 上呈现的多媒体元素(如图像、音频、视频等)的时间序列和交互。
SMIL 本身是一种 XML(可扩展标记语言)应用,它提供了一种描述和同步多媒体元素的标准方式。SMIL Animation 通过在 SMIL 文档中使用特定的元素和属性,使得开发者能够创建丰富的多媒体演示和动画效果。
以下是SMIL Animation的一些关键特性和用法:
尽管 SMIL Animation 在过去在 Web 开发中曾经很流行,但随着 HTML5 和 CSS3 的兴起,以及对 JavaScript 动画的广泛应用,其使用逐渐减少。然而,SMIL Animation 仍然在某些特定的场景和应用中具有一定的优势,特别是在需要强调多媒体元素同步性的情况下。
注意,了解 SVG 动画时, SMIL Animation 相比于 SVG 和 SMIL 更为重要。
SVG 是一种用于描述二维矢量图形的 XML 标记语言,用于在 Web 上呈现图形。SVG 本身提供了一些基本的图形元素,例如矩形、圆形、线条等,以及文本和图像的支持。与此同时,SVG 还支持通过 SMIL和SMIL Animation(SMIL动画) 等标准来添加多媒体和动画效果。
简单地说,SVG 图形可以使用动画元素进行动画处理。这些动画元素最初是在 SMIL Animation 规范中定义的,其中包括:
<animate> : 它允许你在 SVG 图形中定义各种动画效果,如平移、旋转、缩放、颜色变化等。<set> :它允许你在 SVG 图形中指定一个属性的值,并在特定的时间点将其设置为目标值。 <set> 通常用于创建静态的、非交互式的属性更改。<animateMotion> : 它允许你在 SVG 图形中指定元素的路径,然后动画元素将沿着运动路径移动。 <animateMotion> 提供了一种简便的方法来定义元素的动态运动。<animateColor> : 它允许你在 SVG 图形中指定颜色属性的动画效果,实现颜色的平滑过渡和变化。<animateColor> 主要用于对 SVG 元素的颜色属性(如 fill 或 stroke)进行动画处理。请注意,该元素已被弃用,推荐使用animate元素直接定位可以接受颜色值的属性。尽管它仍然存在于 SVG 1.1 规范中,但明确指出已被弃用,并已在 SVG 2 规范中完全删除。除了 SMIL 规范中定义的动画元素外,SVG 还包括与 SMIL 动画规范兼容的扩展。这些扩展包括扩展元素功能的属性和其他动画元素。SVG 扩展包括:
<animateTransform> : 它允许你对 SVG 元素应用不同类型的变换,如平移、缩放、旋转和倾斜。通过使用 <animateTransform>,你可以为这些变换属性创建平滑的动画效果。path(属性): 允许在 <animateMotion> 元素的 path 属性中指定 SVG 路径数据语法的任何特征(SMIL Animation 仅允许在 path 属性中使用 SVG 路径数据语法的子集)。<mpath> :SVG 动画中的一个元素,用于指定一个路径元素,使得其他动画元素(比如 <animateMotion>)沿着这个路径进行运动。它通常作为 <animateMotion> 元素的子元素出现,用于将动画运动的路径与动画元素分离,使得动画元素的运动更加灵活。keypoints(属性):作为<animateMotion> 的属性,用于精确控制运动路径动画的速度。rotate(属性):作为 <animateMotion> 的属性,用于控制对象是否会自动旋转,以使其 x 轴指向运动路径的方向切线矢量相同(或相反方向)。该属性是使沿路径运动正常工作的关键。SVG 动画在性质上类似于 CSS 动画和过渡。关键帧被创建,物体移动,颜色改变等。然而,它们可以做一些 CSS 动画无法做到的事情,将在后文中详细介绍。
首先,我们来看看如何将动画添加到简单的 SVG 形状中。
通常情况之下,使用 SVG 元素绘制的形状是静态的,例如,使用 <rect> 绘制一个 40 x 40 的矩形:
<svg width="400" height="300" viewBox="0 0 400 300" class="element">
<rect x="50" y="50" width="40" height="40" stroke="lime" fill="#3498db" stroke-width="2"/>
</svg>正如你所看到的,矩形没有任何动画效果。
如果你要给绘制的矩形元素添加一个动画效果,那么你得使用适当的 SVG 动画元素将动画嵌套进去。例如,使用 <animate> 元素来对单一属性进行动画:
<svg width="400" height="300" viewBox="0 0 400 300" class="element">
<rect x="50" y="50" width="40" height="40" stroke="lime" fill="#3498db" stroke-width="2">
<!-- 动画元素嵌套在这里 -->
<animate attributeName="width" from="40" to="300" begin="0s" dur="5s" repeatCount="indefinite" fill="freeze" />
</rect>
</svg>上面的代码,在嵌套的动画元素 <animate> 中,定义了动画的属性,例如 attributeName、from、to、begin、dur 等。这些属性控制着动画的行为,例如属性名称、起始值、结束值、开始时间、持续时间等。
通过像这样嵌套动画元素,你定义了动画的作用范围。因为在这种情况下,<animate> 元素直接嵌套在 <rect> 元素内,所以将对包含的 <rect> 元素的属性进行动画处理。这将使矩形的宽度在 5s 内从 40 个单位变化到 300 个单位,并且不断的重复播放这个动画效果:
正如你所看到的,将 <animate> 元素嵌套在 <rect> 元素内,并且使用其简单语法在动画的持续时间产生属性(width)的线性变化。然而,SVG 提供了其他方法来添加非线性或非插值的动画。
例如,在 SVG 中,你可以使用 calcMode 属性创建非线性和非插值的动画。以下是一个简单的示例,演示了在颜色变化方面使用 calcMode 属性:
<svg width="400" height="300" viewBox="0 0 400 300" class="element">
<rect x="50" y="50" width="300" height="50" fill="#ffffff">
<animate attributeName="fill" calcMode="linear" from="#ffffff" to="#ff3366" begin="1s" dur="5s" fill="freeze" />
</rect>
<rect x="50" y="150" width="300" height="50" fill="#ffffff">
<animate attributeName="fill" calcMode="discrete" from="#ffffff" to="#ff3366" begin="1s" dur="5s" fill="freeze" />
</rect>
</svg>在上面这个示例中,我们创建了两个矩形,它们都有颜色变化的动画。第一个矩形(顶部的矩形)使用了 calcMode="linear" ,矩形的填充色(fill)在 5s 内从白色(#ffffff)缓慢变为玫红色(#ff3366),它会在动画期间线性变化。第二个矩形(底部的矩形)使用了 calcMode="discrete" ,矩形的填充色保持白色,直到过了 5s ,然后颜色突然从白色变成玫红色。也就是说,第二个矩形颜色变化是非插值的,即在动画结束时突然切换到目标值。
注意,应用在 <animate> 元素中的 fill 属性和应用在 SVG 元素,如 <rect> 元素的 fill 属性所起的作用不同。如果你想让 SVG 元素保留在动画结束时的状态,就需要在 <animate> 元素上显式设置 fill="freeze" 。稍后我们还会详细介绍该属性。
上面这个示例告诉我们,在没有插值值存在的情况下,将 calcMode 设置为 discrete 尤其重要。例如,当你将 visibility 属性从 visible 更改变 hidden 或者从 hidden 更改变 visible 。
你还可以使用 calcMode 属性的其他值,如 paced 和 spline,来实现不同类型的非线性动画。当 calcMode 属性值为 paced 时,动画将在给定的时间内均匀分布,以便在整个动画周期内保持匀速。
<svg width="400" height="100" viewBox="0 0 400 100" class="element">
<!-- 使用 calcMode="paced" 创建均匀分布的动画 -->
<circle cx="50" cy="50" r="40" fill="lime">
<animate attributeName="cx" values="50; 300" dur="4s" calcMode="paced" repeatCount="indefinite"/>
</circle>
</svg>在这个例子中,一个圆沿着 x 轴从起点 (50, 50) 移动到终点 (300, 50)。由于 calcMode 设置为 paced,它将以均匀的速度移动。
在 spline 模式下,你可以使用 keyTimes 和 keySplines 属性来定义动画的变化曲线。这些属性允许你更精细地控制动画的进度。
<svg width="400" height="100" viewBox="0 0 400 100" class="element">
<!-- 使用 calcMode="spline" 创建复杂路径的动画 -->
<circle cx="50" cy="50" r="40" fill="lime">
<animate attributeName="cx" values="50; 300" dur="4s" calcMode="spline" keyTimes="0; 1" keySplines="0.5 0 0.5 1" repeatCount="indefinite"/>
</circle>
</svg>在这个例子中,圆沿着 x 轴从起点 (50, 50) 移动到终点 (300, 50),但使用了 spline 模式来定义路径。keyTimes 属性指定关键时刻,而 keySplines 属性定义了与这些关键时刻对应的贝塞尔曲线控制点。这样,你可以创建更加复杂和自定义的路径。
下面这个示例演示了 calcMode 四种不同模式带来的动画效果差异:
除此之外,SVG 还允许你随时间动画 SVG 元素的许多属性。你可以改变形状的颜色、位置、透明度,或使形状的一部分可见或隐藏,例如,通过使用声明性动画语法。另外,你还可以随时间动画 SVG 滤镜,创建特别引人注目或微妙的动画效果。
下面是一个使用 SVG 滤镜创建动画效果的简单案例。在这个例子中,将使用 <feGaussianBlur> 滤镜来创建一个模糊效果,并通过 <animate> 元素改变模糊半径以产生动画。
<svg width="400" height="150" viewBox="0 0 400 150" class="element">
<defs>
<filter id="blurFilter">
<feGaussianBlur in="SourceGraphic" stdDeviation="0" result="blurResult">
<!-- 使用动画改变滤镜属性(模糊半径) -->
<animate attributeName="stdDeviation" values="0;5;0" dur="2s" keyTimes="0;0.5;1" repeatCount="indefinite" />
</feGaussianBlur>
</filter>
</defs>
<!-- 创建矩形元素并应用滤镜 -->
<rect x="10" y="10" width="380" height="130" fill="lime" filter="url(#blurFilter)" />
</svg>在这个例子中,<feGaussianBlur> 滤镜用于创建模糊效果。通过 <animate> 元素,我们动态地改变 stdDeviation 属性,即模糊半径。values 属性定义了模糊半径在动画过程中的变化,dur 属性设置了动画的持续时间,keyTimes 属性指定了关键时间点,repeatCount 属性设置为 indefinite 表示动画将无限循环。你可以根据需要调整这些属性值以及滤镜效果,创建不同的动画效果。
通过上面这几个简单的示例演示,我想你已经知道了如何给 SVG 静态元素添加动画效果。不知道是你否发现了,上面这几个示例都是将动画“封装”到应用于的元素中,即目标元素将是当前动画元素的直接父元素:
<rect width="50" height="50">
<animate />
</rect>如果你想在 SVG 中将“动画元素”与目标元素分开,那么可以通过 xlink:href 来实现。即使用 xlink:href 将“动画元素”与目标元素绑定在一起。该属性接受对当前 SVG 文档片段中的目标元素的 URI 引用,因此该目标元素将随时间而修改。例如:
<svg width="400" height="300" viewBox="0 0 400 300" class="element">
<defs>
<animate xlink:href="#animated" attributeName="width" from="40" to="300" begin="0s" dur="5s" repeatCount="indefinite" fill="freeze" />
</defs>
<rect id="animated" x="50" y="50" width="40" height="40" stroke="lime" fill=" #3498db" stroke-width="2" />
</svg>上面这个示例,我们使用 xlink:href="#animated" 指定了动画(<animate>)的目标元素,即 ID 为 "animated" 的矩形。它与下面这段代码所呈现的效果是相同的:
<svg width="400" height="300" viewBox="0 0 400 300" class="element">
<rect x="50" y="50" width="40" height="40" stroke="lime" fill=" #3498db" stroke-width="2">
<animate attributeName="width" from="40" to="300" begin="0s" dur="5s" repeatCount="indefinite" fill="freeze"/>
</rect>
</svg>现在,我们已经知道如何给 SVG 静态元素添加动画。不过,仅仅知道这些还是不够的,我们还需要知道如何在动画元素上处理动画内容。即“可动画化的 SVG 元素属性”。
在 SVG 中,要给 SVG 静态元素添加动画效果,需要执行两个步骤:
<animate> 、<animateTransform> 和 <animateMotion> 等 )嵌套在目标元素中,或者通过 xlink:href 指定目标元素在 SVG 中定义动画和在 CSS 中定义动画非常相似。要在 SVG 中定义动画,需要知道要进行动画处理的内容是什么,动画将在何时开始,要进行动画处理的属性的起始值是什么,以及在动画结束时该属性的演示值是什么。
接下来一起来探讨如何在动画元素上定义动画属性。
就 SVG 动画而言,通常是由 attributeName属性的值指定,该属性用于指定你要动画化的属性的名称。例如:
<svg width="400" height="20" viewBox="0 0 400 20" class="element">
<rect width="400" height="20" fill="#356908" rx="10">
<animate attributeName="width" from="0" to="400" dur="5s" repeatCount="indefinite" />
</rect>
</svg>在这个示例中,我们有一个绿色的矩形(#356908),通过 <animate> 元素对其 width 属性进行动画。在 <animate> 元素中,attributeName 被设置为 "width" ,这意味着我们希望动画矩形(<rect> )的宽度(width)属性。
attributeName 属性只接受一个值,而不是值列表,因此一次只能对一个属性进动画化。如果要动画化多个属性,则需要为该元素定义多个动画。例如,基于上面这个示例,如果你希望同时对矩形的宽度和填充颜色两个属性进行动画化,则需要同时在 <rect> 中同时嵌套两个 <animate> 元素,并且将 attributeName 属性分别指定为 width 和 fill :
<svg width="400" height="20" viewBox="0 0 400 20" class="element">
<rect width="400" height="20" fill="#356908" rx="10">
<!-- 对 rect 元素的 width 属性进行动画化 -->
<animate attributeName="width" from="0" to="400" dur="5s" repeatCount="indefinite" />
<!-- 对 rect 元素的 fill 属性进行动画化 -->
<animate attributeName="fill" from="#356908" to="#ff3366" dur="5s" repeatCount="indefinite" />
</rect>
</svg>就这一点而言,CSS 要比 SMIL 更具优势。但从另一个角度来说,一次只定义一个属性名称是有道理的,否则其他属性的值可能会变得过于复杂,难以处理。
正如你所看到的,在 SVG 中,在“动画元素”指定 attributeName 属性的值,可以指定要应用动画的确切属性,从而实现不同种类的动画效果。
在指定动画化属性名称的同时,还可以通过 attributeType 属性用于指定动画要操作的属性的命名空间。在 SVG 动画中,有两个主要的命名空间:CSS 和 XML :
<svg width="400" height="200" xmlns="http://www.w3.org/2000/svg">
<rect x="50" y="50" width="300" height="50" fill="lime">
<!-- 通过 attributeType 属性,指定动画要操作的属性的命名空间是 XML -->
<animate attributeType="XML" attributeName="width" from="50" to="300" dur="5s" repeatCount="indefinite" />
</rect>
<rect x="50" y="100" width="300" height="50" fill="lime">
<!-- 通过 attributeType 属性,指定动画要操作的属性的命名空间是 CSS -->
<animate attributetype="CSS" attributename="opacity" from=".3" to="1" dur="5s" repeatcount="indefinite" />
</rect>
</svg>通常情况下,如果要操作的属性是 CSS 属性,可以将 attributeType 设置为 "CSS" (这意味着该属性也可以作为 CSS 属性找到)。而其他属性仅属于 "XML"。
如果 attributeType 属性的值没有被明确设置或设置为 auto,则浏览器必须首先在 CSS 属性列表中搜索匹配的属性名称,如果找不到,则在元素的默认 XML 命名空间中搜索。这意味着,在实际应用中,通常不需要显式设置 attributeType,因为浏览器会根据属性的类型自动选择正确的命名空间。
当使用 SVG 动画元素时, from、to、dur 、begin 和 fill 等属性用于定义动画的关键特征。通过一个实际案例详细解释它们的作用和如何使用:
<svg width="400" height="120" viewBox="0 0 400 120" xmlns="http://www.w3.org/2000/svg" class="element">
<circle cx="50" cy="60" r="50" fill="lime">
<animate attributeName="cx" from="50" to="350" dur="2s" begin="click" fill="freeze" />
</circle>
</svg>上面这个示例,使用 <animate> 更改 <circle> 的 cx 属性的值(圆心位置),把圆从 50 (起始位置)移动到 350 (终点位置),整个过程持续 2s 钟,并在动画结束后保持在最终状态。为了在一段时间内将一个值更改变另一个值,我们使用了 from 、to 和 dur 属性。
from 属性: from="50" 指定动画属性的起始值为 50to 属性: to="350" 指定动画属性的结束值为 350dur 属性: dur="2s" 指定动画的持续时间为 2s其中 from 和 to 属性类似于 CSS @keyframes 中的 from 和 to 关键帧:
@keyframes moveCircle {
from {
/* 起始值 */
}
to {
/* 结束值 */
}
}dur 属性类似于 CSS 中的 animation-duration :
.animated {
animation-duration: 2s;
}除了这些属性,你还会想要指定动画应该在什么时候开始,这就是 begin 属性的作用。在上面的示例中,我们设置了 begin 属性的值为 "click" 。这意味着圆将在点击时移动。你也可以将此值设置为时间值,例如 begin="0s" ,表示应用于圆的动画将在页面加载后立即启动。你还可以将 begin 设置正的时间值来延迟动画。例如,begin="2s" 将在页面加载后的 2s 后启动动画。
另外,你也可以将 begin 属性的值设置为 click + 1s 这样的值,它会在元素(<circle>)被点击后的 1s 后启动动画:
<svg width="400" height="120" viewBox="0 0 400 120" xmlns="http://www.w3.org/2000/svg" class="element">
<circle cx="50" cy="60" r="50" fill="lime">
<animate begin="click + 1s" attributeName="cx" from="50" to="350" dur="2s" fill="freeze" />
</circle>
</svg>更有趣的是,你还可以将 begin 设置为其他值,允许你在不必计算其他动画的持续时间和延迟的情况下同步动画。稍后会详细介绍这一特性。
你也可能已发现了,动画结束之后,圆的停留位置保持在最终状态。这是“动画元素”的 fill 属性所起的作用,它有点类似于 CSS 的 animation-fill-mode 属性,该属性指定动画结束后元素应该如何填充。SVG 中的值与 CSS 中的值相似,只是名称不同:
freeze (默认值):动画结束后,元素将保持在动画的最终状态。换句话说,它会被冻结在最后一帧的状态上,不再改变remove :动画结束后,元素将被移除,不再应用动画效果。这意味着元素会返回到其初始状态在 SVG 动画元素中, by 属性主要用于指定相对偏移,即动画属性相对于其初始值的偏移量。通过使用 by ,你可以定义相对于 from 或当前值的增量,而无需显式指定目标值 to 。例如:
<svg width="400" height="150" viewBox="0 0 400 150" xmlns="http://www.w3.org/2000/svg" class="element">
<circle cx="50" cy="75" r="50" fill="orange">
<!-- 将 cx 属性从当前值增加 300 -->
<animate attributeName="cx" from="50" by="300" dur="2s" fill="freeze" begin="click" />
</circle>
</svg>上面示例中,cx 属性的初始值是 50 个单位值,通过使用 by="300" ,动画将 cx 属性从当前值(50)开始增加 300 ,因此最终位置是 350 。它与 to="350" 最终效果是等同的:
<svg width="400" height="150" viewBox="0 0 400 150" xmlns="http://www.w3.org/2000/svg" class="element">
<circle cx="50" cy="75" r="50" fill="orange">
<animate attributeName="cx" from="50" to="350" dur="2s" fill="freeze" begin="click" />
</circle>
</svg>by 属性在 SVG 动画中的应用非常有用,特别是在相对性动画和累积动画中。在相对性动画中,你可以指定相对于当前值的增量,而无需知道元素的确切初始值。在积累动画中,多个动画可以基于之前动画的结果进行操作,通过 by 属性可以更方便地实现这一点。
SVG 动画中,你可以使用 restart 属性来控制动画元素在其活动期间是否可以被重新启动,即使在其活动期间也可以。这对于 SVG 动画处于活动状态时阻止重新启动可能是有用的。你可以将 restart 属性设置为三种可能的值:
always (始终重新启动) :这是默认值,动画可以在任何时候重新启动,即使在其活动期间也可以whenNotActive (非活动时重新启动) :动画只能在其非活动状态下重新启动,即在活动结束后。如果尝试在活动期间重新启动动画,该尝试将被忽略never (永不重新启动):元素在其父时间容器的当前简单持续时间内不能重新启动。(在 SVG 的情况下,由于父时间容器是 SVG 文档片段,因此动画在文档持续时间内不能重新启动。)课程前面曾提到过,可以将 begin 设置为其他值,允许你在不必计算其他动画的持续时间和延迟的情况下同步动画。
简单地说,在 SVG 动画中,可以使用 begin 属性来命名动画并同步它们。通过给动画元素设置 id 属性,可以为动画命名,然后在其他动的的 begin 属性中引用这个 id ,以实现动画的同步。比如下面这个示例,向大家展示了如何使用 begin 属性同步两个命名的动画:
<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg" class="element">
<!-- 圆形动画 -->
<circle r="30" cx="50" cy="50" fill="orange">
<animate begin="click" id="circ-anim" attributeName="cx" from="50" to="450" dur="5s" fill="freeze" />
</circle>
<!-- 矩形动画,开始时间与圆形动画同步 -->
<rect width="50" height="50" x="25" y="200" fill="#0099cc">
<animate begin="circ-anim.begin + 1s" id="rect-anim" attributeName="x" from="50" to="425" dur="5s" fill="freeze" />
</rect>
</svg>上面这个示例中,圆形(<circle>)和矩形(<rect>)动画(嵌套在它们里面的 <animate>)都有一个唯一的 id ,分别是 circ-anim 和 rect-anim 。嵌套在矩形(<rect>)的 <animate> 元素的 begin 属性设置为 "circ-anim.begin + 1s" ,表示它的开始时间与圆形动画的开始时间同步,但是在圆形动画的开始时间基础上延迟 1s 。这样,可以实现两个动画的同步效果。
此外,如果需要,还可以使用 end 事件来指定一个动画在另一个动画结束时开始。例如:
<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg" class="element">
<!-- 圆形动画 -->
<circle r="30" cx="50" cy="50" fill="orange">
<animate begin="click" id="circ-anim" attributeName="cx" from="50" to="350" dur="5s" fill="freeze" />
</circle>
<!-- 矩形动画在圆形动画结束时开始 -->
<rect width="50" height="50" x="25" y="200" fill="#0099cc">
<animate begin="circ-anim.end + 0s" id="rect-anim" attributeName="x" from="50" to="350" dur="5s" fill="freeze" />
</rect>
</svg>这允许你在 SVG 动画中非常灵活地控制动画元素之间的时间关系。例如,创建链式动画。
<svg width="400" height="300" viewBox="0 0 400 300" class="element">
<rect x="10" y="10" width="30" height="30" fill="pink">
<animate begin="2s" dur="10s" attributeName="width" from="10" to="380" />
</rect>
</svg>上面的代码,我们创建了一个矩形,然后应用了一个动画,使其在 2s 后开始(begin=2s),持续了 10s ,动画属性是宽度从 10 到 380 的变化。在这个动画中,矩形的宽度将从初始值 10 逐渐增加到目标值 380 。
这是一个最基础的 SVG 动画,但要理解链式动画,我们还需要额外做一些工作,比如为 <rect> 、<animate> 和新的 <rect> 元素添加 id 属性:
<svg width="400" height="300" viewBox="0 0 400 300" class="element">
<rect id="PinkRect" x="10" y="10" width="30" height="30" fill="pink" id="PinkRect">
<animate id="PinkAnim" begin="2s" dur="10s" attributeName="width" from="10" to="380" />
</rect>
<rect id="limeRect" x="10" y="45" width="30" height="30" fill="lime">
<animate begin="PinkAnim.begin + 2s" id="limeAnim" dur="10s" attributeName="width" from="10" to="380" />
</rect>
</svg>上面示例,动画的执行顺序如下:
SVG 文档加载完成后的两秒钟,粉色矩形(PinkRect)由于以下代码而产生动画效果:
<animate id="PinkAnim" begin="2s" dur="10s" attributeName="width" from="10" to="380" />请注意,动画(<animate> 元素)的 id 是是PinkAnim。在下一行中,另一个动画(LimeAnim)与该动画的开始相链接:
<animate begin="PinkAnim.begin + 2s" id="LimeAnim" dur="10s" attributeName="width" from="10" to="380" />上面代码中的 begin="PinkAnim.begin + 2s" 表示 LimeAnim 动画将会在 PinkAnim 动画开始后的 2s 运行。具体效果如下:
类似地,你可以通过使用以下代码添加另一个相对于PinkAnim 动画结束时开始的动画:
<svg width="400" height="300" viewBox="0 0 400 300" class="element">
<rect id="PinkRect" x="10" y="10" width="30" height="30" fill="pink" id="PinkRect">
<animate id="PinkAnim" begin="2s" dur="10s" attributeName="width" from="10" to="380" />
</rect>
<rect id="LimeRect" x="10" y="45" width="30" height="30" fill="lime">
<animate begin="PinkAnim.begin + 2s" id="LimeAnim" dur="10s" attributeName="width" from="10" to="380" />
</rect>
<rect id="YellowRect" x="10" y="80" width="30" height="30" fill="yellow">
<animate begin="PinkAnim.end + 2s" id="YellowAnim" dur="10s" attributeName="width" from="10" to="380" />
</rect>
</svg>如果运行上述代码,你将看到粉色矩形(PinkRect)在文档加载完成后的两秒钟内开始动画。再过两秒钟,绿色矩形(LimeRect)开始动画;粉色矩形的动画完成,再过两秒钟,黄色矩形的动画开始。
如果要让这三个矩形动画按顺序链接执行,那么你可以将第一个动画的结束作为第二个动画的开始,将第二个动画的结束作为第三个动画的开始:
<svg width="400" height="300" viewBox="0 0 400 300" class="element">
<rect id="PinkRect" x="10" y="10" width="30" height="30" fill="pink" id="PinkRect">
<animate id="PinkAnim" begin="2s" dur="10s" attributeName="width" from="10" to="380" />
</rect>
<rect id="LimeRect" x="10" y="45" width="30" height="30" fill="lime">
<animate begin="PinkAnim.end" id="LimeAnim" dur="10s" attributeName="width" from="10" to="380" />
</rect>
<rect id="YellowRect" x="10" y="80" width="30" height="30" fill="yellow">
<animate begin="LimeAnim.end" id="YellowAnim" dur="10s" attributeName="width" from="10" to="380" />
</rect>
</svg>这段代码中,第一个动画(粉色矩形)开始于文档加载完成后的 2s ,第二个动画(绿色矩形)开始于第一个动画的结束,第三个动画(黄色矩形)开始于第二个动画的结束。
以此类推,你可以轻易制作出交错动画。
除了指定动画何时开始,你还可以使用 end 属性指定动画何时结束。例如,你可以将动画设置为无限重复,然后在另一个元素开始动画时停止它。end 属性接受与 begin 值类似的值。你可以指定绝对或相对的时间值、偏移量、重复值、事件值等。
例如,在以下演示中,粉色矩形(PinkRect)的宽度 width 在 5s内缓慢由 50 变成 380 。绿色矩形(LimeRect)也会动画,但只有在点击时才会触发。粉色矩形的动画将在绿色矩形的动画开始时结束。点击绿色矩形查看粉色停止:
<svg width="400" height="110" viewBox="0 0 400 110" class="element">
<!-- 点击绿色矩形时,粉色矩形动画将结束 -->
<rect id="PinkRect" x="10" y="20" width="30" height="30" fill="pink" id="PinkRect">
<animate id="PinkAnim" begin="0s" dur="5s" end="LimeAnim.begin" fill="freeze" attributeName="width" from="10" to="380" />
</rect>
<rect id="LimeRect" x="10" y="60" width="30" height="30" fill="lime">
<animate begin="click" id="LimeAnim" dur="5s" attributeName="width" from="10" to="380" fill="freeze" />
</rect>
</svg>当然,同样的动画同步效果也可以应用于同一元素的两个动画。例如,假设将矩形的颜色设置为无限制地从一种值(lime)变化到另一种值(pink)。然后,当单击元素时,它的宽度会从 30 缓慢变宽到 380。我们现在将其设置为,一旦单击元素并触发动宽度变化动画(WidthAnim),颜色动画(ColorAnim)就会停止。
<svg width="400" height="70" viewBox="0 0 400 70" class="element">
<rect id="PinkRect" x="10" y="20" width="30" height="30" fill="lime" id="PinkRect">
<animate id="ColorAnim" attributeName="fill" begin="0s" dur="5s" from="lime" to="deepPink" repeatCount="indefinite" end="WidthAnim.begin" fill="freeze" from="10" to="380" />
<animate id="WidthAnim" attributeName="width" begin="click" dur="5s" fill="freeze" from="30" to="380" />
</rect>
</svg>到目前为止,你只看到了在“动画元素”(例如 <animate>)的 begin 或 end 属性中使用一个值。然而,在 SVG 中,begin 和 end 属性都接受一个以分号分隔的值列表。begin 属性中的每个值将对应于 end 属性中的一个值,从而形成活动和非常活动的动画间隔。这好比行驶的小汽车,汽车的轮胎在一段时间内容是活动的,然后是非活动的,这取决于小汽车是否在行驶中。
例如:
<svg width="400" height="70" viewBox="0 0 400 70" class="element">
<rect id="PinkRect" x="10" y="20" width="30" height="30" fill="pink" id="PinkRect">
<animate begin="2s; click" end="widthAnim.end; mouseover"
id="widthAnim" attributeName="width" from="30" to="380" dur="5s" fill="freeze" />
</rect>
</svg>详细解释这个例子:
begin="2s; click":这表示动画将在页面加载后 2 秒开始,然后可以通过点击事件触发。这样,动画就有了两个入口点,分别是时间触发和用户点击。end="widthAnim.end; mouseover":这表示动画将在 widthAnim 结束后或鼠标悬停(mouseover)事件来结束动画。这样,动画会在点击后开始,然后在鼠标悬停时停止。这种方式允许动画有多个入口和出口点,提供了更灵活的控制方式。这在创建复杂的动画序列时特别有用。
<svg width="430" height="310" viewBox="0 0 430 310" class="element">
<rect id="rect1" x="10" y="10" width="50" height="50" fill="lime">
<animate id="anim1" begin="0s; anim35.end" dur="0.2s" attributeName="opacity" from="0" to="1" fill="freeze" />
</rect>
<rect id="rect2" x="70" y="10" width="50" height="50" fill="lime" >
<animate id="anim2" begin="anim1.end" dur="0.2s" attributeName="opacity" from="0" to="1" fill="freeze" />
</rect>
<!-- 省略其他 rect 元素 -->
<rect id="rect35" x="370" y="250" width="50" height="50" fill="lime" >
<animate id="anim35" begin="anim34.end" dur="0.2s" attributeName="opacity" from="0" to="1" fill="freeze" />
</rect>
</svg>在上面的示例中,我们为每个 <animate> 元素都定义了一个 id 名称,并且第二个动画的 begin 属性的值是前一个 <animate> 的结束时间,例如 anim1.end 。需要注意的是,第一个 <animate> 元素的 begin 属性的值为 0s; anim35.end 。它包含了一个值列表。第一个值表示矩形在文档加载完后就开始动画。第二个值表示它在最后一个矩形完成动画后开始动画。因此,通过将第一个动画链接到最后一个动画的结束,你就创建了一个循环动画:
正如你所看到的,在任何单独动画的末尾,你都可以启动其他动画。此外,你还可以循环回第一个动画的开始,创建循环动画。当动画像矩形那样简单时,这个过程并不引人注目;然而,如果你将创造力应用于创建更复杂的动画,例如涉及颜色变化或动画渐变,你就可以开始窥视到可用于你的潜在创造力。
你可以为这个过程添加另一个维度。如果动画的开始是由用户事件触发的会怎么样?如果通过鼠标悬停在 SVG 图像的某个部分或点击特定部分,你可以创建一系列链式动画,其中一些可能也循环?你能看到这里的巨大潜力吗?如果你的大脑在可视化、规划和编码所有这些材料的实际困难上感到困扰,不要担心——只需让自己看一看这个潜在的力量。
下面是一个带有简单交互效果的链式动画,你需要将鼠标悬浮在玫红(最顶部的)矩形或黄色(最底部的)矩形上,或者点击粉色矩形(中间的)来触发动画。
<svg width="300" height="150" viewBox="0 0 300 150" class="element">
<g>
<rect id="MaroonRect" x="10" y="15" width="32" height="32" fill="#ff3366">
<animate id="MaroonAnim" begin="PinkAnim.end; mouseover" dur="5s" attributeName="width" from="32" to="250" />
</rect>
<rect id="PinkRect" x="10" y="62" width="32" height="32" fill="pink">
<animate id="PinkAnim" begin="YellowAnim.end; click" dur="5s" attributeName="width" from="32" to="250" />
</rect>
<rect id="YellowRect" x="10" y="107" width="32" height="32" fill="#ffff00">
<animate id="YellowAnim" begin="MaroonAnim.end; mouseover" dur="5s" attributeName="width" from="32" to="250" />
</rect>
</g>
<g>
<text x="50" y="38" fill="red" stroke="red">
<animate begin="MaroonAnim.begin" dur="0.1s" attributeName="visibility" from="visible" to="hidden" fill="freeze" calcMode="discrete" />
<animate begin="MaroonAnim.end" dur="0.1s" attributeName="visibility" from="hidden" to="visible" fill="freeze" calcMode="discrete" />
👈Mouse here
</text>
<text x="50" y="86" fill="red" stroke="red">
<animate begin="PinkAnim.begin" dur="0.1s" attributeName="visibility" from="visible" to="hidden" fill="freeze" calcMode="discrete" />
<animate begin="PinkAnim.end" dur="0.1s" attributeName="visibility" from="hidden" to="visible" fill="freeze" calcMode="discrete" />
👈Click here
</text>
<text x="50" y="130" fill="red" stroke="red">
<animate begin="YellowAnim.begin" dur="0.1s" attributeName="visibility" from="visible" to="hidden" fill="freeze" calcMode="discrete" />
<animate begin="YellowAnim.end" dur="0.1s" attributeName="visibility" from="hidden" to="visible" fill="freeze" calcMode="discrete" />
👈Mouse here
</text>
</g>
</svg>这些动画通过鼠标事件和点击事件相互关联,创造出一个动态的视觉体验。
repeatCount 属性主要用于指定动画重复播放的次数,它与 CSS 的 animation-iteration-count 属性非常相似。你可以设置为一个具体的次数,或者使用关键字 indefinite 表示动画将无限重复。以下是一个示例,演示了如何使用 repeatCount 属性:
<svg width="300" height="300" viewBox="0 0 300 300" class="element">
<circle cx="150" cy="150" r="50" stroke="lime" stroke-width="3" fill="orange">
<animate attributeName="r" from="50" to="10" dur="2s" repeatCount="3" fill="freeze" />
</circle>
</svg>在这个例子中,圆的半径从 50 逐渐减小到 10,动画的持续时间为 2s,而 repeatCount 属性被设置为 3,表示动画将重复播放 3 次。一旦动画完成 3 次,它将保持在最后一帧的状态。
你还可以将 repeatCount 设置为 indefinite,使动画的播放无限重复:
<svg width="300" height="300" viewBox="0 0 300 300" class="element">
<circle cx="150" cy="150" r="50" stroke="lime" stroke-width="3" fill="orange">
<animate repeatCount="indefinite" attributeName="r" from="50" to="10" dur="2s" fill="freeze" begin="click" />
</circle>
</svg> 这意味着动画将持续无限次数,直到停止或文档卸载。
不难发现,动画重新开始时是从初始的 from 值而不是动画的结束时的值 to 。令人遗憾的是,SMIL 中并没有提供类似于 CSS 动画中的 animation-direction 属性的机制。在 CSS 中,可以将 animation-direction 属性的值设置为 alternate ,使奇数次的动画周期迭代以正常方向播放,而偶数次的动画周期迭代以相反方向播放。这意味着,第一个周期将从开始(from)到结束(to)播放,然后第二个周期将从结束(to)到开始(from)播放,然后第三个周期从开始到结束播放,依此类推。
在 CSS 动画中可以很轻易实现的一个效果,但在 SMIL 中却不得不依赖于 JavaScript 脚本动态更改“动画元素”的 from 和 to 属性的值。不过,在某些场景中,可以将结束值指定为中间值,然后将结束值和初始值设置为相同的值。这和 在 CSS 的 @keyframes 的 50% 处设置结束值,然后在 from 和 to 设置相同值是类似的。以 pluse 动画为例:
@keyframes pluse {
from,to {
scale: 1;
}
50% {
scale: 1.3;
}
}要在 SMIL 中实现相似的效果,不得不使用 values 属性,稍后我们会详细介绍。
repeatDur 限制动画的重复时间“限制动画的重复时间” 是一个新的概念,在 CSS 动画以及 WAAPI 中并没有这样的概念。在 SVG 动画中,通过 repeatDur 属性可以限制动画的重复时间,这样可以更好的地控制动画的表现。
试想一下,如果将一个动画设置为无限次重复播放,很可能会惹人烦,甚至对用户也不够友好,尤其是那种持续时间很长的动画。因此,将重复时间限制在一定的时间段内,并在相对于文档开头的某个时间后停止重复,这对于动画自身以及用户来说,都可能会更好一些。这也就是大家所说的“演示时间”。
SVG 动画中的“演示时间”是指相对于文档开始的时间,表示文档片段在时间轴上的位置。演示时间可以用来控制动画的时序和重复行为。你可以通过
begin、end、dur、repeatCount和repeatDur等属性来控制演示时间。演示时间的理解对于精确控制 SVG 动画的行为非常重要。
在 SVG 动画中,可以使用 repeatDur 属性来限制动画的总重复时间,使其在指定的时间段内反复播放。它的语法类似于时钟值,但与另一个动画事件或交互事件相对无关,而是相对于文档的开头。
例如下面这个示例:
<svg width="300" height="300" viewBox="0 0 300 300" class="element">
<circle cx="150" cy="150" r="50" fill="orange">
<animate attributeName="r" values="50;10;50" dur="3s" repeatCount="indefinite" repeatDur="2s" />
</circle>
</svg>在这个例子中,我们有一个圆形,它的半径(r)在动画期间发生变化。动画使用 <animate> 元素,其中 repeatCount 属性被设置为 "indefinite",表示动画将无限次重复。而 repeatDur 属性被设置为 "2s",表示动画将在文档开始后的 2s 内重复,即在第 2s 时,动画将重新开始,继续执行。
我们也可以将上面示例中的 repeatDur 设置为 "01:30" ,表示动画将在文档开始后的 1 分 30 秒内重复。
<svg width="300" height="300" viewBox="0 0 300 300" class="element">
<circle cx="150" cy="150" r="50" fill="orange">
<animate repeatDur="01:30" attributeName="r" values="50;10;50" dur="3s" repeatCount="indefinite" />
</circle>
</svg>这在需要在特定时长内重复运行的场景中很有用,例如制作循环播放的动画或展示。
前面我们一起探讨了如何通过 begin 和 end 属性来同步动画。事实上,在 SMIL 中,除了使用 begin 和 end 属性之外,还可以使用 repeat() 重复次数来同步动画(SVG动画中的同步动画基于重复次数是指一个动画可以根据另一个动画的重复次数来启动)。例如,你可以在另一个动画的第 n 次重复后的某个时间开始一个动画,可以增加或减少一定的时间。
<svg width="400" height="240" viewBox="0 0 400 240" class="element">
<circle cx="60" cy="60" r="30" fill="lime">
<animate id="circleAnim" attributeName="cx" begin="click" dur="5s" repeatCount="3" from="60" to="300" fill="freeze"/>
</circle>
<!-- 圆形动画的第三次重复后,矩形的动画在 2.5s 后开始 -->
<rect x="230" y="140" width="60" height="60" fill="#ff3366">
<animate id="rectAnim" begin="circleAnim.repeat(2)+2.5s" attributeName="x" dur="5s" from="230" to="30" fill="freeze"/>
</rect>
</svg>上面的示例中,将 rectAnim 动画的 begin 设置为 "circleAnim.repeat(2)+2.5s" ,它表示的意思是,circleAnim 动画第三次重复播放的 2.5s 后,rectAnim 动画才开始播放。这就是基于动画重复播放次数来同步动画的效果。
这种方法使得两个动画能够同步,确保第一个动画的重复次数和第二个动画的启动时间是相关联的。这对于创造更加复杂和有机的动画序列非常有帮助。
在 CSS 和 WAAPI 动画中,我们都可以指定动画属性在动画过程中的某一帧中应该采用的值。例如,你正在对元素进行偏移动画处理,你可以在动画过程中指定它采用特定的值,而不是直接从开始值(例如 0)到结束值(例如 300px):
/* CSS 定义关键帧值的方式 */
@keyframes move {
0% {
translate: 0px;
}
50% {
translate: 360px;
}
80% {
translate: 260px;
}
100% {
translate: 300px;
}
}其中 @keyframes 中的 0% 、20% 、80% 和 100% 是动画的关键帧,每个关键帧中的值是每个帧的值
const move = [
{
translate: '0px',
offset: 0
},
{
translate: '360px',
offset: .5
},
{
translate: '260px',
offset: .8
},
{
translate: '300px',
offset: 1
}
]在 WAAPI 中指定每个关键帧的值与 CSS 的 @keyframes 非常相似,不同的是,使用 offset 指定关键帧位置。
在 SMIL 中, 可以以类似的方式控制每帧的值,但语法有些不同。你需要使用 keyTimes 属性来指定关键帧位置,然后使用 values 属性为每帧指定动画属性的值。例如:
<svg width="400" height="240" viewBox="0 0 400 240" class="element">
<circle cx="60" cy="60" r="30" fill="lime">
<animate values="0; 360; 260; 300" keytimes="0; 0.5; 0.8; 1" attributename="cx" from="0" to="300" dur="2s" begin="click" fill="freeze" />
</circle>
</svg>那么,我们在这里做了什么呢?
首先要注意的是,关键帧时间和中间值被指定为列表。keyTimes 属性是一个以分号分隔的时间值列表,用于控制动画的速度。列表中的每个时间对应于 values 属性列表中的一个值,并定义了值在动画函数中的使用时间。keyTimes列表中的每个时间值都指定为 0 ~ 1 之间的浮点值(包括 0 和 1),表示动画元素的简单持续时间内的比例偏移。因此,keyTimes类似于CSS中的百分比,只是你将它们指定为分数,它更类似于 WAAPI 的 offset。
需要知道的是,如果使用值列表,动画将按顺序在动画过程中应用这些值。如果指定了值列表,则会忽略任何 from 、to 和 by 属性值。
在这一点上,值得一提的是,你可以在没有 keyTimes 属性的情况下使用 values 属性,这些值会自动在时间上均匀分布。
在 CSS 中,可以使用 animation-timing-function 或 transition-timing-function 属性指定动画的速率,即缓动函数。它可以是一些预定义的关键词,例如 linear 、ease 、ease-in 、ease-out 和 ease-in-out 等,也可以是一些函数,例如 cubic-bezier() 、linear() 和 steps() 等。
在 SVG 动画中(SMIL 中),可以使用 calcMode 属性来指定动画的节奏(速率)。默认的动画节奏对于所有动画元素都是线性的(linear),除 <animateMotion> 之外。除了线性值之外,你还可以将其值设置为 discrete,paced或spline :
discrete:指定动画函数将从一个值跳到下一个值,没有任何插值。这类似于CSS中的 steps() 函数。paced:类似于线性(linear),但会忽略由 keyTimes 定义的任何中间进度时间。它计算出后续值之间的距离,并相应地分配时间。如果你的值都按线性顺序排列,你将不会注意到差异。但是如果它们来回移动,或者如果它们是颜色(被视为三维矢量值),你肯定会看到中间值。spline :它根据由 keySplines 属性定义的时间函数,从值列表中的一个值插值到下一个值。 spline 的的点在keyTimes 属性中定义,而每个区间的控制点在 keySplines 属性中定义。下面这个示例演示了 calcMode 四种不同模式带来的动画效果差异:
那么,keySplines 属性是做什么的呢?继续借助 CSS 中的相关特性来解释 keySplines 属性。
不知道你是否还有印象,我们在介绍 CSS 的 @keyframes 时,曾经说过,在 CSS 中,你可以在每个关键帧内指定动画缓动函数(即 animation-timing-function),这使你能更好控制动画的每个关键帧应该如何进行。例如:
@keyframes bounce {
0% {
translate: 0 0;
animation-timing-function: ease-in;
}
15% {
translate:0 200px;
animation-timing-function: ease-out;
}
30% {
translate: 70px;
animation-timing-function: ease-in;
}
45% {
translate: 0 200px;
animation-timing-function: ease-out;
}
60% {
translate: 0 120px;
animation-timing-function: ease-in;
}
75% {
translate: 0 200px;
animation-timing-function: ease-out;
}
90% {
translate: 0 170px;
animation-timing-function: ease-in;
}
100% {
translate: 0 200px;
animation-timing-function: ease-out;
}
}注意,CSS 中的预定义的缓动函数的关键词都可以使用相应的三次贝塞尔函数 cubic-bezier() 来描述,例如 :
ease-in = cubic-bezier(0.42, 0, 1, 1)ease-out = cubic-bezier(0, 0, 0.59, 1)现在,可以在 SVG 中像下面这样来描述与 CSS 的 bounce 类似的动画效果:
<svg width="300" height="300" viewBox="0 0 300 300" class="element">
<circle cx="150" cy="60" r="30" fill="lime">
<animate
values="0; 200; 70; 200; 120; 200; 170; 200"
keytimes="0; 0.15; 0.3; 0.45; 0.6; 0.75; 0.9; 1"
attributeName="cy" from="60" to="250" dur="3s" begin="click" fill="freeze" />
</circle>
</svg>动画将在点击时开始,并在达到结束值时冻结。接下来,我们添加 keySplines 属性为每个关键帧指定速率(缓动函数)。
keySplines 属性接受与 keyTimes 列表相关联的一组贝塞尔控制点,定义控制间隔节奏的三次贝塞尔函数。该属性值是以分号分隔的控制点描述列表。每个控制点描述是四个值的集合:x1 y1 x2 y2,描述了一个时间段的贝塞尔控制点。所有值必须在 0 ~ 1 的范围内,并且 calcMode 属性的值要设置为spline,否则该属性将被忽略。
keySplines 不像以值形式接受三次贝塞尔函数,而是接受用于绘制曲线的两个控制点的坐标。
这些坐标点将用于 keySplines 属性,以定义关键帧动画的缓动函数。keySplines 属性的使用与 keyTimes 属性相似,允许使用逗号和可选空格分隔这些值,或仅使用空格分隔这些值。换句话说,定义相关段的 keyTimes 值是贝塞尔“锚点”,而 keySplines 值是控制点。因此,必须有一个控制点的集合比 keyTimes 少一个。
<svg width="300" height="300" viewBox="0 0 300 300" class="element">
<circle cx="150" cy="60" r="30" fill="lime">
<animate
calcMode="spline"
keytimes="0; 0.15; 0.3; 0.45; 0.6; 0.75; 0.9; 1"
values="0; 200; 70; 200; 120; 200; 170; 200"
keySplines="0.42 0 1 1; 0 0 0.59 1; 0.42 0 1 1; 0 0 0.59 1; 0.42 0 1 1; 0 0 0.59 1; 0.42 0 1 1"
attributeName="cy" from="60" to="250" dur="3s" begin="click" fill="freeze" />
</circle>
</svg>如果你只想为整个动画指定一个整体缓动函数而没有任何中间值,你仍然必须使用 keyTimes 属性指定关键帧,但只需指定起始和结束关键帧,即 0; 1,而不指定中间值。
在 CSS 动画中,有一个名为“动画合成”的特性(即 animation-composition 属性),允许同时使用多个 CSS 动画效果来影响同一属性的值。这使得在多个动画效果同时作用于元素时,可以更加灵活地控制动画的表现方式,包括属性值的叠加、替换等。
在 WAAPI 中,可以使用 composite 来实现与 CSS 的 animation-composite 属性相似的效果。
在 SVG 动画中,additive 和 accumulate 属性具备相似的功能。当你定义一个动画,它从前一个动画结束的地方开始;或者使用前一个动画的累积和作为进行下去的值,那么 additive 和 accumulate 就显得非常有用。
例如,下面这个示例,你正在对圆形的 cx 进行动画处理,假设从 0 (from="0") 移动到 360 (to="360")。但是,当你将 additive 属性的值设置为 sum 时,它们的每个值都将相对于动画属性的原始值。
<svg width="400" height="200" viewBox="0 0 400 200" class="element">
<circle cx="50" cy="50" r="30" fill="#ff3366">
<animate additive="sum" attributeName="cx" from="0" to="360" dur="3s" begin="click" fill="freeze" />
</circle>
</svg>上面示例中,圆的 cx 初始位置都是 50 ,并且圆会沿着 x 轴从 0 (起始位置 from="0")移动到 360 (终点位置 to="360")。但是,当动画元素 <animate> 的 additive 设置为 sum 时,0 (from 的值)实际上是原始的 50 ,而 360 (to 的值)实际上是 50 + 360 。换句话说,这实际上与设置了 from="50" 和 to="410" 的效果是相同的:
<!-- 未设置additive -->
<svg width="400" height="200" viewBox="0 0 400 200" class="element">
<circle cx="50" cy="50" r="30" fill="lime">
<animate attributeName="cx" from="50" to="410" dur="3s" begin="click" fill="freeze" />
</circle>
</svg>
<!-- additive="sum" -->
<svg width="400" height="200" viewBox="0 0 400 200" class="element">
<circle cx="50" cy="50" r="30" fill="#ff3366">
<animate additive="sum" attributeName="cx" from="0" to="360" dur="3s" begin="click" fill="freeze" />
</circle>
</svg>正如你所看到的,additive 属性用于定义动画的值是否相对于当前值(原始值)进行累加。在 SVG 动画中,有时我们希望一个动画从前一个动画结束的地方开始,而不是从初始值开始。这时就可以使用 additive 属性。additive 属性有两个可能的值:
replace: 默认值。表示动画的 from 和 to 值将替换当前(原始)值。这可能导致在动画开始之前出现奇怪的跳跃sum: 表示动画的 from 和 to 值将相对于当前值进行累加。也就是说,它们的值将与当前值相加。<!-- additive="sum" -->
<svg width="400" height="100" viewBox="0 0 400 100" class="element">
<circle cx="50" cy="50" r="30" fill="#ff3366">
<animate additive="sum" attributeName="cx" from="0" to="360" dur="3s" begin="orangeAnim.begin; click" fill="freeze" id="redAnim" />
</circle>
</svg>
<!-- additive="replace" -->
<svg width="400" height="100" viewBox="0 0 400 100" class="element">
<circle cx="50" cy="50" r="30" fill="orange">
<animate additive="replace" attributeName="cx" from="0" to="360" dur="3s" begin="redAnim.begin; click" fill="freeze" id="orangeAnim" />
</circle>
</svg>在这个示例中,有两个圆,一个是玫红色的,一个是橙色的。两个圆都有相同的动画,将 cx 属性从 0 移动到 360。但是,橙色的圆使用默认值 additive="replace",而玫红色的圆使用 additive="sum"。不难发现,玫红色的圆的动画是 50 移动到 410 ,它相对于当前值进行了累加。
但是,如果我们希望值相加,使第二次重复从前一个的结束值开始呢?这就是accumulate属性发挥作用的地方。accumulate 属性用于控制动画是否累积。累积意味着在动画重复进行时,每次迭代都会基于前一次迭代的最终值进行累加。这对于创建连续累积的动画效果非常有用。
accumulate 属性有两个可能的值:
none(默认值):表示动画在重复时将从头开始,不进行累积。每次重复都从初始状态重新开始。sum ****:表示动画在重复时将累积。每次迭代都会基于前一次迭代的最终值进行累加。<svg width="400" height="200" viewBox="0 0 400 200" class="element">
<!-- 初始圆 -->
<circle cx="50" cy="60" r="20" fill="blue">
<animate attributeName="cx" from="50" to="150" dur="2s" accumulate="none" begin="click" repeatCount="2" fill="freeze" />
</circle>
<!-- 累积动画 -->
<circle cx="50" cy="140" r="20" fill="red">
<animate attributeName="cx" from="50" to="150" dur="2s" accumulate="sum" repeatCount="2" fill="freeze" begin="click" />
</circle>
</svg>在这个示例中,有两个圆,一个是蓝色的,一个是红色的。两个圆都有相同的动画,将 cx 属性从 50 移动到 150。但是,蓝色的圆使用默认值 accumulate="none",而红色的圆使用 accumulate="sum"。你可以注意到,红色的圆的动画在每次重复时都是基于前一次最终值进行累加的,而蓝色的圆在每次重复时都从初始状态重新开始。这是通过设置 accumulate 属性来实现的。
请注意,如果目标属性值不支持加法,或者如果动画元素不重复,则accumulate属性将被忽略。如果仅使用to属性指定动画函数,它也将被忽略。
就像 repeatDur 属性一样,在 SVG 动画中,你可以使用 min和 max 属性来限制动画的活动持续时间。它们分别用于指定动画活动持续时间的最小值(min)和最大值(max)。它们为我们提供了控制元素活动持续时间的下限和上限的方式。这两个属性都以时钟值作为值。
对于 min 属性,它指定活动持续时间的最小值,以元素活动时间为单位测量。其默认值为 0 ,表示没有对活动持续时间进行限制。如果设置了 min 属性,值必须大于或等于 0 。
对于 max 属性,它指定活动持续时间的最大值,以元素活动时间为单位测量。其默认值为 indefinite ,表示没有对活动持续时间进行限制。如果设置了 max 属性,值必须大于 0 。
如果同时指定了 min 和 max 属性,则 max 值必须大于或等于 min 值。否则,这两个属性都将被忽略。
这里多次提到“元素的活动持续时间”,你可能会感到好奇,“元素的活动持续时间”究竟指的是什么?对吧!
其实,在 SVG 动画中,有三个关键的时间概念,它们分别是“元素的活动持续时间”、“元素的重复持续时间”、以及“元素的简单持续时间”。这些时间概念描述了动画元素在时间轴上的行为。
dur (持续时间)、repeatCount (重复次数)、repeatDur (重复持续时间)和 end 属性都会影响动画元素的活动持续时间。dur (持续时间)和 repeatCount (重复次数)的影响。dur (持续时间)属性的影响。总的来说,这些时间概念之间的差异主要在于它们关注的时间段和受到的影响因素。活动持续时间是整个动画执行的总时间,重复持续时间是单个重复周期的时间,而简单持续时间是没有任何重复的情况下的单次执行时间。这些概念帮助我们更好地理解和控制 SVG 动画的时间轴行为。
那么新问题来了,所有这些是如何在一起工作的呢?哪个会覆盖哪个?然后 end 属性又会覆盖并简单结束动画?
简单地说,浏览器首先根据动画元素的 dur 、repeatCount 、repeatDur 和 end 属性的值计算出元素的活动持续时间。如果动画元素使用了 min 和 max 属性,那么浏览器会将计算得到的活动持续时间与指定的 min 和 max 进行比较。要是元素活动持续时间超出了 min 和 max 的范围,浏览器将会根据下面两种情况进行调整:
max ,则元素的活动持续时间被设定为 max 的值min ,则元素的活动持续时间被设定为 min 的值。如果元素的重复持续时间(或元素简单持续时间)大于 min ,则元素将以(min 受限的)活动持续时间正常播放;否则,元素将以其重复持续时间(或元素简单持续时间)正常播放,然后根据 fill 属性的值冻结或不显式如果元素使用了 end 属性,它会覆盖计算得到的活动持续时间,直接结束动画。
综合起来,各属性的优先级如下:
end 属性具有最高的优先级,它可以直接结束动画。min 和 max 属性会限制活动持续时间的范围,如果超出范围则进行调整。dur、repeatCount、repeatDur 等属性,但会受到 min 和 max 的调整。在规范中有一张非常全面的表格,展示了 dur 、repeatCount 、repeatDur 和 end 属性的不同组合,然后显示了基于每种组合的活动持续时间是什么。
表格来源于:https://www.w3.org/TR/2001/REC-smil-animation-20010904/#ComputingActiveDur
我们来看一个简单示例:
<svg width="400" height="120" viewBox="0 0 400 120" class="element">
<circle cx="50" cy="60" r="30" fill="lime">
<animate attributeName="cx" from="50" to="400" dur="5s" begin="click" repeatCount="indefinite" min="2s" max="4s" fill="freeze" />
</circle>
</svg>在上面的示例中,动画元素 <circle> 的活动持续时间被限制在 2s (min="2s")到 4s (max="4s")之间。这意味着,无论 dur (dur="5s")、repeatCount (repeatCount="indefinite")或其他可能影响活动持续时间的属性设置如何,该动画元素的活动持续时间都将在 2s ~ 4s 之间。
需要注意的是,min 和 max 属性需要与其他控制动画时长的属性(如 dur、repeatCount、repeatDur 和 end)一起使用,以获得所需的效果。浏览器会根据这些属性的值计算出初始的活动持续时间,然后将其与 min 和 max 的设置进行比较,最终确定元素的活动持续时间。
总体而言,这些属性一起决定了动画元素在时间轴上的行为,提供了对动画执行时间的灵活控制。
课程前面向大家呈现的案例大多数是使用 <animate> 元素来创建的。在 SVG 动画中,我将 <animate> 元素视为通用的 SVG 动画元素,因为它可以完成各种任务。当然,对于某些动画来说,则需要应用专门的动画元素,例如 <animateTransform> 、<animateMotion> 和 <set> ,甚至还可以使用已被废弃的 <animateColor> 元素,这些元素提供了额外的控制或便利性。
接下来,我们一起来探讨这些动画元素。先从最通用的 <animate> 元素开始。
<animate> 元素<animate> 元素是 SVG 中用于创建动画的主要元素之一。它允许你在 SVG 图形中应用各种动画效果,例如平移动画、缩放动画、颜色动画、透明度动画等。你可以通过组合不同的属性和使用其他的 SVG 动画元素来创建更复杂和引人注目的动画。
可应用于 <animate> 元素上的属性主要有:
xlink:href:指定要应用动画的目标元素的 ID 或 URL。动画将影响此元素的属性。attributeName:指定要动画的属性的名称。例如,如果你想要更改圆的半径,则设置为 "r"。from:指定动画属性的起始值。to:指定动画属性的结束值。dur:指定动画的持续时间。可以使用表示时间的值,如s(秒)或ms(毫秒)。begin:指定动画何时开始。可以是 "0s" 表示立即开始,也可以是事件触发(例如"click")。repeatCount:指定动画重复的次数。可以设置为整数值或 "indefinite"(无限循环)。fill:指定动画结束后属性值的处理方式。常见的值包括 "freeze"(保持最终值)和 "remove"(恢复到初始状态)。keyTimes:指定关键帧的时间值,用于控制动画属性的插值。keySplines:如果使用 calcMode="spline",则指定贝塞尔曲线的控制点,以定制缓动效果。calcMode:指定插值模式,包括 "linear"、"paced"、"discrete" 和 "spline"。keyPoints:用于指定关键帧的百分比值,仅在 calcMode="paced" 时使用。accumulate:指定是否累积动画效果,允许上一个动画的效果叠加到下一个动画。additive:指定是否相对于原始值叠加动画效果,允许动画从当前值开始。min 和 max:指定动画的最小和最大活动时间,以控制动画的时长。end:指定动画的结束时间。以下是使用 <animate> 元素创建的脉博动画:
<svg width="400" height="400" viewBox="0 0 400 400" class="element">
<circle class="pulse" cx="50%" cy="50%" r="30" stroke="black" stroke-width="4" fill="#ffffff" stroke-opacity="0">
<animate attributeName="stroke-width" from="4" to="0" repeatCount="indefinite" begin="0s" dur="2s" />
<animate attributeName="stroke-opacity" from="1" to="0" repeatCount="indefinite" begin="0s" dur="2s" />
<animate attributeName="r" from="9" to="60" repeatCount="indefinite" begin="0s" dur="2s" />
</circle>
<circle cx="50%" cy="50%" r="30" stroke="black" stroke-width="4" stroke-opacity="1" fill="black"></circle>
</svg>在这个示例中,对 .pluse 圆的 stroke-width 、stroke-opacity 和 r 属性进行动画处理,from 和 to 分别设置了它们的起始值和结束值,dur 设置了动画持续时间为 2s ,begin 设置为 0s ,文档加载完就开始播放,repeatCount 设置为 indefinite ,动画无限循环播放。
我们在使用 <animate> 元素创建 SVG 动画时,有一些重要的事项需要注意。
确保 attributeName 属性指定的属性名称在目标元素上是有效的。同时,需要将 <animate> 嵌套在目标元素内,或者通过 xlink:href 属性选择要应用动画的目标元素。
<animate
xlink:href="#target-element"
attributeName="opacity"
from="0"
to="1"
dur="2s"
begin="click"
fill="freeze"
/>根据属性的类型,确保提供正确类型和格式的值。例如,颜色属性可能需要使用有效的颜色值,而位置属性可能需要数字或百分比。
这一点和 CSS 的动画有点相似,如果你将 animation-duration 或 transition-duration 设置为 0 时,你将看不到任何动画效果。在 SVG 中,也是如此。你需要使用 dur 属性为动画设置一个适当的动画持续时间,以确保动画在适当的时间内完成。过长或过短的持续时间可能导致不理想的动画效果。
通过 begin 属性设置动画的开始时间,可以使用关键字,例如 click 、mouseover 等,或时钟值,例如 2s 。
通过 repeatCount 或 repeatDur 属性来指定动画的重复次数或重复持续时间。
使用 fill 属性定义动画结束后元素的状态。常见的值包括 "freeze"(保持最后一帧)和 "remove"(回到初始状态)。
<animate
xlink:href="#target-element"
attributeName="opacity"
from="0"
to="1"
dur="2s"
begin="click"
fill="freeze" <!-- 或者 fill="remove" -->
/>可以使用 additive 和 accumulate 属性来控制动画效果的附加和累积。
<animate
xlink:href="#moving-rectangle"
attributeName="x"
from="0"
to="300"
dur="2s"
begin="click"
fill="freeze"
additive="sum"
/>使用 min 和 max 属性限制动画的活动持续时间。这可以帮助你确保动画不会过短或过长:
<animate
xlink:href="#scaling-circle"
attributeName="r"
from="20"
to="50"
dur="1s"
begin="click"
fill="freeze"
min="0.5s"
max="2s"
/>通过理解并注意这些事项,你可以更有效地使用 <animate> 元素创建令人印象深刻的 SVG 动画。
<set> 元素<set> 元素提供了一种直接设置属性或属性值在指定时间段内达到特定值的方式。与其他 SMIL 动画和 SVG 动画元素一样,<set> 元素设置表示属性值,保留目标属性在 DOM 中的原始值不变。
在使用 <set> 时,你需要知道:
<set> 元素设置属性的值,对于这些属性,插值值没有意义;例如,visibility 属性只能有 hidden 或 visible 这两个值<set> 元素是非累积的,不允许使用 additive 和 accumulate 属性,如果指定了这些属性,将会被忽略<set> 用于在特定时间内将元素设置为特定值,它不接受前面动画元素中提到的所有属性。例如,它没有 from 或 by 属性,因为在一段时间内发生变化的值并不是渐进性地变化<set> ,你可以指定要定位的元素、属性名称和类型、目标值,并且动画的时序可以通过 begin、dur、end、min、max、restart、repeatCount、repeatDur 和 fill 进行控制。需要知道的是,repeatCount 属性并不会导致动画重复,而只是简单地延长显示动画呈现值的时间例如,你可以使用 <set> 元素来控制简单的滑入滑出的效果:
<svg width="400" height="400" viewBox="0 0 400 400" class="element">
<rect x="100" y="100" rx="10" ry="10" width="200" height="200" fill="#000099">
<set begin="mouseover" end="mouseout" attributeName="class" to="round"/>
</rect>
</svg>.round {
rx: 100px;
ry: 100px;
fill: #CCCCFF;
}当用户的鼠标移动到矩形上时,矩形变成圆形,同时填充颜色会变成一种淡蓝色,并保持在这个视觉效果,直到鼠标移开,结束动画。相对于使用成对的 <animate> 元素,<set> 元素提供了一种更简洁的方式。
SVG 允许使用 <set> 元素使一个元素或一组元素在可见和不可见之间切换。假设你想要在最初的 2s 内保持一些文本的隐藏,然后让它在接来的 6s 内可见。那么你可以先在 <text> 上设置 visibility 属性的值为 hidden ,然后在里面嵌套一个 <set> 元素,该元素将 visibility 属性从 hidden 动画到 visible ,并且设置 begin 的值为 2s (文本在最初的 2s 内不可见),dur 设置为 6s ,表示文本可见 6s 。在此期间后,visibility 属性的原始值(hidden)将恢复,文本也再次隐藏,变得不可见。
<svg width="400" height="60" viewBox="0 0 400 60" class="element">
<text x="40" y="40" fill="#990066" stroke="#990066">
<set attributeName="visibility" attributeType="CSS" to="visible" begin="2s" dur="6s" />
现在你看到我了!🦹♀️
</text>
</svg>text {
visibility: hidden;
}你也可以在 <set> 元素上指定 fill 属性的值为 freeze ,使文本保持在可见状态:
<svg width="400" height="60" viewBox="0 0 400 60" class="element">
<text x="40" y="40" fill="#990066" stroke="#990066">
<set attributeName="visibility" fill="freeze" attributeType="CSS" to="visible" begin="2s" dur="6s" />
现在你看到我了!🦹♀️
</text>
</svg>在 SVG 中,当引用 SVG 填充时,你可能会使用一个 URI ,该 URI 指向定义填充的 <linearGradient> 或其他元素的 id 属性。这个 URI ,就像许多其他 SVG 属性一样,是可以被动画化的。例如下面这个示例:
<svg width="460" height="120" viewBox="0 0 460 120" class="element">
<defs>
<linearGradient id="gradient1" gradientUnits="userSpaceOnUse" x1="10" y1="10" x2="450" y2="100">
<stop offset="10%" stop-color="#ff0066" />
<stop offset="90%" stop-color="#eeffee" />
</linearGradient>
<linearGradient id="gradient2" gradientUnits="userSpaceOnUse" x1="60" y1="60" x2="400" y2="120">
<stop offset="20%" stop-color="#ff3366" />
<stop offset="80%" stop-color="#ffccff" />
</linearGradient>
</defs>
<circle cx="60" cy="60" r="50" fill="url(#gradient1)" id="circle1" />
<circle cx="170" cy="60" r="50" fill="url(#gradient1)" id="circle2" />
<circle cx="280" cy="60" r="50" fill="url(#gradient1)" id="circle3" />
<circle cx="390" cy="60" r="50" fill="url(#gradient1)" id="circle4">
<set attributeName="fill" from="url(#gradient1)" to="url(#gradient2)" begin="3s" dur="5s" repeatCount="3" />
</circle>
</svg>上面的示例中,使用 <linearGradient> 定义了 id 为 gradient1 和 gradient2 两个线性渐变,并且将所有 <circle> 的 fill 属性设置为 url(#gradient1) ,相当于圆填充了一个渐变效果(gradient1)。同时在 circle4 (第四个圆) 中嵌套了 <set> 元素,该元素的 from 属性引用了具有 id 属性 gradient1 的 <linearGradient> 元素,然后将引用的渐变修改为 id 属性 gradient2 的 <linearGradient> 元素。在动画结束时,因为 <set> 元素上没有设置 fill 属性,所以使用的渐变返回到 <circle> 元素的 fill 属性描述的渐变:
<circle cx="390" cy="60" r="50" fill="url(#gradient1)" id="circle4">
<set attributeName="fill" from="url(#gradient1)" to="url(#gradient2)" begin="3s" dur="5s" repeatCount="3" />
</circle><animateMotion> 元素<animateMotion> 元素是我最喜欢的 SMIL 动画元素之一,因为它和 CSS 的路径动画类似,它可以在 SVG 中创建沿着路径运动的动画效果。通过结合路径信息和动画属性,<animateMotion> 元素能够实现各种有趣的效果:
<animateMotion> 可以使图表元素沿着预定的路径移动,以引起用户的注意或强调数据的变化。在使用 <animateMotion> 元素时,你可以通过设置 dur(动画持续时间)、repeatCount(重复次数)、rotate(旋转方向)、path(路径定义)等属性来控制动画的行为。这使得 <animateMotion> 元素成为在 SVG 动画中非常灵活和有创意的工具。
例如下面这个示例:
<svg width="500" height="300" viewBox="0 0 500 300" class="element">
<path d="M0,300 C 100,0 400,0 500,300" fill="none" stroke="#00ff00" stroke-width="2"/>
<circle x="100" y="50" r="20" fill="red">
<animateMotion dur="6s" repeatCount="indefinite" path="M0,300 C 100,0 400,0 500,300" />
</circle>
</svg>上面代码创建了一个路径和一个沿着该路径移动的动画圆圈:
正如你看到的,一个红色的圆圈沿着预定义的曲线路径移动,动画持续 6s 并无限循环。这种效果是通过 <animateMotion> 元素结合路径信息实现的,它使得元素在路径上运动,并且动画属性通过设置 dur、repeatCount 和 path 进行控制。
上面这个是最简单的一个路径动画,你还可以使用相似的方法,创建出更复杂的路径动画,例如下面这个小汽车在赛道上行驶的效果,也是路径动画构建的:
使用 <animateMotion> 元素制作路径动画并不难,它有一个关键要素,即运动路径,因为它是元素沿着路径运动的首要和必要条件。在 SVG 中,你可以通过两种不同的方式为元素指定运动路径:
path 属性指定运动路径<mpath> 元素指定运动路径<animateMotion> 元素在控制动画行为方面与 <animate> 元素相似,除了可以使用 SVG 动画的基本属性之外,它还额外有 keyPoints 、rotate 和 path 三个属性。此外,关于 calcMode 属性有一个不同之处,默认值为 paced ,而不是 linear 。
接下来,继续以一些简单的实例来向大家阐述 <animateMotion> 元素。我们先从指定运动路径开始。
使用 <animateMotion> 元素的 path 属性来指定运动路径,是最常见的方式之一,它的格式和在 <path> 元素上的 d 属性相同。你需要使用路径数据来定义路径的形状。路径数据是由一系列的命令和参数组成的字符串:
M (Move To):将画笔移动到指定的坐标位置,不画线。例如,M10 20 表示将画笔移动到坐标 (10, 20)。L (Line To):从当前位置画一条直线到指定的坐标位置。例如,L30 40 表示从当前位置画一条直线到坐标 (30, 40)。H (Horizontal Line To):画一条水平线到指定的 x 坐标。例如,H50 表示画一条水平线到 x 坐标为 50 的位置。V (Vertical Line To):画一条垂直线到指定的 y 坐标。例如,V60 表示画一条垂直线到 y 坐标为 60 的位置。C (Cubic Bezier Curve):创建三次贝塞尔曲线。例如,C80 90, 100 110, 120 130 表示创建一个三次贝塞尔曲线。Q (Quadratic Bezier Curve):创建二次贝塞尔曲线。例如,Q40 50, 60 70 表示创建一个二次贝塞尔曲线。S (Smooth Curve):创建平滑的贝塞尔曲线,可以继续前面的曲线。例如,S80 90, 100 110 表示创建一个平滑的贝塞尔曲线。A (Elliptical Arc Curve):创建椭圆弧曲线。例如,A15 20 45 0 1 75 85 表示创建一个椭圆弧曲线。Z (Close Path):封闭当前路径,连接当前点和起始点。例如,Z 表示封闭当前路径。这些指令可以组合在一起,以创建复杂的路径形状。路径数据是通过这些指令和相关参数的组合来构建的。有关于这方面更详细的介绍,请移步阅读本文系列的《SVG 路径的高级技术》!
需要知道的是,使用命令来绘制路径是件极其痛苦的事情,甚至还易于出错,个人建议还是通过一些矢量图形绘制工具,或在线工具来获取所需要路径数据。例如,在 Figma 中绘制路径,这里就不向大家展示这个过程了。
下面是一个小汽车沿着指定路径运动的动画效果:
<svg viewBox="0 0 307 184" xmlns="http://www.w3.org/2000/svg" class="element">
<!-- 定义路径形状 -->
<path fill="none" stroke="#facc15" stroke-dasharray="4" d="M167.88,111.3 c3.11,0,6.12,1.44,8.06,3.96l30.58,39.82c1.75,2.28,4.95,2.91,7.43,1.46l64.07-37.37c1.75-1.02,2.84-2.92,2.84-4.95V96.04 c0-1.64-0.71-3.21-1.94-4.29c-1.23-1.09-2.87-1.59-4.5-1.39l-9.83,1.23c-4.66,0.58-8.96-1.96-10.68-6.34 c-1.73-4.37-0.33-9.16,3.48-11.93l0.19-0.14c2.48-1.8,2.71-4.49,2.09-6.41c-0.62-1.91-2.38-3.96-5.45-3.96 c-3.66,0-7.05-1.98-8.84-5.18L229.65,29.6c-1.01-1.8-2.93-2.93-5-2.93H155.2c-1.59,0-3.12,0.67-4.2,1.83l-28.78,31.06 c-1.91,2.06-4.62,3.25-7.43,3.25H31.87c-3.16,0-5.73,2.57-5.73,5.73v32.5c0,2.68,1.83,4.98,4.44,5.58l35.59-1.79 c3.27,0.76,23.73,23.28,26.51,22.67l73.06-15.98C166.45,111.38,167.17,111.3,167.88,111.3z" />
<!-- 在路径上移动的小汽车 -->
<path class="car" fill="#84CC16" style="transform: translate(-12px, -12px)" d="M16.38,18.39c0.22-0.44,0.24-0.8,0.2-1.07l0.19,0.01c0.02,0.21,0.18,0.37,0.4,0.38l2.44,0.06 c0.21,0.01,0.38-0.15,0.41-0.35l1.05,0.03c0.34,0.01,0.66-0.15,0.86-0.42l0.86-1.17c0.18-0.25,0.28-0.55,0.28-0.85V8.99 c0-0.31-0.1-0.6-0.28-0.85l-0.86-1.17c-0.2-0.27-0.52-0.43-0.86-0.42l-1.05,0.03c-0.03-0.2-0.2-0.36-0.41-0.35l-2.44,0.06 c-0.21,0.01-0.38,0.17-0.4,0.38l-0.19,0.01c0.04-0.27,0.02-0.63-0.2-1.07c-0.15-0.3-0.27-0.31-0.35-0.18 c-0.15,0.25-0.16,1,0.01,1.27L7.73,6.93C7.7,6.73,7.53,6.57,7.32,6.57L4.88,6.64C4.66,6.64,4.5,6.82,4.48,7.03L2.58,7.08 c0,0,0,0,0,0l-0.08,0C2.07,7.1,1.7,7.33,1.5,7.69C1.47,7.74,1.45,7.8,1.43,7.85C1.19,8.53,0.93,9.76,0.93,12 c0,2.24,0.26,3.47,0.5,4.15c0.16,0.45,0.59,0.75,1.06,0.77l2.04,0.06c0.01,0.21,0.18,0.38,0.4,0.39l2.44,0.06 c0.21,0.01,0.39-0.15,0.41-0.36l8.26,0.24c-0.17,0.27-0.16,1.02-0.01,1.27C16.11,18.71,16.23,18.7,16.38,18.39z M17.22,12 c0,0-0.02,3.07-0.57,4.6c0,0-1.07-0.27-2.03-1.49c-0.11-0.14-0.17-0.32-0.17-0.5V12V9.38c0-0.18,0.06-0.35,0.17-0.5 c0.96-1.22,2.03-1.49,2.03-1.49C17.2,8.93,17.22,12,17.22,12z M21.66,6.99c0.03-0.03,0.08-0.03,0.11,0.01 c0.19,0.25,0.88,1.16,1.04,1.38c0.18,0.26-0.02,0.74-0.2,0.36c-0.14-0.29-0.7-1.03-0.97-1.38C21.55,7.25,21.56,7.09,21.66,6.99z M15.83,7.18c0,0-0.95,1.2-3.43,1.2c-0.01,0-0.01,0-0.02,0V7.27L15.83,7.18z M6.51,7.42l5.56-0.14v1.1c-2.07,0-1.26,0-3.14,0 C6.97,8.37,6.51,7.42,6.51,7.42z M5.56,12c0-3.28,0.57-3.99,0.57-3.99C6.59,8.37,7.14,8.62,7.5,8.76c0.24,0.1,0.41,0.33,0.41,0.6 V12v2.64c0,0.26-0.16,0.5-0.41,0.6c-0.37,0.14-0.91,0.4-1.37,0.75C6.13,15.99,5.56,15.28,5.56,12z M6.51,16.58 c0,0,0.46-0.95,2.43-0.95c1.88,0,1.07,0,3.14,0v1.1L6.51,16.58z M12.38,16.73v-1.11c0.01,0,0.01,0,0.02,0c2.49,0,3.43,1.2,3.43,1.2 L12.38,16.73z M21.64,16.64c0.27-0.35,0.83-1.1,0.97-1.38c0.18-0.38,0.38,0.1,0.2,0.36c-0.16,0.22-0.85,1.13-1.04,1.38 c-0.03,0.03-0.08,0.04-0.11,0.01C21.56,16.91,21.55,16.75,21.64,16.64z">
<!-- 使用animateMotion指定路径动画 -->
<animateMotion dur="4s" repeatCount="indefinite" rotate="auto" begin="0.8" path="M167.88,111.3 c3.11,0,6.12,1.44,8.06,3.96l30.58,39.82c1.75,2.28,4.95,2.91,7.43,1.46l64.07-37.37c1.75-1.02,2.84-2.92,2.84-4.95V96.04 c0-1.64-0.71-3.21-1.94-4.29c-1.23-1.09-2.87-1.59-4.5-1.39l-9.83,1.23c-4.66,0.58-8.96-1.96-10.68-6.34 c-1.73-4.37-0.33-9.16,3.48-11.93l0.19-0.14c2.48-1.8,2.71-4.49,2.09-6.41c-0.62-1.91-2.38-3.96-5.45-3.96 c-3.66,0-7.05-1.98-8.84-5.18L229.65,29.6c-1.01-1.8-2.93-2.93-5-2.93H155.2c-1.59,0-3.12,0.67-4.2,1.83l-28.78,31.06 c-1.91,2.06-4.62,3.25-7.43,3.25H31.87c-3.16,0-5.73,2.57-5.73,5.73v32.5c0,2.68,1.83,4.98,4.44,5.58l35.59-1.79 c3.27,0.76,23.73,23.28,26.51,22.67l73.06-15.98C166.45,111.38,167.17,111.3,167.88,111.3z" />
</path>
</svg>我们通过图形设计软件,绘制了一个路径,而小汽车 .car (它也是 <path> 绘制出来的)使用 <animateMotion> 元素指定了一个动画,通过 path 属性引用了路径(它和 <path> 元素的 d 值相同)。dur 属性指定了动画的持续时间,repeatCount 属性设置为 "indefinite" 表示动画会无限循环。rotate 属性设置为 auto ,指定元素沿运动路径的方向运动。
通过 path 属性指定运动路径看上去并不复杂,但你需要知道的是,运动路径的动画的效果是将一个被充变换矩阵添加到引用对象的当前变换矩阵上,从而通过计算得到的 x 和 y 值在当前用户坐标系的 x 和 y 轴上进行平移。换句话说,指定的路径是相对于元素当前位置计算的,通过使用路径数据将元素变换到路径位置。
要是对 SVG 坐标系了解不够的话,理解上面这段话是有较大困难的。非常遗憾的是,它也是一个复杂的知识体系,也是 SVG 中非常重要的一个知识。但这部分内容已经超出了本篇的范畴,因此无法在这里对其进行详细阐述,可参阅本系列《坐标与视口》《坐标系变换》两篇。在这里,我尝试着用下面这个案例来解释上面这段话。
以下是让一个圆沿着指定路径运动所需的代码:
<svg width="500" height="350" viewBox="0 0 500 350" class="element">
<!-- 定义圆的运动路径 -->
<path fill="none" stroke="#facc15" stroke-dasharray="4" d="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" />
<!-- 在指定的路径上移动圆 -->
<circle id="circle" r="20" cx="100" cy="100" fill="lime">
<!-- 使用animateMotion指定路径动画 -->
<animateMotion 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" />
</circle>
</svg>你可能已经发现了,圆的中心位置并没有在指定的路径上。我们来看一下路径数据中的坐标。路径开始时通过(M)移动到坐标点 (0,0) ,然后开始绘制曲线(c)到另一点。重要的是要注意,(0,0) 点实际上是圆的位置,它并不是坐标系统左上角的 (0,0) 。这就是前面所提到的,路径属性中的坐标是相对于元素当前位置的!
要改变这种现象,我们需要调整路径的位置或调整元素的位置。就上面的示例而言,可以使用 CSS 的 transform 将 <path> 位置移动到与圆圈相同的坐标位置:
<svg width="500" height="350" viewBox="0 0 500 350" class="element">
<!-- 定义圆的运动路径 -->
<path style="translate:100px 100px" fill="none" stroke="#facc15" stroke-dasharray="4" d="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" />
<!-- 在指定的路径上移动圆 -->
<circle id="circle" r="20" cx="100" cy="100" fill="lime">
<!-- 使用animateMotion指定路径动画 -->
<animateMotion 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" />
</circle>
</svg>或者调整圆圈的坐标位置:
<svg width="500" height="350" viewBox="0 0 500 350" class="element">
<!-- 定义圆的运动路径 -->
<path fill="none" stroke="#facc15" stroke-dasharray="4" d="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" />
<!-- 在指定的路径上移动圆 -->
<circle id="circle" r="20" cx="0" cy="0" fill="lime">
<!-- 使用animateMotion指定路径动画 -->
<animateMotion 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" />
</circle>
</svg>你可能已经发现了,不管使用哪种方式,最终呈现的效果圆圈都是位于指定路径坐标上运动,只是两种方式最终呈现的效果有所差异。这也是路径动画较为头痛之处,这一点在 CSS 路径动画中是一样的。因此,在准备动画的运动路径时,请确保记住这一点。
<mpath> 元素指定运动路径在 SVG 中,<mpath> 是另一种指定运动路径的方法。它使用的不是相对路径属性,而是引用外部路径。<mpath> 元素的 xlink:href 属性会引用外部路径。
<svg width="500" height="300" viewBox="0 0 500 300" class="element">
<path id="motionPath" d="M0,300 C 100,0 400,0 500,300" fill="none" stroke="#facc15" stroke-dasharray="4" />
<circle x="100" y="50" r="20" fill="lime">
<animateMotion dur="6s" repeatCount="indefinite">
<mpath xlink:href="#motionPath" />
</animateMotion>
</circle>
</svg>在上面这个示例中,<path> 元素定义了一个曲线路径,它有一个 id 为 motionPath 的名称,同时在 <circle> 内嵌套了一个 <animateMotion> 元素,并且在 <animateMotion> 元素内部嵌套了一个 <mpath> 元素,该元素通过 xlink:href="#motionPath" 引用了 id 名为 motionPath 的 <path> 元素定义的曲线路径。
这样,圆将沿着 motionPath 路径移动,并且动画将不断重复。你可以根据需要更改路径的形状和<animateMotion>元素的属性来调整动画效果。
正如上面示例所示,运动路径可以在文档的任何地方定义,甚至可以在文档中仅仅定义,而不在 SVG 画布上渲染。在上面的示例中,路径被渲染,因为在大多数情况下,你可能希望显示元素沿其移动的路径。
需要注意的是,W3C 规范有这样的一段描述:
形状的各个
(x, y)点为引用对象的 CTM 提供了一个补充变换矩阵,该矩阵通过计算得到的形状的(x,y)值在当前用户坐标系的x和y轴上进行平移。因此,引用对象会随时间的推移通过运动路径的偏移相对于当前用户坐标系的原点进行平移。该补充变换是应用在由于目标元素的transform属性或目标元素上的animateTransform元素导致的任何变换之上的。
同样,圆的位置由路径数据中的坐标“乘以”或“变换”。例如下面这个示例,我们在 SVG 画布的中央有一条路径,圆位于该路径的起点。然而,当应用运动路径时,圆不会从其当前位置开始运动:
<svg width="500" height="350" viewBox="0 0 500 350" class="element">
<path id="motionPath" fill="none" stroke="#facc15" stroke-dasharray="4" 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" r="20" cx="100" cy="100" fill="lime" />
<animateMotion xlink:href="#circle" dur="1s" begin="click" fill="freeze">
<mpath xlink:href="#motionPath" />
</animateMotion>
</svg>看看圆是如何遵循路径的相同形状,但在不同位置上的?这是因为圆的位置由路径数据的值转换。这个现象和 path 属性指定的路径是相似的。其中一个解决方法是从圆的位置 (0, 0) 开始,以便在应用路径时,它将按预期开始并进行。另一种方法是应用“重置”圆坐标的变换,使它们在应用路径之前计算为零。
以下是上述演示的修改版本,使用了一个闭合路径并无限次重复运动动画。
<svg width="500" height="350" viewBox="0 0 500 350" class="element">
<path id="motionPath" fill="none" stroke="#facc15" stroke-dasharray="4" 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="lime" />
<animateMotion xlink:href="#circle" dur="5s" begin="0s" fill="freeze" repeatCount="indefinite">
<mpath xlink:href="#motionPath" />
</animateMotion>
</svg><animateMotion> 元素的覆盖规则由于在 <animateMotion> 中有多种方式执行相同的操作,因此有必要制定覆盖规则以指定哪些值会覆盖其他值。它的覆盖规则分为两个部分:
<mpath> 元素指定的运动路径将会覆盖 path 属性指定的运动路径;path 属性会覆盖 <animateMotion> 元素的 values 属性,而 values 属性又会覆盖 from 、by 和 tokeyTimes 属性对应的点的定义:keyPoints 属性会覆盖 path,path 会覆盖 values,values 会覆盖 from、by 和 to这些规则帮助你在定义运动路径和关键点时确保正确的覆盖和动画效果。例如,如果你使用 path 属性定义了运动路径,但同时也使用了 <mpath> 元素,那么 <mpath> 中的路径将覆盖 path 中的路径。同样,keyPoints 属性会覆盖 path 中的定义。
不知道你是否发现了,课程前面小汽车在沿着指定路径移动的案例中,我们在 <animateMotion> 元素上设置了 rotate 属性的值为 auto :
<svg viewBox="0 0 307 184" xmlns="http://www.w3.org/2000/svg" class="element">
<!-- 定义路径形状 -->
<path fill="none" stroke="#facc15" stroke-dasharray="4" d="M167.88,111.3 c3.11,0,6.12,1.44,8.06,3.96l30.58,39.82c1.75,2.28,4.95,2.91,7.43,1.46l64.07-37.37c1.75-1.02,2.84-2.92,2.84-4.95V96.04 c0-1.64-0.71-3.21-1.94-4.29c-1.23-1.09-2.87-1.59-4.5-1.39l-9.83,1.23c-4.66,0.58-8.96-1.96-10.68-6.34 c-1.73-4.37-0.33-9.16,3.48-11.93l0.19-0.14c2.48-1.8,2.71-4.49,2.09-6.41c-0.62-1.91-2.38-3.96-5.45-3.96 c-3.66,0-7.05-1.98-8.84-5.18L229.65,29.6c-1.01-1.8-2.93-2.93-5-2.93H155.2c-1.59,0-3.12,0.67-4.2,1.83l-28.78,31.06 c-1.91,2.06-4.62,3.25-7.43,3.25H31.87c-3.16,0-5.73,2.57-5.73,5.73v32.5c0,2.68,1.83,4.98,4.44,5.58l35.59-1.79 c3.27,0.76,23.73,23.28,26.51,22.67l73.06-15.98C166.45,111.38,167.17,111.3,167.88,111.3z" />
<!-- 在路径上移动的小汽车 -->
<path class="car" fill="#84CC16" style="transform: translate(-12px, -12px)" d="M16.38,18.39c0.22-0.44,0.24-0.8,0.2-1.07l0.19,0.01c0.02,0.21,0.18,0.37,0.4,0.38l2.44,0.06 c0.21,0.01,0.38-0.15,0.41-0.35l1.05,0.03c0.34,0.01,0.66-0.15,0.86-0.42l0.86-1.17c0.18-0.25,0.28-0.55,0.28-0.85V8.99 c0-0.31-0.1-0.6-0.28-0.85l-0.86-1.17c-0.2-0.27-0.52-0.43-0.86-0.42l-1.05,0.03c-0.03-0.2-0.2-0.36-0.41-0.35l-2.44,0.06 c-0.21,0.01-0.38,0.17-0.4,0.38l-0.19,0.01c0.04-0.27,0.02-0.63-0.2-1.07c-0.15-0.3-0.27-0.31-0.35-0.18 c-0.15,0.25-0.16,1,0.01,1.27L7.73,6.93C7.7,6.73,7.53,6.57,7.32,6.57L4.88,6.64C4.66,6.64,4.5,6.82,4.48,7.03L2.58,7.08 c0,0,0,0,0,0l-0.08,0C2.07,7.1,1.7,7.33,1.5,7.69C1.47,7.74,1.45,7.8,1.43,7.85C1.19,8.53,0.93,9.76,0.93,12 c0,2.24,0.26,3.47,0.5,4.15c0.16,0.45,0.59,0.75,1.06,0.77l2.04,0.06c0.01,0.21,0.18,0.38,0.4,0.39l2.44,0.06 c0.21,0.01,0.39-0.15,0.41-0.36l8.26,0.24c-0.17,0.27-0.16,1.02-0.01,1.27C16.11,18.71,16.23,18.7,16.38,18.39z M17.22,12 c0,0-0.02,3.07-0.57,4.6c0,0-1.07-0.27-2.03-1.49c-0.11-0.14-0.17-0.32-0.17-0.5V12V9.38c0-0.18,0.06-0.35,0.17-0.5 c0.96-1.22,2.03-1.49,2.03-1.49C17.2,8.93,17.22,12,17.22,12z M21.66,6.99c0.03-0.03,0.08-0.03,0.11,0.01 c0.19,0.25,0.88,1.16,1.04,1.38c0.18,0.26-0.02,0.74-0.2,0.36c-0.14-0.29-0.7-1.03-0.97-1.38C21.55,7.25,21.56,7.09,21.66,6.99z M15.83,7.18c0,0-0.95,1.2-3.43,1.2c-0.01,0-0.01,0-0.02,0V7.27L15.83,7.18z M6.51,7.42l5.56-0.14v1.1c-2.07,0-1.26,0-3.14,0 C6.97,8.37,6.51,7.42,6.51,7.42z M5.56,12c0-3.28,0.57-3.99,0.57-3.99C6.59,8.37,7.14,8.62,7.5,8.76c0.24,0.1,0.41,0.33,0.41,0.6 V12v2.64c0,0.26-0.16,0.5-0.41,0.6c-0.37,0.14-0.91,0.4-1.37,0.75C6.13,15.99,5.56,15.28,5.56,12z M6.51,16.58 c0,0,0.46-0.95,2.43-0.95c1.88,0,1.07,0,3.14,0v1.1L6.51,16.58z M12.38,16.73v-1.11c0.01,0,0.01,0,0.02,0c2.49,0,3.43,1.2,3.43,1.2 L12.38,16.73z M21.64,16.64c0.27-0.35,0.83-1.1,0.97-1.38c0.18-0.38,0.38,0.1,0.2,0.36c-0.16,0.22-0.85,1.13-1.04,1.38 c-0.03,0.03-0.08,0.04-0.11,0.01C21.56,16.91,21.55,16.75,21.64,16.64z">
<!-- 使用animateMotion指定路径动画 -->
<animateMotion dur="4s" repeatCount="indefinite" rotate="auto" begin="0.8" path="M167.88,111.3 c3.11,0,6.12,1.44,8.06,3.96l30.58,39.82c1.75,2.28,4.95,2.91,7.43,1.46l64.07-37.37c1.75-1.02,2.84-2.92,2.84-4.95V96.04 c0-1.64-0.71-3.21-1.94-4.29c-1.23-1.09-2.87-1.59-4.5-1.39l-9.83,1.23c-4.66,0.58-8.96-1.96-10.68-6.34 c-1.73-4.37-0.33-9.16,3.48-11.93l0.19-0.14c2.48-1.8,2.71-4.49,2.09-6.41c-0.62-1.91-2.38-3.96-5.45-3.96 c-3.66,0-7.05-1.98-8.84-5.18L229.65,29.6c-1.01-1.8-2.93-2.93-5-2.93H155.2c-1.59,0-3.12,0.67-4.2,1.83l-28.78,31.06 c-1.91,2.06-4.62,3.25-7.43,3.25H31.87c-3.16,0-5.73,2.57-5.73,5.73v32.5c0,2.68,1.83,4.98,4.44,5.58l35.59-1.79 c3.27,0.76,23.73,23.28,26.51,22.67l73.06-15.98C166.45,111.38,167.17,111.3,167.88,111.3z" />
</path>
</svg>这是因为汽车的方向是固定的,不会随着运动路径而改变。为了改变这一点,将使用 rotate 属性。
也就是说,在 SVG 动画中,rotate 属性可以用来设置元素沿着路径的方向。换句话说,这个属性影响元素在路径上的朝向,确保元素在运动路径上始终面向运动的方向。它和 CSS 路径动画中的 offset-rotate 属性是非常相似的。
rotate 属性可以接受以下三个值:
auto:表示对象会随着运动路径方向的角度(即,方向切线矢量)而随时间旋转。auto-reverse:表示对象会随着运动路径方向的角度(即,方向切线矢量)加上 180deg 而随时间旋转。在 SVG 动画中,keyPoints 属性主要用于控制沿运动路径的动画距离。它允许为 keyTimes 指定的每个值的运动路径上的进度,以便更灵活地控制动画的行为。如果指定了 keyPoints,则 keyTimes 将应用于 keyPoints 中的值,而不是在 values 属性数组中指定的点或 path 属性上的点。
keyPoints 接受一个以分号分隔的浮点数值列表,这些值介于 0 ~ 1 之间。这些值表示在由 keyTimes 属性指定的时刻,对象在运动路径上的移动距离,距离的计算由浏览器的算法确定。列表中的每个进度值对应于 keyTimes 属性列表中的一个值。如果指定了keyPoints列表,它将导致 keyTimes 应用于keyPoints 中的值,而不是 values 属性数组或 path 属性中指定的点。
这里需要注意的一件事是,要使 keyPoints 正常工作,需要将 calcMode 值设置为 linear。如果您的关键点来回移动,看起来似乎应该逻辑上适用于步调动画,但实际上不适用。
以下是一个简单的示例,演示如何使用 keyPoints 属性来控制沿着运动路径的动画距离:
<svg width="300" height="180" viewBox="0 0 300 180" class="element">
<path id="motionPath" fill="none" stroke="#facc15" stroke-dasharray="4" d="M10,80 Q150,10 300,80" />
<circle cx="10" cy="80" r="10" fill="red" style="translate: -10px -80px">
<animateMotion dur="6s" repeatCount="indefinite" keyPoints="0;0.5;1" keyTimes="0;0.5;1" calcMode="linear">
<mpath xlink:href="#motionPath"/>
</animateMotion>
</circle>
</svg>在这个示例中,keyPoints属性设置为 "0;0.5;1",它表示动画在 0%、50% 和 100% 的时刻沿运动路径的进度。通过这种方式,你可以在关键时刻更精确地控制动画的位置。需要注意的是,为了让 keyPoints 正常工作,calcMode 属性应设置为 "linear"。
在 SVG 动画中,沿任意路径移动文本与其他 SVG 元素沿路径移动有所不同。要为文创建沿指定路径运动,你不得不使用 SVG 的 <textPath> 元素,而不是 <animateMotion> 元素。
首先,通过将 <textPath> 元素嵌套在 <text> 元素中,来沿路径定位文本。将要沿路径定位的文本将在 <textPath> 元素内定义,而不是作为 <text> 元素的子元素。即通过 <textPath> 元素,我们使用xlink:href 属性引用了 <path> 元素定义的路径,使文本沿着这条路径移动。
<svg width="500" height="180" viewBox="0 0 500 180" class="element">
<path id="motionPath" fill="none" stroke="#facc15" stroke-dasharray="4" d="M10,120 Q250,10 500,120" />
<text fill="lime">
<textPath xlink:href="#motionPath">Follow me along the path!</textPath>
</text>
</svg>到目前为止,你只能看到文本沿着 #motionPath 路径排列文本,并看不到任何的动画效果:
为了使文本能沿着指定的路径 #motionPath 运动,我们需要使用 <animate> 元素来动画化 startOffset 属性:
<svg width="500" height="180" viewBox="0 0 500 180" class="element">
<path id="motionPath" fill="none" stroke="#facc15" stroke-dasharray="4" d="M10,120 Q250,10 500,120" />
<text fill="lime">
<textPath xlink:href="#motionPath">
<animate attributeName="startOffset" from="0%" to ="100%" begin="0s" dur="10s" repeatCount="indefinite" keyTimes="0;1" calcMode="spline" keySplines="0.1 0.2 .22 1"/>
Follow me along the path!
</textPath>
</text>
</svg>startOffset 表示文本在路径上的偏移。0% 是路径的开始;100% 表示路径的结束。通过动画化 startOffset,将创建文本沿路径移动的效果。
<animateTransform> 元素熟悉 CSS 的同学都知道,在 CSS 中,可以通过 transform 属性以及新的变换特性,例如 translate 、scale 或 roate 对元素进行变换,而且 CSS 变换是创建流畅动画的首先之一。
在 SVG 中,可以使用 <animateTransform> 元素对目标元素进行变换动画,它允许你对元素进行平移、缩放、旋转和倾斜。它可以用来在一定的时间内逐渐改变目标元素的变换属性,创建各种动画效果。它采用与 <animate> 元素相同的属性,还额外提供了一个属性:type。
type 属性用于指定正在进行动画的变换类型。它有五个可能的值:translate、scale、rotate、skewX和skewY。
from、by 和 to 属性采用一个使用给定变换类型的相同语法表达的值:
type="translate" ,每个独立的值表示为 <tx> [,<ty>]。type="scale",每个独立的值表示为 <sx> [,<sy>]。type="rotate",每个独立的值表示为 <rotate-angle> [<cx> <cy>]</cy></cx></rotate-angle>。type="skewX"和type="skewY",每个独立的值表示为 <skew-angle></skew-angle>。相比而言,个人认为 <animateTransform> 是 SVG 动画中最复杂的部分。为什么这么说呢?
对于单个变换动画,使用 <animateTransform> 并不复杂。然而,当包含多个变换时,情况可能变得非常混乱和复杂,特别是因为一个 <animateTransform> 可能会覆盖另一个,所以你可能最终得到的效果不是你预期的效果。如果让我选择的话,我更趋向于选择 CSS 的变换,因为它要比 SVG 的变换简单地多,而且现在 CSS 和 SVG 能完美的配合在一起创建复杂,酷炫和引人注目的动画效果。这将在下一篇中详细介绍。这也意味着,你可能根本不需要在 SVG 中使用 <animateTransform> 来创建动画。
下面是一个使用 <animateTransform> 元素旋转一个矩形的简单示例:
<svg width="500" height="400" viewBox="0 0 500 400" class="element">
<rect width="30" height="30" x="300" y="100" fill="lime">
<animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 50 25" to="360 50 25" dur="3s" repeatCount="indefinite" />
</rect>
</svg>上述代码中,矩形在 3s 秒内从初始状态(旋转角度为 0)逐渐旋转到 360deg,实现了一个旋转动画,并且设置了无限次重复。
再来看一个稍微复杂一点的效果,使用 <animate> 和 <animateTransform> 一起创建一个脉博动画效果:
<svg width="400" height="400" viewBox="0 0 400 400" class="element">
<circle class="pulse" cx="50%" cy="50%" r="30" stroke="black" stroke-width="4" fill="#ffffff" stroke-opacity="0">
<animate attributeName="stroke-width" from="4" to="0" repeatCount="indefinite" begin="0s" dur="2s" />
<animate attributeName="stroke-opacity" from="1" to="0" repeatCount="indefinite" begin="0s" dur="2s" />
<animateTransform attributeName="transform" type="scale" from=".3" to="2" repeatCount="indefinite" begin="0s" dur="2s" />
</circle>
<circle cx="50%" cy="50%" r="30" stroke="black" stroke-width="4" stroke-opacity="1" fill="black"></circle>
</svg>上面的代码中,使用 <animateTransform> 元素实现了一个缩放动画。逐步解释这些属性的含义:
attributeName="transform":指定要进行动画的属性是元素的变换属性。type="scale":指定进行的变换类型是缩放。from=".3":指定缩放动画的起始值,从原始大小的 0.3 倍开始。to="2":指定缩放动画的结束值,缩放到原始大小的 2 倍。repeatCount="indefinite":表示动画无限次重复。begin="0s":指定动画何时开始,这里是从动画开始就立即执行。dur="2s":指定动画的持续时间为 2 秒。注意,需要使用 CSS 的 transform-origin 使圆围绕着圆心进行缩放:
.pulse {
transform-origin: 50% 50%;
}你最终看到的效果如下:
通过前面的学习,我想你已经可以使用 SVG 动画元素创建出自己喜欢的动画效果了。即使是如此,我还是想再花一点时间向大家展示一些 SVG 动画案例。
在 SVG 中,可以使用 <linearGradient> 创建渐变效果,但这里我想向大家演示的是,如何为渐变添加动画效果:
<svg width="500" height="200" viewBox="0 0 500 200" class="element">
<defs>
<linearGradient id="gradient">
<stop offset="5%" stop-color="#FF6600" />
<stop offset="95%" stop-color="#FFFFCC" />
</linearGradient>
</defs>
<rect x="50" y="50" width="400" height="100" fill="url(#gradient)" />
</svg>上面代码,我们使用 <linearGradient> 创建了一个 #ff6600 到 ffffcc 的线性渐变,并且将其当作矩形的背景。
假设你想对构建成渐变右端的淡黄色进行动画。为此,你需要在 <stop> 元素中插入一个 <animate> 元素。例如:
<svg width="500" height="200" viewBox="0 0 500 200" class="element">
<defs>
<linearGradient id="gradient">
<stop offset="5%" stop-color="#FF6600" />
<stop offset="95%" stop-color="#FFFFCC">
<animate attributeName="stop-color" begin="0s" dur="5s" from="#FFFFCC"
to="#000066" repeatCount="indefinite" />
</stop>
</linearGradient>
</defs>
<rect x="50" y="50" width="400" height="100" fill="url(#gradient)" />
</svg>第二个渐变色会从淡黄色 #ffffcc 到深蓝色 #000066 进行动画,整个过程会持续 5s ,而且动画会不断循环播放:
如果你想要同时为渐变的两个颜色添加动画效果,只需要按照上面的方式给另一个 <stop> 元素添加动画效果:
<svg width="500" height="200" viewBox="0 0 500 200" class="element">
<defs>
<linearGradient id="gradient">
<stop offset="5%" stop-color="#FF6600">
<animate attributeName="stop-color" begin="0s" dur="5s" from="#FF6600" to="#FFFFCC" repeatCount="indefinite" />
</stop>
<stop offset="95%" stop-color="#FFFFCC">
<animate attributeName="stop-color" begin="0s" dur="5s" from="#FFFFCC"
to="#FF6600" repeatCount="indefinite" />
</stop>
</linearGradient>
</defs>
<rect x="50" y="50" width="400" height="100" fill="url(#gradient)" />
</svg>你会发现,上面的代码会动画化改变渐变的方向:
如果你并不喜欢上面这种渐变效果,那么你可以考虑使用 SVG 的滤镜,并且动画化滤镜。
<svg width="500" height="400" viewBox="0 0 500 400" class="element">
<defs>
<!-- 给渐变添加动画 -->
<linearGradient id="gradient">
<stop offset="5%" stop-color="#FF6600">
<animate attributeName="stop-color" begin="0s" dur="5s" from="#FF6600" to="#FFFFCC" repeatCount="indefinite" />
</stop>
<stop offset="95%" stop-color="#FFFFCC">
<animate attributeName="stop-color" begin="0s" dur="5s" from="#FFFFCC" to="#FF6600" repeatCount="indefinite" />
</stop>
</linearGradient>
<!-- 给滤镜添加动画 -->
<filter id="Turbulence1" in="SourceImage" filterUnits="objectBoundingBox">
<feTurbulence in="SourceAlpha" type="turbulence" baseFrequency="0.01" numOctaves="1" seed="0">
<animate attributeName="baseFrequency" values="0.01; 0.008; 0.01; 0.012; 0.01" keyTimes="0; 0.25; 0.5; 0.75; 1" begin="0s" dur="5s" repeatCount="indefinite" />
</feTurbulence>
</filter>
</defs>
<rect x="0" y="0" width="500" height="400" fill="url(#gradient)" filter="url(#Turbulence1)" />
</svg>你将看到的效果如下:
继续开动大脑,使用相似的方法,你还可以创建类似于投射在一些字母上的探照灯的效果:
<svg width="600" height="270" viewBox="0 0 600 270" class="element">
<defs>
<filter id="spot" x="-20%" y="-60%" width="150%" height="300%">
<feDiffuseLighting in="SourceGraphic" lighting-color="red" result="lamp" diffuseConstant=".8" surfaceScale="10" resultScale=".2">
<feSpotLight x="300" y="250" z="15" pointsAtX="0" pointsAtY="100" pointsAtZ="0" specularExponent="10">
<animate attributeName="pointsAtX" values="0;100;400;100;0" begin="0s" dur="8s" repeatCount="indefinite" />
</feSpotLight>
<animate attributeName="lighting-color" values="yellow;white;red;white;yellow;" begin="0s" dur="8s" repeatCount="indefinite"/>
</feDiffuseLighting>
<feComposite in="lamp" result="lamp" operator="arithmetic" k2="1" k3="1" />
</filter>
</defs>
<text id="Spotlight" pointer-events="none" fill="white" stroke-width="4" stroke="white" filter="url(#spot)" x="10" y="150">SVG Animation!</text>
</svg>发挥你的创意,你可以利用 SVG 滤镜创建更多引人注目的 SVG 动画效果。
在 SVG 中,我们还可以对 SVG <path> 元素的 d 属性进行动画处理。通过这种方式,你可以轻易地创建一个形状变形的动画效果。
<path> 元素的 d 属性包含定义所绘制形状轮廓的数据。路径数据包括一组命令和坐标,告诉浏览器在最终路径中绘制点、弧线和线段的位置和方式。
通过对该属性进行动画处理,可以对 SVG 路径进行形状变形,创造出形状过渡的效果。但是,为了能够实现形状变形,起始、结束和任何中间路径形状都需要具有相同数量的顶点(点),并且它们需要以相同的顺序出现。如果顶点数量不匹配,动画将无法正常工作。这是因为形状变化实际上是通过移动顶点并插值它们的位置来实现的。也就是说,如果形状缺少或不匹配一个顶点,路径将不再被插值。
例如,我们要创建一个 SVG 形状变形的动画,我们需要超过两个具有相同数量的点(或顶点)的 SVG :
注意,两个形状(即 <path>)不要删除或添加任意节点,否则动画将会出错。基于相同数量和顺序的顶点,你可以获得两个或多个不同效果的形状:
<!-- 形状一 -->
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 288 288">
<linearGradient id="PSgrad_0" x1="70.711%" x2="0%" y1="70.711%" y2="0%">
<stop offset="0%" stop-color="rgb(95,54,152)" stop-opacity="1" />
<stop offset="100%" stop-color="rgb(247,109,138)" stop-opacity="1" />
</linearGradient>
<path fill="url(#PSgrad_0)" d="M37.5,186c-12.1-10.5-11.8-32.3-7.2-46.7c4.8-15,13.1-17.8,30.1-36.7C91,68.8,83.5,56.7,103.4,45 c22.2-13.1,51.1-9.5,69.6-1.6c18.1,7.8,15.7,15.3,43.3,33.2c28.8,18.8,37.2,14.3,46.7,27.9c15.6,22.3,6.4,53.3,4.4,60.2 c-3.3,11.2-7.1,23.9-18.5,32c-16.3,11.5-29.5,0.7-48.6,11c-16.2,8.7-12.6,19.7-28.2,33.2c-22.7,19.7-63.8,25.7-79.9,9.7 c-15.2-15.1,0.3-41.7-16.6-54.9C63,186,49.7,196.7,37.5,186z"/>
</svg>
<!-- 形状二 -->
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 288 288">
<linearGradient id="PSgrad_0" x1="70.711%" x2="0%" y1="70.711%" y2="0%">
<stop offset="0%" stop-color="rgb(95,54,152)" stop-opacity="1" />
<stop offset="100%" stop-color="rgb(247,109,138)" stop-opacity="1" />
</linearGradient>
<path fill="url(#PSgrad_0)" d="M51,171.3c-6.1-17.7-15.3-17.2-20.7-32c-8-21.9,0.7-54.6,20.7-67.1c19.5-12.3,32.8,5.5,67.7-3.4C145.2,62,145,49.9,173,43.4 c12-2.8,41.4-9.6,60.2,6.6c19,16.4,16.7,47.5,16,57.7c-1.7,22.8-10.3,25.5-9.4,46.4c1,22.5,11.2,25.8,9.1,42.6 c-2.2,17.6-16.3,37.5-33.5,40.8c-22,4.1-29.4-22.4-54.9-22.6c-31-0.2-40.8,39-68.3,35.7c-17.3-2-32.2-19.8-37.3-34.8 C48.9,198.6,57.8,191,51,171.3z"/>
</svg>这两个形状大致如下:
如果比较这两个 SVG,你会发现 <path> 元素的 d 属性的值有所差异,但 d 属性中的顶点数量和顺序都是相同的。要是我们把两个 <svg> 合并在一起,并且只有一个 <path> 元素,同时在 <path> 元素内嵌套一个 <animate> 元素。然后对 <path> 元素的 d 属性进行动画处理,并且设置动画持续时间为 5s ,整个动画无限次循环播放。
<svg xmlns="http://www.w3.org/2000/svg" width="288" height="288" viewBox="0 0 288 288" class="element">
<linearGradient id="PSgrad_0" x1="70.711%" x2="0%" y1="70.711%" y2="0%">
<stop offset="0%" stop-color="rgb(95,54,152)" stop-opacity="1" />
<stop offset="100%" stop-color="rgb(247,109,138)" stop-opacity="1" />
</linearGradient>
<path fill="url(#PSgrad_0)">
<animate repeatCount="indefinite" attributeName="d" dur="5s"
values="
M37.5,186c-12.1-10.5-11.8-32.3-7.2-46.7c4.8-15,13.1-17.8,30.1-36.7C91,68.8,83.5,56.7,103.4,45 c22.2-13.1,51.1-9.5,69.6-1.6c18.1,7.8,15.7,15.3,43.3,33.2c28.8,18.8,37.2,14.3,46.7,27.9c15.6,22.3,6.4,53.3,4.4,60.2 c-3.3,11.2-7.1,23.9-18.5,32c-16.3,11.5-29.5,0.7-48.6,11c-16.2,8.7-12.6,19.7-28.2,33.2c-22.7,19.7-63.8,25.7-79.9,9.7 c-15.2-15.1,0.3-41.7-16.6-54.9C63,186,49.7,196.7,37.5,186z;
M51,171.3c-6.1-17.7-15.3-17.2-20.7-32c-8-21.9,0.7-54.6,20.7-67.1c19.5-12.3,32.8,5.5,67.7-3.4C145.2,62,145,49.9,173,43.4 c12-2.8,41.4-9.6,60.2,6.6c19,16.4,16.7,47.5,16,57.7c-1.7,22.8-10.3,25.5-9.4,46.4c1,22.5,11.2,25.8,9.1,42.6 c-2.2,17.6-16.3,37.5-33.5,40.8c-22,4.1-29.4-22.4-54.9-22.6c-31-0.2-40.8,39-68.3,35.7c-17.3-2-32.2-19.8-37.3-34.8 C48.9,198.6,57.8,191,51,171.3z;
M37.5,186c-12.1-10.5-11.8-32.3-7.2-46.7c4.8-15,13.1-17.8,30.1-36.7C91,68.8,83.5,56.7,103.4,45 c22.2-13.1,51.1-9.5,69.6-1.6c18.1,7.8,15.7,15.3,43.3,33.2c28.8,18.8,37.2,14.3,46.7,27.9c15.6,22.3,6.4,53.3,4.4,60.2 c-3.3,11.2-7.1,23.9-18.5,32c-16.3,11.5-29.5,0.7-48.6,11c-16.2,8.7-12.6,19.7-28.2,33.2c-22.7,19.7-63.8,25.7-79.9,9.7 c-15.2-15.1,0.3-41.7-16.6-54.9C63,186,49.7,196.7,37.5,186z" />
</path>
</svg>请注意上面代码中 <aniamte> 元素的 values 属性的值,它是一个以分号分隔的列表值,列表值中的每个值对应的是 d 属性的值。就上面的代码,我们设置定义了三个形状的变化:
上面示例我们所展示的只是三个不同形状之间的变形。你可以根据自己需要,创建更多不同的形状变形。例如 @Felix Hornoiu 写的一个数字倒计时的变形效果:
Demo 地址:https://codepen.io/felixhornoiu/full/JjmVZw (来源于 @Felix Hornoiu)
很多时候,我们在 Web 上应用的 SVG 都是从第三方库或工具中获取的。通常情况下,我们获得的 SVG 都是静态的。例如,我们在 unDraw 平台上获得的插图:
现在,可以使用上面所学到的知识,给插图添加一些动画效果。
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="657.03596" height="776.13897" viewBox="0 0 657.03596 776.13897" xmlns:xlink="http://www.w3.org/1999/xlink">
<path id="line1" data-name="Path 2548" d="M319.381,207.34151a4.98,4.98,0,0,1-3.286-1.233l-42.939-37.481a5,5,0,0,1,6.576-7.533l42.939,37.481a5,5,0,0,1-3.29,8.767Z" transform="translate(-271.48202 -61.93052)" fill="#e4e4e4" fill-opacity="0">
<animate attributeName="fill" from="#e4e4e4" to="#FFEB3B" dur=".5s" begin="0s" fill="freeze" />
<animate attributeName="fill-opacity" from="0" to="1" dur=".5s" begin="0s" fill="freeze" id="anim1" />
</path>
<path id="line2" data-name="Path 2553" d="M451.981,157.43052a5,5,0,0,1-5-5v-85.5a5,5,0,0,1,10,0v85.5A5,5,0,0,1,451.981,157.43052Z" transform="translate(-271.48202 -61.93052)" fill="#e4e4e4" fill-opacity="0">
<animate attributeName="fill" from="#e4e4e4" to="#FFEB3B" dur=".5s" begin="anim1.end" fill="freeze" />
<animate attributeName="fill-opacity" from="0" to="1" dur=".5s" begin="anim1.end" fill="freeze" id="anim2" />
</path>
<path id="line3" data-name="Path 2549" d="M577.45905,207.34151a5,5,0,0,1-3.29-8.767l42.93792-37.484a5,5,0,0,1,6.576,7.533l-42.939,37.481A4.98,4.98,0,0,1,577.45905,207.34151Z" transform="translate(-271.48202 -61.93052)" fill="#e4e4e4" fill-opacity="0">
<animate attributeName="fill" from="#e4e4e4" to="#FFEB3B" dur=".5s" begin="anim2.end" fill="freeze" />
<animate attributeName="fill-opacity" from="0" to="1" dur=".5s" begin="anim2.end" fill="freeze" id="anim3" />
</path>
<path d="M916.98312,822.59834a50.79382,50.79382,0,0,1-13.58984,12.63c-1.12012.71-2.27,1.38-3.43994,2H872.42355c.32959-.66.64991-1.33.96-2a95.35446,95.35446,0,0,0-19.84034-109.34c16.64014,5.14,32.02,15.16,42.08008,29.37006a64.46956,64.46956,0,0,1,10.23,23,96.27571,96.27571,0,0,0-7.66992-48.41c13.50977,10.99,24.03027,26.04,28.04,42.98C930.23312,789.77833,927.32346,808.58833,916.98312,822.59834Z" transform="translate(-271.48202 -61.93052)" fill="#f0f0f0" />
<path id="b35682e1-9f03-403a-b8b0-c3d37d0d1380-2562" data-name="Path 2533" d="M928.136,838.06948H670.398v-2.106h258.12Z" transform="translate(-271.48202 -61.93052)" fill="#3f3d56" />
<path d="M580.23,325.2c0,38.64-13.5,71.63-39.19,96.18-18.23,17.43-46.31,41.86-46.31,67.69v21.37h-85.5V489.07c0-26.1-28.19-50.58-46.31-67.69-25.11-23.69-39.19-58.92-39.19-96.18v-.79a128.25061,128.25061,0,0,1,256.5.79Z" transform="translate(-271.48202 -61.93052)" fill="#6c63ff" />
<path id="ecc2cf6c-8ee8-498c-972d-3c77e28b77e9-2563" data-name="Path 2546" d="M494.729,513.44052a3,3,0,0,1-3-3v-21.374c0-25.125,24.779-48.58,42.873-65.706,1.5-1.422,2.961-2.8,4.363-4.145,25.029-23.928,38.259-56.437,38.259-94.014,0-70.229-55.013-125.243-125.242-125.243h-.354A125.03,125.03,0,0,0,326.739,324.49652v.7c0,36.8,13.939,71.063,38.245,94.007,1.231,1.163,2.51,2.36,3.823,3.589,18.327,17.159,43.427,40.658,43.427,66.275v21.373a3,3,0,0,1-6,0v-21.374c0-23.015-24-45.486-41.528-61.9q-1.9785-1.851-3.841-3.605c-25.5-24.072-40.126-59.924-40.126-98.364v-.72c.2-72.045,58.885-130.523,130.88-130.523h.371A130.557,130.557,0,0,1,583.22407,323.83091q.00357.68526-.00006,1.3706c0,39.255-13.871,73.264-40.112,98.351-1.409,1.347-2.876,2.736-4.386,4.165-17.3,16.377-41,38.806-41,61.349v21.374a3,3,0,0,1-3,3Z" transform="translate(-271.48202 -61.93052)" fill="#3f3d56" />
<path id="b22bef41-3cd7-4c1e-881c-c9a642005cb0-2564" data-name="Path 2547" d="M480.481,551.93552h-57a5,5,0,1,1,0-10h57a5,5,0,0,1,0,10Z" transform="translate(-271.48202 -61.93052)" fill="#6c63ff" />
<path d="M458.10991,585.43538H445.84282a23.44664,23.44664,0,0,1-12.09412-3.35059,2.79354,2.79354,0,0,1-1.26831-3.21435,2.8865,2.8865,0,0,1,2.80164-2.14209h33.38891a2.88664,2.88664,0,0,1,2.80176,2.14209,2.79336,2.79336,0,0,1-1.26856,3.21435A23.44509,23.44509,0,0,1,458.10991,585.43538Z" transform="translate(-271.48202 -61.93052)" fill="#3f3d56" />
<path d="M480.49077,567.72932l-57.33814-.00634-.17761-.38086a4.726,4.726,0,0,1,.89978-4.05811,4.85563,4.85563,0,0,1,3.82288-1.84863h48.55773a4.855,4.855,0,0,1,3.82325,1.84912,4.698,4.698,0,0,1,.91089,4.00732l-.09009.38623Z" transform="translate(-271.48202 -61.93052)" fill="#3f3d56" />
<path id="a74da646-4683-465e-97fe-dd817e7fc328-2567" data-name="Path 2550" d="M494.729,534.19052h-85.5a5,5,0,0,1,0-10h85.5a5,5,0,0,1,0,10Z" transform="translate(-271.48202 -61.93052)" fill="#6c63ff" />
<path d="M449.88,403.57,432.1,510.44H421.95l18.06-108.51a5.00266,5.00266,0,0,1,9.87,1.64Z" transform="translate(-271.48202 -61.93052)" fill="#e6e6e6" >
<animate attributeName="fill" from="#e4e4e4" to="red" dur="1s" begin="0s" fill="freeze" repeatCount="indefinite" />
</path>
<path d="M484.05,510.44H473.9L456.12,403.57a5.00266,5.00266,0,0,1,9.87-1.64Z" transform="translate(-271.48202 -61.93052)" fill="#e6e6e6" >
<animate attributeName="fill" from="#e4e4e4" to="red" dur="1s" begin="0s" fill="freeze" repeatCount="indefinite" />
</path>
<path id="line4" data-name="Path 2551" d="M419.48452,400a5,5,0,0,1,5-5h54.993a5,5,0,0,1,0,10h-54.993A5,5,0,0,1,419.48452,400Z" transform="translate(-271.48202 -61.93052)" fill="#e6e6e6" >
<animate attributeName="fill" from="#e4e4e4" to="red" dur="1s" begin="0s" fill="freeze" repeatCount="indefinite" />
</path>
<path id="eba1f826-879a-4c25-b118-6bb025be95d0-2570" data-name="Path 2559" d="M860.701,824.07953H848.443l-5.832-47.288h18.092Z" transform="translate(-271.48202 -61.93052)" fill="#feb8b8" />
<path id="f6694a1b-3521-46e6-aa9c-51f4297aac52-2571" data-name="Path 2560" d="M863.82805,835.96354H824.297v-.5a15.387,15.387,0,0,1,15.385-15.386h24.146Z" transform="translate(-271.48202 -61.93052)" fill="#2f2e41" />
<path id="fae71c2d-33fb-45f7-8331-19b4de5eddc8-2572" data-name="Path 2561" d="M767.564,823.67553l-11.844-3.167,6.58-47.19,17.48,4.674Z" transform="translate(-271.48202 -61.93052)" fill="#feb8b8" />
<path id="b3b10fd6-5fa4-49f3-8270-96ad6a3a1e0e-2573" data-name="Path 2562" d="M767.515,835.96354l-38.189-10.212.129-.483a15.387,15.387,0,0,1,18.839-10.89h0l23.325,6.237Z" transform="translate(-271.48202 -61.93052)" fill="#2f2e41" />
<path id="f6267da0-acb1-421a-9dac-5a6151c1f955-2574" data-name="Path 2563" d="M867.754,559.06753l10.269,11.931-13.28,242.442h-28.369l-14.3-186.551-44.349,191.554-29.492-7.233,26.816-243.3Z" transform="translate(-271.48202 -61.93052)" fill="#2f2e41" />
<path id="ea0dabd6-18d7-4484-8048-4cddc34702bd-2575" data-name="Path 2564" d="M781.905,402.54252l28.246-14.172,43.437.764,37.38,19.127L869.7,514.4795l9.187,55.38h0a226.53255,226.53255,0,0,1-108.335.892l-.284-.068s21.114-74.916,12.126-97.779Z" transform="translate(-271.48202 -61.93052)" fill="#e6e6e6" />
<path id="a75b604a-8411-40e9-b5d3-81fba8b6e00c-2576" data-name="Path 2565" d="M863.621,345.93452a31.994,31.994,0,1,0,0,.237Z" transform="translate(-271.48202 -61.93052)" fill="#feb8b8" />
<path id="b97e8772-edf1-4d51-bb40-aaab108bf113-2577" data-name="Path 2567" d="M778.007,479.9835a11.462,11.462,0,0,0,16.65,5.627l57.353,30.318,1.857-13.971-55.73-32.863a11.524,11.524,0,0,0-20.131,10.889Z" transform="translate(-271.48202 -61.93052)" fill="#feb8b8" />
<path id="b7638692-4395-4c34-bc33-e4b41c888504-2578" data-name="Path 2568" d="M854.436,482.39851a11.462,11.462,0,0,1-17.478,1.848l-62.6,17.035.545-17.738,62.27106-16.173a11.524,11.524,0,0,1,17.261,15.03Z" transform="translate(-271.48202 -61.93052)" fill="#feb8b8" />
<path id="fb7d8996-2760-43c1-a9a4-d402d6d3756c-2579" data-name="Path 2569" d="M878.762,409.0305l12.205-.765s14.29,18.855,6.364,39.316c0,0,1.373,73.5-30.276,70.48s-41.65-3.019-41.65-3.019l9.5-26.5,21.253-6.562s-6.55-28.894,5.849-40.916Z" transform="translate(-271.48202 -61.93052)" fill="#e6e6e6" />
<path id="a9eb06d8-8cbf-43ab-991d-4f9eccd60edd-2580" data-name="Path 2570" d="M790.63005,408.38551l-1.725-8.843s-25.44-.6-30.47,37.951c0,0-22.877,57.692-.454,65.121s47.089,0,47.089,0l-1.85711-25.444-24.67-5.033s12.745-16.489,5.805-30.792Z" transform="translate(-271.48202 -61.93052)" fill="#e6e6e6" />
<path id="ba7ab43e-494b-455d-a87f-0e0f24cb0a47-2581" data-name="Path 2387" d="M842.70445,361.6451c-2.35273-.92321-4.95894-.87527-7.44567-1.33105-8.89342-1.63849-15.75239-10.70648-14.90781-19.70962a10.84778,10.84778,0,0,0,.05763-3.5329c-.61621-2.569-3.47169-3.94789-6.09484-4.25558s-5.33331.086-7.885-.59694c-3.93954-1.05505-6.883-4.64835-7.88062-8.603a19.487,19.487,0,0,1,1.267-11.96329l1.22416,2.55209a9.91759,9.91759,0,0,0,2.5062-4.85275,6.25462,6.25462,0,0,1,5.16622,3.27253c1.58106.70614,1.388-2.90614,2.96395-3.621a2.95887,2.95887,0,0,1,2.10019.27339c3.47269,1.16515,7.156-.583,10.59739-1.83576a40.41732,40.41732,0,0,1,18.64164-2.13018c4.18269.51056,8.40506,1.74686,11.66025,4.41951a26.60667,26.60667,0,0,1,6.156,8.27393c4.017,7.50846,7.04316,15.79159,6.96075,24.30956a35.29808,35.29808,0,0,1-6.1645,19.5192c-2.052,2.98581-8.67978,13.34292-13.08484,12.20287C843.00869,372.607,849.32934,364.24347,842.70445,361.6451Z" transform="translate(-271.48202 -61.93052)" fill="#2f2e41" />
</svg>这种思路创建的动画效果非常适用于一些插图的页面,比如 404 页面:
SMIL 有很大的潜力,本篇只是触及了其表面,只涉及了它们在 SVG 中的基础和技术性方面。但这些知识足够你创建出许多令人印象深刻的动画效果,尤其是涉及到形状变形和滤镜的效果。技术是有限的,创意是无限的,最终的效果不是局限于技术,而是创意。
虽然花了很长的篇幅与大家一起探讨了 SMIL 动画,但我还是想在课程的末尾强调一下。
SMIL 是 SVG 动画的规范,因为它为高性能的 SVG 动画渲染提供了许多强大的功能。不幸运的是,对于 Webkit 来说,对 SMIL 的支持逐渐减弱,并且 Microsoft 的 IE 或 Edge 浏览器从未支持过 SMIL,而且可能永远不会支持。不用担心,我们有解决方案,可以使用 CSS 和 WAAPI 找到相应的替代方案,而且能更好的实现相同的动画效果。
简而言之,你可以花时间去了解和学习 SMIL,但我并不希望你深入去了解,因为它很有可能会像 Flash 技术一样,成为历史,而且当下有很多更好的技术可以替代它。在下一篇中,我们将一起探讨带有 CSS 的 SVG 动画。随着进一步的学习,你会发现,本篇中提到的动画效果,其实使用 CSS 或 WAAPI 可能更好的实现,甚至能更好的控制。