10.3 状态变更动画
分数:5
-
状态动画:
-
代码入口:
packages/vchart/src/animation/ -
解读重点:
-
状态 动画的实现
-
其他参考文档:
https://www.visactor.io/vchart/guide/tutorial_docs/Animation/Animation_Types
https://www.visactor.io/vrender/guide/asd/Basic_Tutorial/Animate
https://visactor.io/vgrammar/guide/guides/animation
魔力之帧(上):前端图表库动画实现原理一幅生动的可视化作品往往少不了动画的参与。无论是各色各样的图表还是叙事作品,组织周 - 掘金
往往在图表呈现的时候,不同的图元有各自所代表的含义,信息展示中需要强调或者对比某些元素,通过切换图元的状态来展示数据,这一过程也需要注重视觉效果,当状态变更时有较为自然的视觉体验。
状态动画(包括
state和normal
状态变更动画、任意时机触发的动画
状态动画是指图表元素根据其当前状态变化时触发的动画效果。在VChart中,状态动画的设计允许开发者为不同的状态(如进入、更新、退出等)定义特定的动画行为。特别地,normal 状态动画指的是那些循环播放或持续存在的动画效果,它们可以在图表渲染完成后一直运行,直到被显式停止。
1. 动画配置结构
IAnimationSpec 接口
IAnimationSpec接口定义了动画配置的基本结构,其中包含了针对不同状态的动画设置。对于normal动画来说,它可以通过animationNormal属性来指定:
interface IAnimationSpec<MarkName extends string, Preset extends string> {
// ... 其他状态 ...
animationNormal?: IMarkAnimateSpec<MarkName>;
}
这里,IMarkAnimateSpec是一个泛型接口,用于描述具体图元(如柱状图中的每个柱子)的动画配置。通过这种方式,开发者可以为每个图元定义个性化的normal动画效果。
2. 动画管理器
AnimateManager 类
AnimateManager类负责管理和协调所有动画的状态。它实现了IAnimate接口,并提供了方法来更新和检索动画状态。对于normal动画而言,AnimateManager会确保这些动画在图表渲染完成后自动启动,并且可以根据需要暂停或恢复。
class AnimateManager extends StateManager implements IAnimate { updateAnimateState(state: AnimationStateEnum, noRender?: boolean) { if (state === AnimationStateEnum.normal) { this.updateState( { animationState: { callback: (datum: any, element: IElement) => state } }, noRender ); } } }
当图表元素进入normal状态时,updateAnimateState方法会被调用,并将状态传递给内部的状态管理逻辑。这使得所有符合条件的元素都能够执行对应的normal动画。
3. 动画配置生成
animationConfig 函数
为了简化用户配置和默认配置之间的合并过程,VChart提供了一个名为animationConfig的辅助函数。该函数遍历所有可能的动画状态,并根据用户提供的配置或默认配置构建出最终的动画配置对象。
function animationConfig<Preset extends string>(
defaultConfig: MarkAnimationSpec = {},
userConfig?: Partial<Record<IAnimationState, boolean | IStateAnimateSpec<Preset> | IAnimationConfig | IAnimationConfig[]>>,
params?: { dataIndex: (datum: any, params: any) => number; dataCount: () => number; }
): MarkAnimationSpec {
const config = {} as MarkAnimationSpec;
for (let i = 0; i < AnimationStates.length; i++) {
const state = AnimationStates[i];
const userStateConfig = userConfig ? userConfig[state] : undefined;
if (userStateConfig === false) continue;
if (state === 'normal') {
userStateConfig && (config.normal = userStateConfig as IAnimationTypeConfig);
continue;
}
let defaultStateConfig: IAnimationConfig[];
if (isArray(defaultConfig[state])) {
defaultStateConfig = defaultConfig[state] as IAnimationConfig[];
} else {
defaultStateConfig = [{ ...DEFAULT_ANIMATION_CONFIG[state], ...defaultConfig[state] } as any];
}
config[state] = defaultStateConfig;
}
return config;
}
此函数处理了normal状态下的动画配置合并,确保用户提供的配置能够正确应用到具体的图元上。如果用户没有提供自定义的normal动画配置,则使用默认配置。
4.
以散点图为例,假设我们希望为每个数据点添加一个轻微的脉冲效果作为normal动画。以下是详细的实现步骤:
- 定义动画配置:首先,在图表配置中为散点图系列指定
animationNormal配置。这里我们可以选择内置的pulse动画类型,并调整其持续时间和缓动函数。
const chartSpec = { series: [ { type: 'scatter', data: [/* 数据数组 */], animationNormal: { type: 'pulse', // 使用脉冲效果 duration: 800, easing: 'easeInOutQuad' } } ] };
- 注册动画:接下来,我们需要确保
pulse动画已经被正确注册到系统中。这一步骤通常在项目启动时完成,或者在需要的地方显式调用。
import { Factory } from '@visactor/vchart'; import { pulseAnimation } from './series/scatter/animation'; Factory.registerAnimation('pulse', pulseAnimation);
这里的pulseAnimation函数定义了脉冲动画的具体逻辑,例如如何改变图形元素的透明度或尺寸。
- 初始化图表实例:有了上述配置之后,我们可以 初始化一个
VChart实例,并将配置传递给它。这会触发图表的渲染过程,并应用相应的动画效果。
import { VChart } from '@visactor/vchart'; const container = document.getElementById('chart-container'); const chart = new VChart({ el: container, spec: chartSpec, options: { animation: true, // 开启动画 theme: 'light' // 使用浅色主题 } });
- 触发动画:一旦图表被渲染出来,所有的数据点都会自动开始执行
normal动画。这种动画会在图表存在期间持续循环,除非被显式停止。
// 如果需要暂停所有正在进行的 normal 动画 chart.pauseAnimation(); // 恢复之前暂停的 normal 动画 chart.resumeAnimation();
5. 动画任务的执行
IAnimationTask 接口
对于复杂的动画序列,VChart引入了IAnimationTask接口来描述动画任务的数据结构。每个任务包含时间偏移、动作队列和后继任务列表,形成了一种链式动画执行机制。
interface IAnimationTask { timeOffset: number; actionList: Action[]; nextTaskList: IAnimationTask[]; }
这种设计使得多个动画任务可以按顺序或并发执行,从而实现更加复杂和细腻的动画效果。对于normal动画而言,它可以作为一个独立的任务链的一部分,与其他动画任务一起协同工作。
6. 示例:创建一个带有
下面以创建一个带有normal动画的散点图为例,说明如何使用VChart的状态动画系统来实现基础流程。
步骤 1: 定义动画配置
首先,我们需要定义散点图的基本配置,包括数据源和其他视觉属性。同时,在这里我们也会指定normal动画配置,以确保每个数据点都能执行脉冲效果。
const chartSpec = { series: [ { type: 'scatter', data: [ { x: 10, y: 20 }, { x: 20, y: 30 }, { x: 30, y: 40 } ], animationNormal: { type: 'pulse', duration: 800, easing: 'easeInOutQuad' } } ] };
步骤 2: 注册动画
确保所需的pulse动画已经被正确注册到系统中。这一步骤通常在项目启动时完成,或者在需要的地方显式调用。
import { Factory } from '@visactor/vchart'; import { pulseAnimation } from './series/scatter/animation'; Factory.registerAnimation('pulse', pulseAnimation);
步骤 3: 初始化图表实例
有了上述配置之后,我们可以初始化一个VChart实例,并将配置传递给它。这一步骤会触发图表的渲染过程,并应用相应的动画效果。
import { VChart } from '@visactor/vchart'; const container = document.getElementById('chart-container'); const chart = new VChart({ el: container, spec: chartSpec, options: { animation: true, // 开启动画 theme: 'light' // 使用浅色主题 } });
步骤 4: 触发
一旦图表被渲染出来,所有的数据点都会自动开始执行normal动画。这种动画会在图表存在期间持续循环,除非被显式停止。
// 如果需要暂停所有正在进行的 normal 动画 chart.pauseAnimation(); // 恢复之前暂停的 normal 动画 chart.resumeAnimation();
步骤 5: 动态控制动画
在某些情况下,你可能想要动态地控制normal动画的行为,比如更改动画的速度或样式。VChart提供了灵活的方法来实现这一点。
// 更新某个系列的 normal 动画配置 chart.updateSeriesOptions(0, { animationNormal: { duration: 1200, // 更改持续时间 easing: 'linear' // 更改缓动函数 } }); // 重新应用新的动画配置 chart.render();
7. 动画状态管理
状态切换与更新
AnimateManager不仅管理normal动画,还负责处理其他状态下的动画切换。例如,当有新数据加入时,enter状态的动画会被触发;当数据更新时,update状态的动画生效;而当数据被移除时,则是exit状态的动画起作用。
class AnimateManager extends StateManager implements IAnimate { updateAnimateState(state: AnimationStateEnum, noRender?: boolean) { if (state === AnimationStateEnum.update) { // 更新状态下的动画逻辑 } else if (state === AnimationStateEnum.appear) { // 出现状态下的动画逻辑 } else if (state === AnimationStateEnum.normal) { // normal 状态下的动画逻辑 this.updateState( { animationState: { callback: (datum: any, element: IElement) => state } }, noRender ); } } }
在这个例子中,当元素进入normal状态时,updateAnimateState方法会更新元素的状态,并触发相应的动画逻辑。这意味着每个数据点都将按照预设的normal动画配置执行动画,直到状态再次发生变化。
8. 动画生命周期管理
事件监听与钩子
为了更好地管理动画的生命周期,VChart提供了一系列事件监听器和钩子函数。例如,VGRAMMAR_HOOK_EVENT.AFTER_DO_RENDER事件可以在图表首次渲染完成后触发,而VGRAMMAR_HOOK_EVENT.ANIMATION_END则会在动画结束时触发。
this._event.on(VGRAMMAR_HOOK_EVENT.AFTER_DO_RENDER, () => { this.runAnimationByState(AnimationStateEnum.normal); }); this._event.on(VGRAMMAR_HOOK_EVENT.ANIMATION_END, ({ event }) => { if (event.animationState === AnimationStateEnum.appear) { this.runAnimationByState(AnimationStateEnum.normal); } });
这段代码展示了如何在图表渲染完成后立即启动normal动画,以及如何在入场动画结束后无缝切换到normal动画。这种设计保证了动画之间的平滑过渡,提升了用户体验。
总结
通过上述步骤,我们详细解读了VChart中normal状态动画的实现原理。normal动画作为状态动画的一种,主要用于描述图表元素在稳定状态下持续存在的动画 效果。VChart通过模块化设计、工厂模式、状态管理器模式以及事件驱动机制,确保了normal动画的灵活性和可维护性。开发者可以根据实际需求轻松地定制不同类型的normal动画效果,从而增强图表的视觉吸引力和交互体验。