上一篇文章我们使用 svg 绘制好了静态的图形,本篇文章将通过给静态图形添加动画效果,以实现如下所示的 MG 动画:
svg 实现动画的方式不止一种,我将会通过多种方式给不同的元素添加动画效果以展示它们的用法。
使用 css
从 SVG2 开始,原本在元素上使用的形变属性 transform 可以通过 css 来设置了,不过要注意用法有些许差别,比如以 css 的形式设置时值需要添加单位。顺便说一句,一旦某个元素应用了形变,该元素内部就会建立一个新的坐标系统,其后续的变化会基于新创建的坐标系。(关于 css 中 transform 与坐标系的更多细节,可浏览《css3 实现一个旋转的掘金方块》)
十字
给定义了十字形的 <g> 元素一个 id,然后在 <style> 中定义动画,通过 transform-origin 将形变的中心点从默认的用户坐标系左上角移动到中心点,然后添加帧动画 rotate-ani。给 rotate-ani 设置动画时间为 2s,同时在 25% 时就逆时针旋转 45°,保持到 100%,这样就有了旋转后停顿的效果,forwards 则是让动画停止时保持最后一帧的状态,因为动画设置了 infinite 是循环的,所以此处其实可以不设置 forwards :
<!-- 代码片段 1.1 -->
<style>
#cross {
transform-origin: 400px 300px;
animation: rotate-ani 2s ease-out forwards infinite;
}
/* 逆时针旋转 45° */
@keyframes rotate-ani {
25%,
100% {
transform: rotate(-45deg);
}
}
</style>
<svg width="100%" height="600" viewBox="0 0 800 600">
<!-- 十字 -->
<g id="cross" stroke-width="8" stroke="#FAB748">
<!-- 省略 -->
</g>
</svg>
中心圆
将之前绘制好的位于图形中心的 4 个半圆组成一组,包裹在 <g id="mid-circle"> 内,统一添加样式,同样是先将形变中心移到用户坐标系的中心点,然后通过 transform: scale(0) rotate(90deg); 让中心圆先缩小到看不见,并呈现顺时针旋转 90° 的状态,然后在帧动画中将 25% 时的状态设置成动画开始时状态,以保证等待十字形的旋转结束后再开始中心圆动画,结束状态为 scale(1) 以及默认的 rotate(0),即恢复原来的大小和旋转角度:
<!-- 代码片段 1.2 -->
<style>
#mid-circle {
transform-origin: 400px 300px;
transform: scale(0) rotate(90deg);
animation: rotation-occurs 2s ease-out infinite;
}
/* 旋转变大出现 */
@keyframes rotation-occurs {
25% {
transform: scale(0) rotate(90deg);
}
50%,
100% {
transform: scale(1);
}
}
</style>
<!-- 中间的圆 -->
<g id="#mid-circle">
<!-- 紫色大半圆 -->
<!-- 蓝色大半圆 -->
<!-- 紫色小半圆 -->
<!-- 蓝色小半圆 -->
</g>
目前实现的动画效果如下:
左右两端大三角形
以左上角黄色三角为例,剩余 3 个三角形的动画以此类推。通过 transform-origin 将形变的起始点移动到三角形的顶点,然后在动画的前 25% 的范围内保持隐藏,25% ~ 50% 时进行伸展动画,之后保持动画结束时的状态:
<!-- 代码片段 1.3 -->
<style>
#left-top-triangle {
transform-origin: 316px 216px;
transform: scale(0);
animation: extend 2s ease-out infinite;
}
/* 伸展出现 */
@keyframes extend {
25% {
transform: scale(0);
}
50%,
100% {
transform: scale(1);
}
}
</style>
<path id="left-top-triangle" d="M 316 300 l 0 -84 l -84 84 Z"></path>
上下两端小正方形
以左上角蓝色正方形为例,添加一个从正方形左下角开始的放大出现效果,extend 动画在代码片段 1.3 中已定义,hide-to-appear 只是设置了一个透明度的变化:
<!-- 代码片段 1.4 -->
<style>
#left-top-square {
opacity: 0;
/* x = 400 - 42; y = 300 - 84 */
transform-origin: 358px 216px;
animation: extend 2s ease-out infinite,
hide-to-appear 2s ease-out infinite;
}
/* 隐藏到出现 */
@keyframes hide-to-appear {
25% {
opacity: 0;
}
50%,
100% {
opacity: 1;
}
}
</style>
<!-- 左上角蓝色正方形 -->
<use id="left-top-square" x="358" y="174" href="#blueRect"></use>
给其它 3 个小正方形添加动画的方法类似,无非是调整下形变的原点坐标,不再赘述。
剪切小三角形
同样以左上角的橘色小三角形为例,添加的动画效果是从左上方渐显移入:
<!-- 代码片段 1.5 -->
<style>
#left-top-s-triangle {
/* x = 400 - 84 - 42; y = 300 - 84 - 42 */
opacity: 0;
transform: translate(-42px, -42px);
animation: hide-to-appear 2s ease-out infinite,
move-to-right-bottom 2s ease-out infinite;
}
@keyframes move-to-right-bottom {
25% {
transform: translate(-42px, -42px);
}
50%,
100% {
transform: translate(0, 0);
}
}
</style>
<!-- 左上角剪切三角 -->
<g
id="left-top-s-triangle"
clip-path="url(#cut-off-left-top)"
fill="#FAB748"
>
<!-- 省略 -->
</g>
translate(x, y) 如果只设置 x,则 y 默认为 0。
至此,动画效果如下:
使用 svg 内置的动画元素
四周小圆
现在介绍如何使用 svg 内置的动画元素来实现位于四周的小圆动画。svg 的内置动画元素除了包括本次案例中会使用的 <animate>、<animatetransform> 和 <animateMotion>,还有个 <set>,它们都是基于 SMIL(Synchronized Multimedia Integration Language,同步多媒体集成语言) 实现的。SMIL 是 W3C 推荐的可扩展标记语言,用于描述多媒体演示,由 XML 编写。使用动画元素时,直接在需要添加动画效果的元素内使用即可:
animate
左上角的小圆我使用 <animate> 来实现动画:
<!-- 代码片段 2.1 -->
<!-- 左上小圆 -->
<use x="232" y="132" href="#blueCircle">
<animate
attributeName="x"
values="316; 232; 232"
keyTimes="0; 0.2; 1"
dur="2s"
repeatCount="indefinite"
fill="freeze"
></animate>
<animate
attributeName="y"
values="216; 132; 132"
keyTimes="0; 0.2; 1"
dur="2s"
repeatCount="indefinite"
fill="freeze"
></animate>
</use>
介绍下用到的属性:
attributeName:指定要让哪个属性发生变动从而产生动画;values:动画期间被修改的属性的值,通过;分割。如果只有初始和最终值,可以用from/to属性替代;keyTimes:用于控制动画的快慢,它的值与values的值一一对应,并且为 [0, 1] 之间的浮点数。比如在values中,定义了 2 个232,与之对应的keyTimes中的值为0.2和1,代表动画在 2s * 0.2 即 0.4s 内就完成了,剩下的 1.6s(2s * 0.8)内x都维持232数值不变;dur:动画的持续时间;repeatCount:动画的重复次数,为indefinite则代表无限重复;fill:定义动画的最终状态,为freeze则表示保持最后一帧的状态,如果是remove则代表回到第一帧。
另外,还有个没有用到但比较有用的 begin 属性,可以控制动画的开始条件,值可以是时间,比如 1s,或是某个事件,比如 cross.click,代表点击了 id 为 cross 元素后触发。
animateTransform
小圆的移动除了可以通过改变属性 x、y 实现,也可以通过像代码片段 1.5 中的剪切三角形动画那样,通过 transform 的 translate 实现,只不过这里我选择使用专门用来定义平移、旋转、缩放等形变动画的元素 <animateTransform>,相比 <animate>,它多了个 type 属性用以指定是那种形变,并且需注意一个元素内只能添加一次 <animateTransform>,如果定义多个,后定义会覆盖之前的定义 :
<!-- 代码片段 2.2 -->
<!-- 左下小圆 -->
<use x="232" y="468" href="#blueCircle">
<animateTransform
attributeName="transform"
type="translate"
values="84, -84; 0, 0; 0, 0"
keyTimes="0; 0.2; 1"
dur="2s"
repeatCount="indefinite"
></animateTransform>
</use>
可以对比下代码片段 1.5,看看使用 css 和 svg 内置动画元素在的区别。
掘金 logo
以左边的 logo 为例,介绍如何使用 <animateMotion> 添加沿着指定路径移动的动画。注意,添加动画的元素的坐标原点,会影响到运动路径,所以我将 <image> 的 x、y 的值均设置为了 0:
<!-- 代码片段 2.3 -->
<!-- 左边 logo -->
<image x="0" y="0" href="../imgs/juejin.png" width="40">
<animateMotion
path="M 0 400, 50 340, 100 360, 150 300, 200 350, 230 300, 214 286"
dur="2s"
keyPoints="0; 0.5; 1; 1"
keyTimes="0; 0.6; 0.8; 1"
repeatCount="indefinite"
></animateMotion>
</image>
path指定运动的路径,其值和<path>中的d属性是一样的。如果想查看路径的轨迹,可以专门定义一个<path>:
<!-- 代码片段 2.3.1 -->
<path
d="M 0 400, 50 340, 100 360, 150 300, 200 350, 230 300, 214 286"
fill="none"
stroke="red"
></path>
下图中的红色折线即为动画的路径:
如果路径已经在其它地方定义好了,则也可以通过 <mpath> 直接引用:
<!-- 代码片段 2.3.2 -->
<path
id="logo-path"
d="M 0 400, 50 340, 100 360, 150 300, 200 350, 230 300, 214 286"
fill="none"
stroke="red"
></path>
<image x="0" y="0" href="../imgs/juejin.png" width="40">
<animateMotion
dur="2s"
keyPoints="0; 0.5; 1; 1"
keyTimes="0; 0.6; 0.8; 1"
repeatCount="indefinite"
>
<mpath href="#logo-path"></mpath>
</animateMotion>
</image>
keyPoints配合前面介绍过的keyTimes可以对运动的快慢进行管理,它的值也是一个列表,列表中的值处于 0 到 1 之间,以;间隔。比如keyPoints设置的0.5对应keyTimes的0.6,意为完成路径的前半段动画所用的时间占全程(2s)的 60%,比较慢;之后keyPoints设置了两个1,对应的keyTimes为0.8和1,意味着在 1.6s(2s * 0.8) 时,logo 就来到了路径终点,然后停留 0.4s(2s * 0.2)。
动画和添加动画的元素也可以分开定义,通过 href 建立关联:
<!-- 代码片段 2.3.3 -->
<image id="logo" x="0" y="0" href="../imgs/juejin.png" width="40"></image>
<animateMotion
href="#logo"
path="M 0 400, 50 340, 100 360, 150 300, 200 350, 230 300, 214 286"
dur="2s"
keyPoints="0; 0.5; 1; 1"
keyTimes="0; 0.6; 0.8; 1"
repeatCount="indefinite"
></animateMotion>
至此,动画效果如下:
使用 js
最后,我使用 js 库 snap.svg 给右下角的签名添加上动画,需要先引入库文件(snap.svg 也可用于创建 svg):
<!-- 代码片段 3 -->
<svg id="ma-canvas" width="100%" height="600" viewBox="0 0 800 600">
<text id="signature" x="800" y="580" font-size="12" fill="#26AAD6">
<!-- 省略 -->
</text>
</svg>
<script src="../js/snap.svg-min.js"></script>
<script>
window.onload = function () {
const svg = Snap('#ma-canvas')
const signature = svg.select('#signature')
Snap.animate(
[1000, 0],
[800, 1],
val => {
signature.attr({
x: val[0],
opacity: val[1]
})
},
2000,
mina.easeout,
() => {
console.log('动画结束')
}
)
}
</script>
给 <svg> 和需要添加动画的 <text> 增加 id 属性后,就可以先通过 Snap() 获取到 svg,再通过 svg.select() 获取到签名。之后使用 Snap.animate() 这个 api,就可以添加让签名从右向左平移出现的动画了。
介绍下 Snap.animate() 的参数:
- 前 2 个参数可以直接是个数字,或是如上所示的数组,分别代表要改变的属性的初始值和最终值;
- 第 3 个参数是个回调函数 setter,
Snap.animate的底层实现依靠的是之前在制作 canvas 动画时介绍过的requestAnimationFrame,所以每秒钟会调用 60 次 setter,每次调用会传入参数val,是在对应时刻的第 1 个参数到第 2 个参数的过渡值,我们在 setter 中通过设置属性的方法将val的值赋给签名,就可以得到动画了; - 第 4 个参数为动画持续时间,单位为 ms;
- 第 5 个参数为可选参数,是动画曲线,其值可参见官网关于 mina 的内容;
- 第 6 个参数为可选参数,为动画结束时执行的回调。