自定义动画
之前我们介绍了 VChart 中如何配置基础动画和动画编排。除此之外,如果你有更特别的需求,或是更棒的动画创意,可以通过自定义动画的能力来实现。自定义动画的方式不仅仅在 VChart 中可以使用,在整个 VisActor 系列的可视化解决方案中,你都能找到它的身影。
本章将会介绍如何在 VChart 中自定义动画,包括自定义动画属性插值函数和自定义动画类两种方法。在开始之前,请确保您已经熟悉了并能够使用 VChart 中的基本图表动画能力。在上一节(复杂动画的配置与编排)中,我们介绍了时间片(TimeSlice)的概念,每个时间片都可以定义多个动画效果(effect),接下来,我们将围绕 effect.custom 来介绍自定义动画的方法。
自定义动画属性插值函数
自定义动画属性插值函数主要是提供设定动画执行前后的视觉通道,来实现一些动画效果。effect.custom 可以配置为一个自定义插值函数:
export type IAnimationChannelInterpolator = (
ratio: number,
from: any,
to: any,
nextAttributes: any,
datum: any,
element: IElement,
parameters: IAnimationParameters
) => boolean | void;
您需要创建一个实现 IAnimationChannelInterpolator 类型的动画函数,函数参数如下:
ratio:动画进行的进度比例,值为 0 到 1 之间的小数。from:动画开始时图元的视觉通道属性。to:动画开始时图元的视觉通道属性。nextAttributes:下一帧的属性值,可以在函数里进行计算和修改。datum:与当前图形元素相关的原始数据。element:当前图形元素。parameters:自定义动画参数,可以在创建动画时传递。
以下是一个自定义动画的示例:
自定义动画类
在 VChart 中,自定义动画是通过实现 ICustomAnimate 接口来完成的。以下是 ICustomAnimate 接口的定义:
export interface ICustomAnimate {
duration: number;
easing EasingType;
step?: IStep;
mode?: AnimateMode;
bind: (target IAnimateTarget, subAni: ISubAnimate => void;
onBind: () => void;
onRun: () => void;
onStart: () => void;
onEnd: () => void;
onUpdate: (end: boolean, ratio: number, out: Record<string, any>) => void;
update (end: boolean, ratio: number, out: Record<string, any>) => void;
getEndProps: () => Record<string, any> | void;
getFromProps: () => Record<string, any> | void;
MergedEndProps: () => Record<string, any> | void;
}
接口中定义了一些动的基本属性和生命周期函数,可以用于自定义动画的创建、更新和销毁等。详细文档可以参考VRender 自定义动画。
下面的例子使用了 VRender 内置 StreamLight 的自定义动画类,来实现的柱状图流光特效:
编写自己的自定义动画类
在 VChart 中,可以继承ACustomAnimate类来实现编写自己的自定义动画类,例如下面代码,编写了一个柱子从某个点 translate 的动画
import { ACustomAnimate, EasingType } from '@visactor/vrender-core';
export class BarFromPoint extends ACustomAnimate<{ y?: number; y1?: number; x?: number; x1?: number }> {
constructor(
from: { y?: number; y1?: number; x?: number; x1?: number },
to: { y?: number; y1?: number; x?: number; x1?: number },
duration: number,
easing: EasingType,
params: any
) {
const f = {
y: from.y1,
y1: from.y1,
x: from.x1,
x1: from.x1
};
super(f, { y: from.y, y1: from.y1, x: from.x, x1: from.x1 }, duration, easing, params);
}
getEndProps(): Record<string, any> {
return this.to;
}
getFromProps(): void | Record<string, any> {
return this.from;
}
onBind(): void {
this.target && this.target.setAttributes(this.from);
}
onUpdate(end: boolean, ratio: number, out: Record<string, any>): void {
const { x: fromX, y: fromY } = this.params.from;
const { x: toX, y: toY, y1: toY1 } = this.to;
const height = toY1! - toY!;
out.x = fromX + (toX! - fromX) * ratio;
out.y = fromY + (toY! - fromY) * ratio;
out.y1 = out.y + height;
}
}
然后将这个类传入 spec 中自定义函数的 custom 字段即可
{ 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 } ] } ], animationAppear: { bar: { channel: ['x', 'y', 'x1', 'y1', 'width', 'height', 'cornerRadius'], custom: BarFromPoint, duration: 1000, easing: 'linear', customParameters: { from: {x: 0, y: 0}, } }, }, xField: 'month', yField: 'sales' }