@antv/g-base
Version:
A common util collection for antv projects
255 lines (240 loc) • 7.37 kB
text/typescript
import { isEqual, isNumber, isFunction } from '@antv/util';
import * as d3Timer from 'd3-timer';
import { interpolate, interpolateArray } from 'd3-interpolate'; // 目前整体动画只需要数值和数组的差值计算
import { getEasing } from './register';
import * as PathUtil from '../util/path';
import { isColorProp, isGradientColor } from '../util/color';
import { ICanvas, IElement } from '../interfaces';
import { Animation } from '../types';
const IDENTITY_MATRIX = [1, 0, 0, 0, 1, 0, 0, 0, 1];
/**
* 使用 ratio 进行插值计算来更新属性
* @param {IElement} shape 元素
* @param {Animation} animation 动画
* @param {number} ratio 比例
* @return {boolean} 动画是否执行完成
*/
function _update(shape: IElement, animation: Animation, ratio: number) {
const cProps = {}; // 此刻属性
const { fromAttrs, toAttrs } = animation;
if (shape.destroyed) {
return;
}
let interf; // 差值函数
for (const k in toAttrs) {
if (!isEqual(fromAttrs[k], toAttrs[k])) {
if (k === 'path') {
let toPath = toAttrs[k];
let fromPath = fromAttrs[k];
if (toPath.length > fromPath.length) {
toPath = PathUtil.parsePathString(toAttrs[k]); // 终点状态
fromPath = PathUtil.parsePathString(fromAttrs[k]); // 起始状态
fromPath = PathUtil.fillPathByDiff(fromPath, toPath);
fromPath = PathUtil.formatPath(fromPath, toPath);
animation.fromAttrs.path = fromPath;
animation.toAttrs.path = toPath;
} else if (!animation.pathFormatted) {
toPath = PathUtil.parsePathString(toAttrs[k]);
fromPath = PathUtil.parsePathString(fromAttrs[k]);
fromPath = PathUtil.formatPath(fromPath, toPath);
animation.fromAttrs.path = fromPath;
animation.toAttrs.path = toPath;
animation.pathFormatted = true;
}
cProps[k] = [];
for (let i = 0; i < toPath.length; i++) {
const toPathPoint = toPath[i];
const fromPathPoint = fromPath[i];
const cPathPoint = [];
for (let j = 0; j < toPathPoint.length; j++) {
if (isNumber(toPathPoint[j]) && fromPathPoint && isNumber(fromPathPoint[j])) {
interf = interpolate(fromPathPoint[j], toPathPoint[j]);
cPathPoint.push(interf(ratio));
} else {
cPathPoint.push(toPathPoint[j]);
}
}
cProps[k].push(cPathPoint);
}
} else if (k === 'matrix') {
/*
对矩阵进行插值时,需要保证矩阵不为空,为空则使用单位矩阵
TODO: 二维和三维场景下单位矩阵不同,之后 WebGL 版需要做进一步处理
*/
const matrixFn = interpolateArray(fromAttrs[k] || IDENTITY_MATRIX, toAttrs[k] || IDENTITY_MATRIX);
const currentMatrix = matrixFn(ratio);
cProps[k] = currentMatrix;
} else if (isColorProp(k) && isGradientColor(toAttrs[k])) {
cProps[k] = toAttrs[k];
} else if (!isFunction(toAttrs[k])) {
// 非函数类型的值才能做插值
interf = interpolate(fromAttrs[k], toAttrs[k]);
cProps[k] = interf(ratio);
}
}
}
shape.attr(cProps);
}
/**
* 根据自定义帧动画函数 onFrame 来更新属性
* @param {IElement} shape 元素
* @param {Animation} animation 动画
* @param {number} elapsed 动画执行时间(毫秒)
* @return {boolean} 动画是否执行完成
*/
function update(shape: IElement, animation: Animation, elapsed: number) {
const { startTime, delay } = animation;
// 如果还没有开始执行或暂停,先不更新
if (elapsed < startTime + delay || animation._paused) {
return false;
}
let ratio;
const duration = animation.duration;
const easing = animation.easing;
const easeFn = getEasing(easing);
// 已执行时间
elapsed = elapsed - startTime - animation.delay;
if (animation.repeat) {
// 如果动画重复执行,则 elapsed > duration,计算 ratio 时需取模
ratio = (elapsed % duration) / duration;
ratio = easeFn(ratio);
} else {
ratio = elapsed / duration;
if (ratio < 1) {
// 动画未执行完
ratio = easeFn(ratio);
} else {
// 动画已执行完
if (animation.onFrame) {
shape.attr(animation.onFrame(1));
} else {
shape.attr(animation.toAttrs);
}
return true;
}
}
if (animation.onFrame) {
const attrs = animation.onFrame(ratio);
shape.attr(attrs);
} else {
_update(shape, animation, ratio);
}
return false;
}
class Timeline {
/**
* 画布
* @type {ICanvas}
*/
canvas: ICanvas;
/**
* 执行动画的元素列表
* @type {IElement[]}
*/
animators: IElement[] = [];
/**
* 当前时间
* @type {number}
*/
current: number = 0;
/**
* 定时器
* @type {d3Timer.Timer}
*/
timer: d3Timer.Timer = null;
/**
* 时间轴构造函数,依赖于画布
* @param {}
*/
constructor(canvas: ICanvas) {
this.canvas = canvas;
}
/**
* 初始化定时器
*/
initTimer() {
let isFinished = false;
let shape: IElement;
let animations: Animation[];
let animation: Animation;
this.timer = d3Timer.timer((elapsed) => {
this.current = elapsed;
if (this.animators.length > 0) {
for (let i = this.animators.length - 1; i >= 0; i--) {
shape = this.animators[i];
if (shape.destroyed) {
// 如果已经被销毁,直接移出队列
this.removeAnimator(i);
continue;
}
if (!shape.isAnimatePaused()) {
animations = shape.get('animations');
for (let j = animations.length - 1; j >= 0; j--) {
animation = animations[j];
isFinished = update(shape, animation, elapsed);
if (isFinished) {
animations.splice(j, 1);
isFinished = false;
if (animation.callback) {
animation.callback();
}
}
}
}
if (animations.length === 0) {
this.removeAnimator(i);
}
}
const autoDraw = this.canvas.get('autoDraw');
// 非自动渲染模式下,手动调用 canvas.draw() 重新渲染
if (!autoDraw) {
this.canvas.draw();
}
}
});
}
/**
* 增加动画元素
*/
addAnimator(shape) {
this.animators.push(shape);
}
/**
* 移除动画元素
*/
removeAnimator(index) {
this.animators.splice(index, 1);
}
/**
* 是否有动画在执行
*/
isAnimating() {
return !!this.animators.length;
}
/**
* 停止定时器
*/
stop() {
if (this.timer) {
this.timer.stop();
}
}
/**
* 停止时间轴上所有元素的动画,并置空动画元素列表
* @param {boolean} toEnd 是否到动画的最终状态,用来透传给动画元素的 stopAnimate 方法
*/
stopAllAnimations(toEnd = true) {
this.animators.forEach((animator) => {
animator.stopAnimate(toEnd);
});
this.animators = [];
this.canvas.draw();
}
/**
* 获取当前时间
*/
getTime() {
return this.current;
}
}
export default Timeline;