anime.js 实战:使用 SVG 实现一个 Twitter 赞的动画效果

2,240 阅读7分钟
原文链接: svgtrick.com

之前翻译过一篇使用anime.js 实战:使用 SVG 实现一个带有描边动画效果的复选框初步的讲了anime.js做动画的一个入门知识,这次来讲讲anime.js做动画进阶教程,使用它来实现一个更复杂的动画效果。

在这篇文章中,我们来一步一步实现一个更高级更复杂的动画效果,主要是实现这个动画效果 Twitter-Fav by Brian W from Dribble,为了实现这个效果,我们可以使用录屏工具把这个动画效果录下来,然后再根据录屏来分析动画效果的每一个步骤都是怎么来动的,这样有利于后面编写动画效果。先来看看最终要实现的动画效果:

使用录屏工具来分析动画是一个非常好的方法,它能让我们详细的观察动画效果的每一部分。对实现动画也有很大的帮助。如果想更详细的看清动画的每一个细节,可以使用能够精确到毫秒级的录屏工具来录制动画,这样可以看清楚每一个细节。

要实现这个动画效果,要综合使用html、CSS、SVG和JavaScript这几种技术。当然,CSS和SVG之间有一些重叠的部分,有些效果可以使用它们之中的任意一个来实现。根据以往的开发经验,一般简单的形状可以使用CSS来实现,因为CSS的语法较之SVG确实更简单些。在另外一些稍微复杂点的场景下,使用SVG更适合些,比如贝塞尔曲线等。具体使用哪种技术,还是要看你面对的具体的业务场景了。对于我来说,有时候单独使用它们,又时候是一起来使用。

当要实现一个动画效果的时候,通常把它分解成一个一个小动画是一个比较好的分析方法,这样可以使动画效果实现起来更加简单容易。实际上这篇文章也是按照这样的思路来写的。对于我们要实现的这个动画效果,我认为可以把它拆解成下面这几个部分:

1、缩小灰色的星星

2、放大光环

3、喷溅效果

4、最后激活放大黄色的星星

要实现上面几个动画效果,需要用到下面这些元素和方法:

1、贝塞尔曲线

2、圆圈

3、圆圈

4、使用贝塞尔曲线重新绘制一个黄色的星星

在上面说的四个步骤中,只有星星这个形状复杂一点。这个时候矢量设计软件就该出场了,比如adobe illustrator等设计工具,可以设计好形状直接导出SVG。

设计好后,可以直接导出SVG代码。从导出的代码看出这个星星是由贝塞尔曲线绘制而成的。

<path class=”favorite__inactive”
      d=”M50.214 10.067c6.4.204 10.753 25.648 10.753 25.648s26.256–1.803 27.13 2.857c.874 4.66–20.04 16.642–20.04 16.642s9.537 24.303 5.523 26.817c-4.015 2.515–23.545–14.023–23.545–14.023S29.333 84.493 25.633 81.785c-3.7–2.71 6.657–26.472 6.657–26.472S11.234 43.94 12.383 39.108c1.15–4.832 26.55–3.393 26.55–3.393s4.88–25.853 11.28–25.648z”
      fill=”#dbdedd” />

先来实现第一个动画效果,即缩小星星。这里可以使用anime.js提供的timeline方法来管理每一个不同的动画。代码如下:

timeline
.add({
  targets: '.favorite__inactive',
  scale: {
    value: [1, 0],
    duration: 400,
    delay: 1000,
    easing: 'easeInQuad'
  }
})

这一步倒是非常的简单。再次提醒一点,为了方便实现动画效果,需要给每一个节点声明一个类。第一个动画其实就是用scale方法把星星的值从1缩小为0,注意我们使用的运动曲线是easeInQuad,这样可以动画有一个加速的效果。

接下来的动画效果是放大光环。要实现这个效果,需要用到两个圆圈,一个在里面,一个在外面。外圈是不透明的用来设置颜色,里面的圈是透明用来显示圈内的元素。这里需要注意的是光环的动画在星星缩小之前就开始运行了。因此,为了使元素在光环内显示,需要使用SVG中的蒙版。SVG蒙版的知识可以去这里看看。使用蒙版就可以显示想显示的内容,隐藏想隐藏的内容。代码如下:

<defs>
  <mask id="favorite__halo-mask">
    <rect width="100%" height="100%" fill="white"/>
    <circle class="favorite__halo-inner" cx="50" cy="50" r="0" fill="black"></circle>
  </mask>
</defs>
<circle class="favorite__halo-outer" cx="50" cy="50" r="48" fill="#feb53c" mask="url(#favorite__halo-mask)"></circle>

注意到代码中最后的一个圆圈元素,这是外圈即favorite__halo-outer元素,它使用mask方法引用了定义好的蒙版,是通过蒙版ID来引用的。并且蒙版包含事先定义好的另外一个圈(里面的圈)。接下来就是光环动画的实现了。另外,在蒙版里面还有一个白色的矩形,是用来保证内圈以外的元素不可见的。

初始状态,内圈的半径是0也就是不可见的状态。当放大光环的时候,两个圆圈之间要用到一点点延迟,当外圈开始动画的时候,内圈要比外圈延迟100毫秒才开始执行动画。这样才会营造圆圈不断的变薄的动画效果。代码如下:

timeline
// ...
.add({
  targets: '.favorite__halo-outer',
  scale: {
    value: [0, 1],
    duration: 400,
    delay: 1400,
    easing: 'easeOutQuad'
  },
  offset: 0
})
.add({
  targets: '.favorite__halo-inner',
  r: {
    value: [0, 49],
    duration: 300,
    delay: 1500,
    easing: 'easeOutQuad'
  },
  offset: 0
})

效果如下图所示:

接着来完成喷溅的动画效果。

喷溅这种动画效果,一般都是用来强调和夸张辅助其它动画效果的。这里的喷溅湿用来敲掉用户的操作,给予用户积极的暗示和引导。这样可以更多的鼓励用户来进行收藏这个操作,也能够拉升用户参与的活跃度。

这个喷溅动画效果是由5个不同方向的向外扩散的小动画组成。动画是一样的,只是角度不同而已,所以我们只需要实现其中一个角度的喷溅动画就可以了,其它四个可以直接复用,改个角度就行了。先创建html结构:

<div class="favorite__sprinkle"> // 1st sprinkle
  <div class="favorite__sprinkle-circle"></div>
</div>
<div class="favorite__sprinkle"> // 2nd sprinkle
  <div class="favorite__sprinkle-circle"></div>
</div>
...
<div class="favorite__sprinkle"> // 5th sprinkle
  <div class="favorite__sprinkle-circle"></div>
</div>

每个div旋转的基准角度是72度,通过360除以5可以得到。这样可以保证第三个喷溅动画在180度的位置。通过计算,可以知道每个div都是旋转72度来移动到它所属的位置。比如,ratate(36),ratate(108),ratate(180)...。

.favorite__sprinkle {
  ...
  transform: rotate(36deg);
}
.favorite__sprinkle:nth-child(2) {
  transform: rotate(108deg);
}
...
.favorite__sprinkle:nth-child(5) {
  transform: rotate(324deg);
}

最后就是来实现喷溅动画。这个动画是交叉来改变元素透明并且拉伸移动它们的位置来形成的一个动画效果。要实现拉伸效果,可以通过直接使用scale来实现。不过在写这篇文章的时候,犯了一个错误,我是直接通过CSS改变元素的高度来实现拉伸效果的,这样的一个不好的地方是性能不是很好,特别是在移动端上会感觉到卡顿的感觉。应该使用transform来实现拉伸效果,因为transform可以使用GPU来进行加速,性能会更好。

.add({
  targets: '.favorite__sprinkle',
  opacity: {
    value: [0, 1],
    ...
})
.add({
  targets: '.favorite__sprinkle-circle',
  height: {
    value: [5, 12],
    ...
})
.add({
  targets: '.favorite__sprinkle-circle',
  height: {
    value: [12, 5],
    ...
})
.add({
  targets: '.favorite__sprinkle-circle',
  opacity: {
    value: [1, 0],
    ...
})
.add({
  targets: '.favorite__sprinkle-circle',
  translateY: {
    value: [0, -28],
    ...
});

接下来的几个喷溅效果,就直接复用这个就可以了。至于最后一个改变圆圈的颜色,跟第一步一样这里就不重复了。

一个赞的动画效果就完成了。

完整代码地址,这里

本文主要是从How to create a favorite animation with anime-js这篇文章整理而来,有删减,有疏漏或者理解不到位的地方,还请多多指教!