ECharts渲染引擎动画揭秘:动画示例解读底层实现

1,049 阅读5分钟

一、简介

ZRender作为ECharts的底层绘图引擎,本身是一个轻量级、高效的Canvas和SVG渲染库,提供了包括图形渲染、事件处理、动画效果等功能。然而,想要让一个静态的元素‘动’起来,ZRender的内部需要做什么处理呢?本文将通过一个简单的示例讲解ZRender的底层动画实现,并总结出其动画设计架构。

二、小球动画示例讲解

首先我们来看一下ZRender官方文档中给出的一个循环往返的小球动画示例:

fd210df0-ebd0-4357-acb9-38f0dc414969.gif

要实现它很简单,只需要以下几行代码:

carbon.svg

这段代码做了四件事,分别是:

  1. 获取一个容器container,并用这个容器初始化一个ZRende实例zr;
  2. 定义一个圆形元素circle;
  3. 监听circle的‘shape’属性,并添加关键帧(实现动画最关键的一步);
  4. 把circle元素添加到zr实例中;

其中第3点是实现动画的核心,为了实现小球“动”起来,ZRender的内部做了以下这些事:

circle.png

(1) 我们从animate函数开始,需要注意的是,此animate函数是ZRender元素从Element基类中继承来的,而Animation类中也有此函数,两者实现的功能是类似的,但此处使用的是元素上的方法。

animate('shape', true).png

(2) 如此,我们为circle元素绑定了一个以其自身shape属性为目标的动画控制器animator,并且通过第二个参数值loop=true指定为循环动画,然后会返回该animator实例。接着,我们继续链式调用动画控制器animator上的when方法:

when(5000, {    cx w-r}).png

(3) 调用when方法后,会遍历传入的对象的属性,为不同属性创建不同的动画轨道track,而相同的属性会使用同一个动画轨道track,并且更新动画的最大持续时间maxTime(此处为5000ms),然后通过track的addKeyfram函数将与此属性相关的关键帧对象(此示例为{time:5000,cx:w-r})放入一个名为keyframes的数组中存放(值得注意的是,若是未显示声明0时刻的关键帧,将会自动在keyframes数组中加入0时刻的初始值关键帧对象,此示例下为{time:0,cx:r}):

track.addKeyframe().png

(4) 通过步骤(2)和(3)可以知道,在调用了两次when函数之后,动画最大持续时间maxTime=10000ms,keyframes数组中应该存在三个关键帧对象,分别是0时刻初始位置cx=r、5000ms时位置cx=w-r和10000ms时位置cx=r,每个关键帧对象除了记录关键帧时间time与关键帧对应变化属性值value外,还会记录对应属性的初始状态值rawValue与当前时间进度的百分比percent。最后链式调用start函数:

start().png

(5) 在start函数中,第一步会首先遍历所有记录的需要变化的属性值,找到属性对应的动画轨道track,并遍历该动画轨道中的关键帧数组keyframes,根据关键帧对象中的time属性值占最大动画持续时间maxTime(此示例中maxTime=10000ms)的比例更新percent属性值。接着第二步会创建一个动画切片clip,在动画切片clip中会定义一个回调函数onframe,它的作用就是每次调用时都会遍历所有动画轨道track,并调用track上的step方法更新当前时间比例percent下,对应属性值相较于初始状态所经历的变化。

到此,就是第3步中ZRender内部所做的全部了,然而,这就结束了吗?当然不,第3步完成了“应该怎么动”这么一件事,还缺少动起来的“能源核心”。这颗“能源核心”就藏在第4步的‘zr.add(circle)’以及步骤(1)中提到的Animation类中:

zr.add(circle).png

(6) ZRender的实例zr将circle元素添加后主要做了两件事。一是将所有动画切片clip(本示例只有一条动画切片)以链表的形式记录在animation实例中;二是通过每16ms一次的频率来调用animation的update函数,update函数会遍历动画切片链表,触发步骤(5)中定义的回调函数onframe,进而更新每个动画切片中动画轨道的状态,如此实现了小球的位置变化,让小球‘动’了起来。

三、ZRender动画设计架构

通过第二章的示例,我们在动画实现的过程中遇到了不同的实例对象,如动画轨道track、动画控制器animator、动画切片clip等,它们之间的关系组成了ZRender的动画设计架构,如下图所示:

ZRender动画架构.drawio.svg

概括来说,每个动画元素都会经历以下步骤:

  1. 浏览器通过requestAnimationFrame函数每16ms调用ZRender实例中animation的update方法;
  2. animation在update方法中通过动画切片clip的表头指针遍历clip上的step方法;
  3. clip在step方法中将遍历当前clip上的动画轨道track,并调用track上的step方法;
  4. track在step方法中将根据当前时刻来查找keyframes数组中对应的关键帧对象,并由当前时刻更新对应属性值;
  5. 若是loop=true的循环动画,则将ZRender内部相关状态初始化后返回步骤1;

若有不对的地方,欢迎指正。