10.1 动画的概念和类型 分数:4
魔力之帧(下):VChart 动画编程实践在这篇文章中,我们将从一些常见的图表动画入手,详细介绍在 VChart 中的编 - 掘金
- 代码入口:
packages/vchart/src/animation/
packages/vchart/src/series/line/animation
packages/vchart/src/core/vchart
packages/vchart/src/core/interface
packages/vchart/src/complie/mark
-
解读重点:
-
动画分类(按执行时机,按效果)
-
动画系统的整体设计
动画的概念
在VChart中,动画是指在图表渲染过程中,通过视觉效果来增强数据展示的动态性和交互性。动画系统允许开发者配置和控制图表元素(如柱状图、饼图、折线图等)在不同状态下的过渡效果。
在 VisActor 中动画被视作为渲染阶段的修饰:动画配置与图形语法流程执行得到的图元视觉通道一起决定了渲染阶段的结果。动画的表现是具体图形元素在某一时间段内视觉通道属性的插值计算或者特殊计算逻辑,而动画配置描述了这一计算的触发时机以及执行时长。

动画的分类
生命周期示范

按执行时机分类
图表动画在 VChart 中根据状态场景(执行时机)会被区分为:图表入场动画、数据更新动画和图表退场动画。
-
**图表入场动画:**是指图表创建时的动画效果。
-
数据更新动画:当我们更新图表数据时,图元发生的属性动画则称为数据更新动画。分为:新增图元动画、图元更新动画和退场图元动画、状态变更动画、任意时机触发的动画。通常情况下,你不需要考虑如何控制这三种更新动画,因为 VChart 会在数据更新时,识别出新数据与上一次数据间的关联,从而正确执行更新动画。
-
图表退场动画:在某些场景下,我们可能需要移除图表。此时,我们可以为图表设置退场动画,让图表在除之前有一个平滑过渡的动画效果。

https://visactor.io/vchart/guide/tutorial_docs/Animation/Animation_Types 动画教程文档
https://www.visactor.io/vchart/option/barChart#animationState 动画配置文档
图表
-
在图表首次渲染时,元素从无到有的过渡效果。
-
示例代码:
animationAppear配置项用于定义图表入场动画。

<body> <div id="chart" style="width: 600px;height:400px;"></div> </body> <script> const spec = { type: 'bar', data: [ { id: 'barData', values: [ { month: 'Monday', sales: 22 }, { month: 'Tuesday', sales: 13 }, { month: 'Wednesday', sales: 25 }, { month: 'Thursday', sales: 29 }, { month: 'Friday', sales: 38 } ] } ], xField: 'month', yField: 'sales', //添加入场动画,持续1500s,线性缓入 animationAppear: { duration: 1500, easing: 'linear' } }; const vchart = new VChart(spec, { dom: 'chart' }); vchart.renderSync(); </script>
animationAppear?: boolean | IStateAnimateSpec<Preset> | IMarkAnimateSpec<MarkName>;
数据更新动画
当我们更新图表数据时,图元发生的属性动画则称为更新动画。在 VChart 中,用户手动调用 updateData 接口会触发图表数据更新,另外,点击图例时也更新图表数据。更新动画分为三类:新增图元动画、图元更新动画和退场图元动画。
- 新增图元动画 (
**animationEnter**):
-
新增图元动画是指当图表数据更新时,新添加数据应的图元的动画效果。
-
我们可以使用
animationEnter配置来设置新增图元动画。
animationEnter?: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>;
- 图元更新动画 (
**animationUpdate**):
-
图元更新动画是指当图表数据更新,原有数据对应的图元的更新动画效果。
-
我们可以使用
animationUpdate配置来设置图元更新动画。
animationUpdate?: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>;
- 退场图元动画 (
**animationExit**** )**:
- 退图元动画是指当图表数据更新时,被删除数据所对应图元的动画效果。我们可以使用
animationExit配置来设置退场图元动画。
animationExit?: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>;
- 图元状态切换动画 (State):
-
当图表状态发生改变时的过渡效果。
-
示例代码:
animationState配置项用于定义状态切换动画。
animationState?: boolean | IStateAnimationConfig;
- 常态动画 (Normal):常用于循环
-
用于定义持续不断的动画效果。
-
示例代码:
animationNormal配置项用于定义循环动画。
animationNormal?: IMarkAnimateSpec<MarkName>;
图表
- 在图表销毁或隐藏时,元素的退场效果。
animationDisappear?: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>;
按效果分类
type 图元原子化
- 动画效果:动画效果描述了在某一特定的动画阶段中图元以怎样的方式执行渲染的变化。动画效果包括普通的视觉通道插值,例如竞速条形图中柱子颜色、宽度、位置的变化;同时动画效果也包含一些特殊的变化,例如下图中的图元形变。
- 渐入渐出 (FadeIn/FadeOut):
-
元素透明度从0到1或从1到0的变化。
-
示例代码:
Appear_FadeIn和Disappear_FadeOut。
const Appear_FadeIn: IAnimationTypeConfig = { type: 'fadeIn' };
- 生长 (Grow):
-
元素从某个初始尺寸逐渐增长到最终尺寸。
-
示例代码:
barGrowOption和pieGrowOption。
function barGrowOption(barParams: IBarAnimationParams, isOverall = true) {/***/}
- 裁剪 (Clip):
-
通过裁剪区域来显示元素的逐步出现或消失。
-
示例代码:
registerCartesianGroupClipAnimation。
const registerCartesianGroupClipAnimation = () => { Factory.registerAnimation('cartesianGroupClip', (params?: ICartesianGroupAnimationParams) => {/***/}); };
- 波浪 (Wave):
-
特殊效果,如液态图中的波浪效果。
-
示例代码:
Appear_Wave。
const Appear_Wave: IAnimationTypeConfig = { duration: 2000, loop: true, channel: { wave: { from: 0, to: 1 } } };
- 缩放 (Scale):
-
元素大小从一个比例缩放到另一个比例。
-
示例代码:
Appear_ScaleIn。
const Appear_ScaleIn: IAnimationTypeConfig = { type: 'growCenterIn' };
更多动画效果的属性和配置可参考https://visactor.com/vchart/guide/tutorial_docs/Animation/Animation_Attributes_and_Settings
动画系统的设计
简单柱状图动画配置
下面以创建一个简单的柱状图为例,说明如何使用VChart的动画系统来实现基本动画效果的原理。

<body> <!-- 为 vchart 准备一个具备大小(宽高)的 DOM,当然你也可以在 spec 配置中指定 --> <div id="chart" style="width: 600px;height:400px;"></div> </body> <script> const spec = { type: 'bar', data: [ { id: 'barData', values: [ { month: 'Monday', sales: 22 }, { month: 'Tuesday', sales: 13 }, { month: 'Wednesday', sales: 25 }, { month: 'Thursday', sales: 29 }, { month: 'Friday', sales: 38 } ] } ], xField: 'month', yField: 'sales' }; // 创建 vchart 实例 /** * 说明:cdn 方式引入的时候,VChart 的引用方式需要注意: * const vchart = new VChart.default(spec, { dom: 'chart' }); */ const vchart = new VChart(spec, { dom: 'chart' }); // 绘制 vchart.renderSync(); </script>
如何创建一个基础VChart可以参考以下文档 https://www.visactor.io/vchart/guide/tutorial_docs/Getting_Started 快速上手 https://www.visactor.io/vchart/guide/tutorial_docs/Basic/How_to_Import_VChart 导入VChart https://www.visactor.io/vchart/guide/tutorial_docs/Basic/A_Basic_Spec 基础配置 https://www.visactor.io/vchart/guide/tutorial_docs/VChart_Skill_Usage VChart Skill 使用
在 VChart 类中,spec(图表配置)中的 animation 配置项用于控制图表的动画行为。具体来说,animation 配置项可以定义图表在不同状态下的动画效果,例如入场动画、更新动画、退出动画等。
animation
- 定义动画行为:
-
animation配置项可以包含多个子属性,如appear、enter、update、exit和disappear,分别对应不同的动画场景。 -
每个子属性可以进一步配置动画的持续时间 (
duration)、缓动函数 (easing)、是否逐个执行 (oneByOne) 等参数。
- 控制动画开关:
-
如果将
animation设置为false,则禁用所有动画效果。 -
如果设置为
true或者提供具体的配置对象,则启用相应的动画效果。
- 传递给底层组件:
VChart类会将animation配置传递给内部的Compiler和Chart实例,这些实例会根据配置来决定是否应用动画以及如何应用动画。
柱状图执行动画示例说明

// 1. 创建图表配置项与数据 const initialSpec = { type: 'bar', data: [ { id: 'barData', values: [ { month: 'January', sales: 22 }, { month: 'February', sales: 13 }, { month: 'March', sales: 25 }, { month: 'April', sales: 29 }, { month: 'May', sales: 38 } ] } ], xField: 'month', yField: 'sales', crosshair: { xField: { visible: true } }, animation: true // 开启动画 };
// 2. 创建 VChart 实例 const vchart = new VChart(initialSpec, { dom: 'chart' });
// 3. 渲染图表 vchart.renderAsync().then(() => { console.log('图表渲染完成'); });
// 4. 动画入场 setTimeout(() => { console.log('动画入场'); }, 1000);
// 5. 数据更新(新增图元) setTimeout(() => { const newData = [ { month: 'June', sales: 45 }, { month: 'July', sales: 50 } ]; vchart.updateDataSync('barData', newData, undefined, { reAnimate: true }); console.log('新增图元'); }, 3000);
// 6. 数据更新(图元更新) setTimeout(() => { const updatedData = [ { month: 'January', sales: 30 }, { month: 'February', sales: 20 }, { month: 'March', sales: 35 }, { month: 'April', sales: 39 }, { month: 'May', sales: 48 }, { month: 'June', sales: 55 }, { month: 'July', sales: 60 } ]; vchart.updateDataSync('barData', updatedData, undefined, { reAnimate: true }); console.log('图元更新'); }, 6000);
// 7. 数据更新(图元退出) setTimeout(() => { const remainingData = [ { month: 'January', sales: 30 }, { month: 'February', sales: 20 }, { month: 'March', sales: 35 }, { month: 'April', sales: 39 }, { month: 'May', sales: 48 } ]; vchart.updateDataSync('barData', remainingData, undefined, { reAnimate: true }); console.log('图元退出'); }, 9000);
// 8. 图元状态(state)的使用 setTimeout(() => { vchart.updateState( { selected: { style: { fill: 'red' } } }, (series, mark, stateKey) => { return mark.datum.sales > 40; } ); console.log('图元状态更新'); }, 12000);
// 9. 图表退场 setTimeout(() => { vchart.release(); console.log('图表退场'); }, 15000);
1. **创建图表配置项与数据**:
1. **创建 VChart 实例**:
1. **渲染图表**:
1. **动画入场**:
1. **数据更新(新增图元)**:
1. **数据更新(图元更新)**:
1. **数据更新(图元退出)**:
1. **图元状态(state)的使用**:
1. **图表退场**:
</div></div>
* 定义了一个初始的图表配置项`initialSpec`,其中包含图表类型、数据、坐标轴字段和动画配置。
* 数据部分包含了一个`barData`的数据集,初始包含5个月的销售数据。
* 使用`initialSpec`和DOM容器`chart`创建一个VChart实例。
* 调用`renderAsync`方法异步渲染图表。图表渲染完成后,会触发动画入场效果。
* 在渲染完成后,通过`setTimeout`模拟动画入场。实际动画效果由VChart内部处理。
* 在3秒后,通过`updateDataSync`方法新增两个月的销售数据。`reAnimate: true`参数确保新增数据时有动画效果。
* 在6秒后,通过`updateDataSync`方法更新所有图元的数据。`reAnimate: true`参数确保更新数据时有动画效果。
* 在9秒后,通过`updateDataSync`方法移除两个月的销售数据。`reAnimate: true`参数确保移除数据时有动画效果。
* 在12秒后,通过`updateState`方法更新图元的状态。这里设置了一个`selected`状态,当图元的`sales`值大于40时,图元的填充颜色变为红色。
* 在15秒后,通过`release`方法销毁图表实例,图表退场。
---
<div style="display: flex;"><div style="flex: 40; margin:5px;">#### 动画流程图
</div><div style="flex: 60; margin:5px;">#### 流程说明
1. **创建图表配置项与数据**:定义初始图表配置和数据。
1. **创建 VChart 实例**:使用配置和DOM容器创建VChart实例。
1. **渲染图表**:调用`renderAsync`方法渲染图表,触发动画入场效果。
1. **动画入场**:图表渲染完成后,自动触发入场动画。
1. **数据更新(新增图元)**:通过`updateDataSync`方法新增数据,触发新增动画。
1. **数据更新(图元更新)**:通过`updateDataSync`方法更新数据,触发更新动画。
1. **数据更新(图元退出)**:通过`updateDataSync`方法移除数据,触发退出动画。
1. **图元状态(state)的使用**:通过`updateState`方法更新图元状态,设置特定条件下的样式。
1. **图表退场**:通过`release`方法销毁图表实例,图表退场。
</div></div>
### 源码实现流程
1. **初始化 VChart 实例**
当你创建一个 `VChart` 实例并传入 `spec` 时,构造函数会处理 `animation` 配置:
文件:`vchart.ts` 方法:`constructor`
```xml
constructor(spec: ISpec, options: IInitOption) {
this._option = mergeOrigin(this._option, { animation: (spec as any).animation !== false }, options);
*// ...*
}
这段代码确保了如果 spec 中没有显式禁用动画(即 animation !== false),则启用动画。
- 设置新 spec 并初始化图表
在 VChart 类中,_setNewSpec 方法用于设置新的 spec,并将其转换为内部使用的格式:
文件:vchart.ts 方法:_setNewSpec
private _setNewSpec(spec: any, forceMerge?: boolean): boolean { if (!spec) { return false; } if (isString(spec)) { spec = JSON.parse(spec); } if (forceMerge && this._originalSpec) { spec = mergeSpec({}, this._originalSpec, spec); } this._originalSpec = spec; this._spec = this._getSpecFromOriginalSpec(); return true; }
接着,_initChartSpec 方法会根据 spec 初始化图表规格:
文件:vchart.ts 方法:_initChartSpec
private _initChartSpec(spec: any, actionSource: VChartRenderActionSource) { *// 如果用户注册了函数,在配置中替换相应函数名为函数内容* if (VChart.getFunctionList() && VChart.getFunctionList().length) { spec = functionTransform(spec, VChart); } this._spec = spec; if (!this._chartSpecTransformer) { this._chartSpecTransformer = Factory.createChartSpecTransformer( this._spec.type, this._getChartOption(this._spec.type) ); } this._chartSpecTransformer?.transformSpec(this._spec); *// 插件生命周期* this._chartPluginApply('onAfterChartSpecTransform', this._spec, actionSource); this._specInfo = this._chartSpecTransformer?.transformModelSpec(this._spec); *// 插件生命周期* this._chartPluginApply('onAfterModelSpecTransform', this._spec, this._specInfo, actionSource); }
- 创建和初始化 Chart 实例
在 _initChart 方法中,创建并初始化图表实例:
文件:vchart.ts 方法:_initChart
private _initChart(spec: any) { if (!this._compiler) { this._option?.onError('compiler is not initialized'); return; } if (this._chart) { this._option?.onError('chart is already initialized'); return; } const chart = Factory.createChart(spec.type, spec, this._getChartOption(spec.type)); if (!chart) { this._option?.onError('init chart fail'); return; } this._chart = chart; this._chart.setCanvasRect(this._currentSize.width, this._currentSize.height); this._chart.created(this._chartSpecTransformer); this._chart.init(); this._event.emit(ChartEvent.initialized, { chart, vchart: this }); }
- 更新动画状态
当图表需要重新渲染或更新时,_updateAnimateState 方法会被调用来更新动画状态:
文件:vchart.ts 方法:_updateAnimateState
private _updateAnimateState(initial?: boolean) { if (this._option.animation) { const animationState = initial ? AnimationStateEnum.appear : AnimationStateEnum.update; this._chart?.getAllRegions().forEach(region => { region.animate?.updateAnimateState(animationState, true); }); this._chart?.getAllComponents().forEach(component => { component.animate?.updateAnimateState(animationState, true); }); } }
-
初始状态:如果
initial为true,则设置动画状态为AnimationStateEnum.appear(入场动画)。 -
更新状态:否则,设置为
AnimationStateEnum.update(更新动画)。
- 渲染图表
在 renderSync 和 renderAsync 方法中,animation 配置会被传递给编译器进行渲染:
文件:vchart.ts 方法:_renderSync
protected _renderSync = (option: IVChartRenderOption = {}) => { const self = this as unknown as IVChart; if (!this._beforeRender(option)) { return self; } *// 填充数据绘图* this._compiler?.render(option.morphConfig); this._afterRender(); return self; };
- 动画状态的更新
在 updateSpec 和 updateCustomConfigAndRerender 方法中,reAnimate 标志用于决定是否重新触发动画:
文 件:vchart.ts 方法:updateSpec 和 updateCustomConfigAndRerender
if (userUpdateOptions?.reAnimate) { this.stopAnimation(); this._updateAnimateState(true); }
动画系统设计概述
VChart的动画系统设计遵循模块化、可扩展和易于配置的原则,旨在为开发者提供一个灵活且强大的工具来创建丰富的动画效果。以下是该系统的几个关键组成部分及其 工作原理:
原理
1. 动画接口与抽象
-
IAnimate 接口:定义了所有动画必须实现的方法和属性,包括获取唯一的ID、更新动画状态以及获取状态信号名称。
-
IAnimationSpec 接口:规定了动画配置的结构,涵盖了从入场到退场的各种动画设置。
classDiagram
class AnimationStateEnum {
--枚举--
appear: AnimationStateEnum
disappear: AnimationStateEnum
enter: AnimationStateEnum
update: AnimationStateEnum
exit: AnimationStateEnum
state: AnimationStateEnum
normal: AnimationStateEnum
none: AnimationStateEnum
}
class IAnimate {
<<interface>>
+updateAnimateState(state: AnimationStateEnum, noRender?: boolean): void
+getAnimationStateSignalName(): string
+id: number
}
class ICartesianGroupAnimationParams {
<<interface>>
+direction(): "x" | "y"
+orient(): "positive" | "negative"
+width(): number
+height(): number
}
class AnimateManager {
--属性--
-_stateMap: IAnimateState & StateMap
+id: number
--方法--
+updateAnimateState(state: AnimationStateEnum, noRender?: boolean): void
+getAnimationStateSignalName(): string
+constructor()
}
class MarkAnimationSpec {
--属性--
appear: IAnimationConfig
enter: IAnimationConfig
update: IAnimationConfig[]
exit: IAnimationConfig
disappear: IAnimationConfig
}
class IAnimationSpec {
--属性--
animationAppear: boolean | IStateAnimateSpec<Preset> | IMarkAnimateSpec<MarkName>
animationEnter: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>
animationUpdate: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>
animationExit: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>
animationDisappear: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>
animationState: boolean | IStateAnimationConfig
animationNormal: IMarkAnimateSpec<MarkName>
}
class IStateAnimateSpec {
--属性--
duration?: number
delay?: number
easing?: EasingType
oneByOne?: boolean
preset?: Preset | false
}
class ICommonStateAnimateSpec {
--属性--
duration?: number
delay?: number
easing?: EasingType
oneByOne?: boolean
}
class IMorphSeriesSpec {
--属性--
enable?: boolean
morphKey?: string
morphElementKey?: string
}
class IAnimateState {
--属性--
animationState: { callback: (datum: any, element: IElement) => AnimationStateEnum }
}
class IAnimationConfig {
--属性--
type?: string
channel?: string
custom?: Function
customParameters?: Function
oneByOne?: boolean | number
duration?: number
easing?: EasingType
delay?: number
delayAfter?: number
}
% 关系
AnimationStateEnum "1" --|> "多" AnimateManager: 使用
AnimateManager "1" --|> "1" IAnimate: 实现
AnimateManager "1" -- "1" ICartesianGroupAnimationParams: 依赖
IAnimationSpec "1" -- "多" spec.ts: 定义于
MarkAnimationSpec "1" -- "1" config.ts: 由 config.ts 使用
IAnimationConfig "1" -- "多" utils.ts: 由 utils.ts 处理
IStateAnimateSpec "1" -- "1" ICommonStateAnimateSpec: 继承
IAnimationSpec "1" -- "1" IStateAnimateSpec: 关联
IAnimationSpec "1" -- "1" IMorphSeriesSpec: 关联
IAnimateState "1" -- "1" AnimateManager: 内部使用
IAnimationConfig "1" -- "1" ICommonStateAnimateSpec: 继承
2. 动画管理器
- AnimateManager 类:继承自
StateManager并实现了IAnimate接口,负责管理动画的状态,并提供方法来根据传入的状态更新动画。它处理动画状态的更新和检索,并根据不同状态更新动画状态。
3. 工厂模式
- Factory 类:用于注册新的动画类型,允许将自定义动画逻辑添加到图表组件中。通过静态方法
registerAnimation,可以将特定类型的动画与其配置关联起来,方便后续调用。