HOME

使用 SVG Morphing 制作自己的加载动画

每一个需要让用户等待的应用都应该有加载界面,可以是简单的文本,比如 加载中…,也可以是有趣的动画。当然,一个好玩的加载动画能够大大增加用户等待的耐心,谁喜欢枯燥的文字呢。所以,投入点时间寻找或者制作一个加载动画是很有意义的。感谢 SVG 和相关的动画技术,现在制作一款复杂的动画已经变得十分容易了。

这里我使用 SVG 的形变技术(Shape Morphing)来做一个简单的矩形、三角形、圆形变换的动画。

See the Pen SVG Morphing Loading Animation by fatelovely (@fatelovely) on CodePen.

SVG Shape Morphing

上面的动画里,有三个量在变换。图形的形状,图形的位置以及颜色。位置和颜色都是比较简单的,CSS Transition 就可以搞定。问题就是形状的变化比较复杂。形变技术理解起来比较简单,图形从一个形状变换到另一个形状,无非就是构成图形的顶点位置发生了变化。所以,只要将开始图形和结束图形的顶点之间的对应关系找到,然后对顶点进行 transition 就行了。从这可以看出,SVG 的形变必须要求开始图形和结束图形的顶点数一定要相同,如下所示。

See the Pen SVG Morphing Demo by fatelovely (@fatelovely) on CodePen.

GSAP

SVG 的形变有两种方式,第一是使用 SMIL,第二是使用 JS 库。SMIL 目前已经被废弃,这里我们使用大名鼎鼎的 GreenSock Animation Platform, GSAP 动画库来实现。GSAP 是一个非常高级的动画库,功能强大接口简洁,我们使用 MorphSVGPlugin 插件来完成 SVG 的形变功能。

上面我说过,SVG 的形变动画要求开始图形和结束图形顶点数一样,但这并不意味着我们提供给 GSAP 的开始图形和结束图形顶点数必须一致。GSAP 的一大特点便是允许我们提供顶点数完全不一样的图形来进行形状变换,GSAP 内部会自己计算顶点并进行 transition。

使用 GSAP 进行 SVG 形变非常简单,指定开始图形,指定结束图形以及顶点映射关系即可。顶点映射关系表示开始图形的哪一个顶点对应结束图形的第一个顶点,后面的顶点按顺序类推。

关于顶点映射关系的问题,可以使用 GSAP 官方提供的一个工具 findShapeIndex 来查看效果,非常直观。

下面的代码表示从 startShape 形变到 endShape ,时间为 1 秒钟,同时开始图形的第三个顶点对应结束图形的第一个顶点。

var stratShape = document.getElementById("start")
var endShape = document.getElementById("end")

TweenLite.to(endShape, 1, {
  morphSVG: endShape,
  shapeIndex: 2,
})

Loading Animation

根据上面的知识,我们来制作一个矩形、三角形、圆形之间的形变动画就变得非常简单了。首先,定义这三个图形,这里图形的位置都在 0,0,100,100 之间。注意隐藏 triangle 以及 circle ,只显示 rect

 <path id="rect" fill="#1EB287" d="M 0,0
                   C 50,0 50,0 100,0
                   100,50 100,50 100,100
                   50,100 50,100 0,100
                   0,50 0,50 0,0
                   Z"></path>

<path id="triangle" fill="#188fc2" d="M 25,50
                   C 37.5,25 37.5,25 50,0
                   75,50 75,50 100,100
                   50,100 50,100 0,100
                   12.5,75 12.5,75 25,50
                   Z"></path>
<path id="circle" fill="#bb625e" d="M 50,0
                   C 77.6,0 100,22.4 100,50
                   100,77.6 77.6,100 50,100
                   22.4,100, 0,77.6, 0,50
                   0,22.4, 22.4,0, 50,0
                   Z"></path>

然后,使用 GSAP 进行变换即可,因为涉及到一系列变换,矩形到三角形,三角形到圆形,圆形到矩形,我们使用 GSAP 提供的 TimelineLite 来调度时间使这三个变换顺序进行。

var tl = new TimelineLite()
var duration = 1
tl.to(rect, duration, {
  morphSVG: triangle,
})
tl.to(rect, duration, {
  morphSVG: circle,
})
tl.to(rect, duration, {
  morphSVG: rect,
})

到了这里,形变就已经完成了。但是还缺少了颜色变换和位置变换。颜色和位置变换需要使用 GSAP 的 CSS 插件,增加一点代码即可,这里不再赘述了。

导出为 GIF

SVG 的动画做好了,但并不是所有的平台都支持 SVG,并且每次使用动画都要加载一堆库和一堆代码也比较麻烦。最好的解决方案是导出为 GIF。

比较简单的方法是使用 LICEcap 软件直接录制浏览器屏幕生成 GIF,缺点是控制度不高,不好微调。

这里我使用 Nightmare 渲染我们的文档,然后自己截屏,最后合成为 GIF。

Nightmare 是类似 Phantom 的一个 Headless Browser,特别适合这种类型的任务,优点是代码比 Phantom 要简洁。

var Nightmare = require("nightmare")
var nightmare = new Nightmare({
  width: 400,
  height: 400,
  titleBarStyle: "hidden", // 影藏标题栏,这样内容区和视口一样大
})
  .goto("http://localhost:8080") // 这是我们的动画页面
  .wait(1000)

for(var i = 0; i < 60; i++) {
  nightmare.screenshot("loading/loading_" + i + ".png")
  nightmare.wait(16.6)
})

nightmare.run(function(err) {
  if(err) {
    console.log(err)
  } else {
    console.log("Done")
  }
})

运行以后,可以在 loading 文件夹里面看到所有截屏出来的图片,将多余的图片剔除掉以后,上传到 gifcreator 上,调整一下速度,然后导出即可。

最终,GIF 效果如下。