@visactor/vrender-animate
Version:
This module provides a graph-based animation system for VRender.
1,558 lines (1,547 loc) • 281 kB
JavaScript
import { interpolateColor, interpolatePureColorArrayToStr, pointsInterpolation, Generator, ColorStore, ColorType, AnimateStatus, AnimateStepType, PerformanceRAF, STATUS, application, CustomPath2D, CurveContext, Graphic, splitRect, splitArc, splitCircle, splitLine, splitPolygon, splitArea, splitPath, AttributeUpdateType, pathToBezierCurves, applyTransformOnBezierCurves, alignBezierCurves, findBestMorphingRotation, pointInterpolation, RichText, divideCubic } from '@visactor/vrender-core';
import { pi2, isString, EventEmitter, isArray, isFunction, mixin, isNil, isValidNumber, clamp, Point, pi, isNumber, isValid, isNumberClose, PointService } from '@visactor/vutils';
class Easing {
constructor() {
}
static linear(t) {
return t;
}
static none() {
return this.linear;
}
static get(amount) {
if (amount < -1) {
amount = -1;
}
else if (amount > 1) {
amount = 1;
}
return function (t) {
if (amount === 0) {
return t;
}
if (amount < 0) {
return t * (t * -amount + 1 + amount);
}
return t * ((2 - t) * amount + (1 - amount));
};
}
static getPowIn(pow) {
return function (t) {
return Math.pow(t, pow);
};
}
static getPowOut(pow) {
return function (t) {
return 1 - Math.pow(1 - t, pow);
};
}
static getPowInOut(pow) {
return function (t) {
if ((t *= 2) < 1) {
return 0.5 * Math.pow(t, pow);
}
return 1 - 0.5 * Math.abs(Math.pow(2 - t, pow));
};
}
static getBackIn(amount) {
return function (t) {
return t * t * ((amount + 1) * t - amount);
};
}
static getBackOut(amount) {
return function (t) {
return --t * t * ((amount + 1) * t + amount) + 1;
};
}
static getBackInOut(amount) {
amount *= 1.525;
return function (t) {
if ((t *= 2) < 1) {
return 0.5 * (t * t * ((amount + 1) * t - amount));
}
return 0.5 * ((t -= 2) * t * ((amount + 1) * t + amount) + 2);
};
}
static sineIn(t) {
return 1 - Math.cos((t * Math.PI) / 2);
}
static sineOut(t) {
return Math.sin((t * Math.PI) / 2);
}
static sineInOut(t) {
return -(Math.cos(Math.PI * t) - 1) / 2;
}
static expoIn(t) {
return t === 0 ? 0 : Math.pow(2, 10 * t - 10);
}
static expoOut(t) {
return t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
}
static expoInOut(t) {
return t === 0 ? 0 : t === 1 ? 1 : t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
}
static circIn(t) {
return -(Math.sqrt(1 - t * t) - 1);
}
static circOut(t) {
return Math.sqrt(1 - --t * t);
}
static circInOut(t) {
if ((t *= 2) < 1) {
return -0.5 * (Math.sqrt(1 - t * t) - 1);
}
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
}
static bounceOut(t) {
if (t < 1 / 2.75) {
return 7.5625 * t * t;
}
else if (t < 2 / 2.75) {
return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
}
else if (t < 2.5 / 2.75) {
return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
}
return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
}
static bounceIn(t) {
return 1 - Easing.bounceOut(1 - t);
}
static bounceInOut(t) {
if (t < 0.5) {
return Easing.bounceIn(t * 2) * 0.5;
}
return Easing.bounceOut(t * 2 - 1) * 0.5 + 0.5;
}
static getElasticIn(amplitude, period) {
return function (t) {
if (t === 0 || t === 1) {
return t;
}
const s = (period / pi2) * Math.asin(1 / amplitude);
return -(amplitude * Math.pow(2, 10 * (t -= 1)) * Math.sin(((t - s) * pi2) / period));
};
}
static getElasticOut(amplitude, period) {
return function (t) {
if (t === 0 || t === 1) {
return t;
}
const s = (period / pi2) * Math.asin(1 / amplitude);
return amplitude * Math.pow(2, -10 * t) * Math.sin(((t - s) * pi2) / period) + 1;
};
}
static getElasticInOut(amplitude, period) {
return function (t) {
const s = (period / pi2) * Math.asin(1 / amplitude);
if ((t *= 2) < 1) {
return -0.5 * (amplitude * Math.pow(2, 10 * (t -= 1)) * Math.sin(((t - s) * pi2) / period));
}
return amplitude * Math.pow(2, -10 * (t -= 1)) * Math.sin(((t - s) * pi2) / period) * 0.5 + 1;
};
}
static registerFunc(name, func) {
Easing[name] = func;
}
}
Easing.quadIn = Easing.getPowIn(2);
Easing.quadOut = Easing.getPowOut(2);
Easing.quadInOut = Easing.getPowInOut(2);
Easing.cubicIn = Easing.getPowIn(3);
Easing.cubicOut = Easing.getPowOut(3);
Easing.cubicInOut = Easing.getPowInOut(3);
Easing.quartIn = Easing.getPowIn(4);
Easing.quartOut = Easing.getPowOut(4);
Easing.quartInOut = Easing.getPowInOut(4);
Easing.quintIn = Easing.getPowIn(5);
Easing.quintOut = Easing.getPowOut(5);
Easing.quintInOut = Easing.getPowInOut(5);
Easing.backIn = Easing.getBackIn(1.7);
Easing.backOut = Easing.getBackOut(1.7);
Easing.backInOut = Easing.getBackInOut(1.7);
Easing.elasticIn = Easing.getElasticIn(1, 0.3);
Easing.elasticOut = Easing.getElasticOut(1, 0.3);
Easing.elasticInOut = Easing.getElasticInOut(1, 0.3 * 1.5);
Easing.easeInOutQuad = (t) => {
if ((t /= 0.5) < 1) {
return 0.5 * Math.pow(t, 2);
}
return -0.5 * ((t -= 2) * t - 2);
};
Easing.easeOutElastic = (x) => {
const c4 = (2 * Math.PI) / 3;
return x === 0 ? 0 : x === 1 ? 1 : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1;
};
Easing.easeInOutElastic = (x) => {
const c5 = (2 * Math.PI) / 4.5;
return x === 0
? 0
: x === 1
? 1
: x < 0.5
? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2
: (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1;
};
function flicker(t, n) {
const step = 1 / n;
let flag = 1;
while (t > step) {
t -= step;
flag *= -1;
}
const v = (flag * t) / step;
return v > 0 ? v : 1 + v;
}
for (let i = 0; i < 10; i++) {
Easing[`flicker${i}`] = (t) => flicker(t, i);
}
for (let i = 2; i < 10; i++) {
Easing[`aIn${i}`] = (t) => i * t * t + (1 - i) * t;
}
function interpolateNumber(from, to, ratio) {
return from + (to - from) * ratio;
}
class InterpolateUpdateStore {
constructor() {
this.opacity = (key, from, to, ratio, step, target) => {
target.attribute.opacity = interpolateNumber(from, to, ratio);
};
this.baseOpacity = (key, from, to, ratio, step, target) => {
target.attribute.baseOpacity = interpolateNumber(from, to, ratio);
};
this.fillOpacity = (key, from, to, ratio, step, target) => {
target.attribute.fillOpacity = interpolateNumber(from, to, ratio);
};
this.strokeOpacity = (key, from, to, ratio, step, target) => {
target.attribute.strokeOpacity = interpolateNumber(from, to, ratio);
};
this.zIndex = (key, from, to, ratio, step, target) => {
target.attribute.zIndex = interpolateNumber(from, to, ratio);
};
this.backgroundOpacity = (key, from, to, ratio, step, target) => {
target.attribute.backgroundOpacity = interpolateNumber(from, to, ratio);
};
this.shadowOffsetX = (key, from, to, ratio, step, target) => {
target.attribute.shadowOffsetX = interpolateNumber(from, to, ratio);
};
this.shadowOffsetY = (key, from, to, ratio, step, target) => {
target.attribute.shadowOffsetY = interpolateNumber(from, to, ratio);
};
this.shadowBlur = (key, from, to, ratio, step, target) => {
target.attribute.shadowBlur = interpolateNumber(from, to, ratio);
};
this.fill = (key, from, to, ratio, step, target) => {
target.attribute.fill = interpolateColor(from, to, ratio, false);
};
this.fillPure = (key, from, to, ratio, step, target) => {
target.attribute.fill = step.fromParsedProps.fill
? interpolatePureColorArrayToStr(step.fromParsedProps.fill, step.toParsedProps.fill, ratio)
: step.toParsedProps.fill;
};
this.stroke = (key, from, to, ratio, step, target) => {
target.attribute.stroke = interpolateColor(from, to, ratio, false);
};
this.strokePure = (key, from, to, ratio, step, target) => {
target.attribute.stroke = step.fromParsedProps.stroke
? interpolatePureColorArrayToStr(step.fromParsedProps.stroke, step.toParsedProps.stroke, ratio)
: step.toParsedProps.stroke;
};
this.width = (key, from, to, ratio, step, target) => {
target.attribute.width = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
};
this.height = (key, from, to, ratio, step, target) => {
target.attribute.height = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
};
this.x = (key, from, to, ratio, step, target) => {
target.attribute.x = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
target.addUpdatePositionTag();
};
this.y = (key, from, to, ratio, step, target) => {
target.attribute.y = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
target.addUpdatePositionTag();
};
this.dx = (key, from, to, ratio, step, target) => {
target.attribute.dx = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
target.addUpdatePositionTag();
};
this.dy = (key, from, to, ratio, step, target) => {
target.attribute.dy = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
target.addUpdatePositionTag();
};
this.angle = (key, from, to, ratio, step, target) => {
target.attribute.angle = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
target.addUpdatePositionTag();
};
this.scaleX = (key, from, to, ratio, step, target) => {
target.attribute.scaleX = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
target.addUpdatePositionTag();
};
this.scaleY = (key, from, to, ratio, step, target) => {
target.attribute.scaleY = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
target.addUpdatePositionTag();
};
this.lineWidth = (key, from, to, ratio, step, target) => {
target.attribute.lineWidth = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
};
this.startAngle = (key, from, to, ratio, step, target) => {
target.attribute.startAngle = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
};
this.endAngle = (key, from, to, ratio, step, target) => {
target.attribute.endAngle = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
};
this.radius = (key, from, to, ratio, step, target) => {
target.attribute.radius = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
};
this.outerRadius = (key, from, to, ratio, step, target) => {
target.attribute.outerRadius = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
};
this.innerRadius = (key, from, to, ratio, step, target) => {
target.attribute.innerRadius = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
};
this.size = (key, from, to, ratio, step, target) => {
target.attribute.size = interpolateNumber(from, to, ratio);
target.addUpdateBoundTag();
};
this.points = (key, from, to, ratio, step, target) => {
target.attribute.points = pointsInterpolation(from, to, ratio);
target.addUpdateBoundTag();
};
}
}
const interpolateUpdateStore = new InterpolateUpdateStore();
function commonInterpolateUpdate(key, from, to, ratio, step, target) {
if (Number.isFinite(to) && Number.isFinite(from)) {
target.attribute[key] = from + (to - from) * ratio;
return true;
}
else if (Array.isArray(to) && Array.isArray(from) && to.length === from.length) {
const nextList = [];
let valid = true;
for (let i = 0; i < to.length; i++) {
const v = from[i];
const val = v + (to[i] - v) * ratio;
if (!Number.isFinite(val)) {
valid = false;
break;
}
nextList.push(val);
}
if (valid) {
target.attribute[key] = nextList;
}
return true;
}
return false;
}
function noop() {
}
class Step {
constructor(type, props, duration, easing) {
var _a;
this._startTime = 0;
this._hasFirstRun = false;
this._syncAttributeUpdate = () => {
this.target.setAttributes(this.target.attribute);
};
this.type = type;
this.props = props;
this.duration = duration;
if (easing) {
this.easing = typeof easing === 'function' ? easing : (_a = Easing[easing]) !== null && _a !== void 0 ? _a : Easing.linear;
}
else {
this.easing = Easing.linear;
}
if (type === 'wait') {
this.onUpdate = noop;
}
this.id = Generator.GenAutoIncrementId();
this.syncAttributeUpdate = noop;
}
bind(target, animate) {
this.target = target;
this.animate = animate;
this.onBind();
this.syncAttributeUpdate();
}
append(step) {
this.next = step;
step.prev = this;
step.setStartTime(this.getStartTime() + this.duration, false);
}
updateDownstreamStartTimes() {
let currentStep = this.next;
let currentStartTime = this._startTime + this.duration;
while (currentStep) {
currentStep.setStartTime(currentStartTime, false);
currentStartTime += currentStep.duration;
currentStep = currentStep.next;
}
this.animate.updateDuration();
}
getLastProps() {
if (this.prev) {
return this.prev.props || {};
}
return this.animate.getStartProps();
}
setDuration(duration, updateDownstream = true) {
this.duration = duration;
if (updateDownstream) {
this.updateDownstreamStartTimes();
}
}
getDuration() {
return this.duration;
}
determineInterpolateUpdateFunction() {
if (!this.props) {
return;
}
const funcs = [];
this.propKeys.forEach(key => {
if (key === 'fill' || key === 'stroke') {
const from = this.fromProps[key];
const to = this.props[key];
if (isString(from) && isString(to)) {
const fromArray = ColorStore.Get(from, ColorType.Color255);
const toArray = ColorStore.Get(to, ColorType.Color255);
if (!this.fromParsedProps) {
this.fromParsedProps = {};
}
if (!this.toParsedProps) {
this.toParsedProps = {};
}
this.fromParsedProps[key] = fromArray;
this.toParsedProps[key] = toArray;
funcs.push(interpolateUpdateStore[key === 'fill' ? 'fillPure' : 'strokePure']);
}
else if (interpolateUpdateStore[key]) {
funcs.push(interpolateUpdateStore[key]);
}
else {
funcs.push(commonInterpolateUpdate);
}
}
else if (interpolateUpdateStore[key]) {
funcs.push(interpolateUpdateStore[key]);
}
else {
funcs.push(commonInterpolateUpdate);
}
});
this.interpolateUpdateFunctions = funcs;
}
setStartTime(time, updateDownstream = true) {
this._startTime = time;
if (updateDownstream) {
this.updateDownstreamStartTimes();
}
}
getStartTime() {
return this._startTime;
}
onBind() {
if (this.target.type === 'glyph') {
this.syncAttributeUpdate = this._syncAttributeUpdate;
}
}
onFirstRun() {
}
onStart() {
if (!this._hasFirstRun) {
this._hasFirstRun = true;
this.fromProps = this.getLastProps();
const startProps = this.animate.getStartProps();
this.propKeys &&
this.propKeys.forEach(key => {
var _a;
this.fromProps[key] = (_a = this.fromProps[key]) !== null && _a !== void 0 ? _a : startProps[key];
});
this.determineInterpolateUpdateFunction();
this.tryPreventConflict();
this.trySyncStartProps();
this.onFirstRun();
}
}
tryPreventConflict() {
const animate = this.animate;
const target = this.target;
target.animates.forEach((a) => {
if (a === animate || a.priority > animate.priority || a.priority === Infinity) {
return;
}
const fromProps = a.getStartProps();
this.propKeys.forEach(key => {
if (fromProps[key] != null) {
a.preventAttr(key);
}
});
});
}
deleteSelfAttr(key) {
var _a;
delete this.props[key];
this.fromProps && delete this.fromProps[key];
const index = this.propKeys.indexOf(key);
if (index !== -1) {
this.propKeys.splice(index, 1);
(_a = this.interpolateUpdateFunctions) === null || _a === void 0 ? void 0 : _a.splice(index, 1);
}
}
trySyncStartProps() {
this.propKeys.forEach(key => {
this.fromProps[key] = this.animate.target.getComputedAttribute(key);
});
}
update(end, ratio, out) {
this.onStart();
if (!this.props || !this.propKeys) {
return;
}
const easedRatio = this.easing(ratio);
this.animate.interpolateUpdateFunction
? this.animate.interpolateUpdateFunction(this.fromProps, this.props, easedRatio, this, this.target)
: this.interpolateUpdateFunctions.forEach((func, index) => {
if (!this.animate.validAttr(this.propKeys[index])) {
return;
}
const key = this.propKeys[index];
const fromValue = this.fromProps[key];
const toValue = this.props[key];
func(key, fromValue, toValue, easedRatio, this, this.target);
});
this.onUpdate(end, easedRatio, out);
this.syncAttributeUpdate();
}
onUpdate(end, ratio, out) {
}
onEnd(cb) {
this.target.setAttributes(this.props);
if (cb) {
this._endCb = cb;
}
else if (this._endCb) {
this._endCb(this.animate, this);
}
}
getEndProps() {
return this.props;
}
getFromProps() {
return this.fromProps;
}
getMergedEndProps() {
return this.getEndProps();
}
stop() {
}
}
class WaitStep extends Step {
constructor(type, props, duration, easing) {
super(type, props, duration, easing);
}
onStart() {
super.onStart();
}
onFirstRun() {
const fromProps = this.getFromProps();
this.target.setAttributes(fromProps);
}
update(end, ratio, out) {
this.onStart();
}
determineInterpolateUpdateFunction() {
return;
}
}
class DefaultTimeline extends EventEmitter {
get animateCount() {
return this._animateCount;
}
constructor() {
super();
this.head = null;
this.tail = null;
this.animateMap = new Map();
this._animateCount = 0;
this._playSpeed = 1;
this._totalDuration = 0;
this._startTime = 0;
this._currentTime = 0;
this._animationEndFlag = true;
this.id = Generator.GenAutoIncrementId();
this.paused = false;
}
isRunning() {
return !this.paused && this._animateCount > 0;
}
forEachAccessAnimate(cb) {
let current = this.head;
let index = 0;
while (current) {
const next = current.next;
cb(current.animate, index);
index++;
current = next;
}
}
addAnimate(animate) {
const newNode = {
animate,
next: null,
prev: null
};
if (!this.head) {
this.head = newNode;
this.tail = newNode;
}
else {
if (this.tail) {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
}
this.animateMap.set(animate, newNode);
this._animateCount++;
this._totalDuration = Math.max(this._totalDuration, animate.getStartTime() + animate.getDuration());
}
pause() {
this.paused = true;
}
resume() {
this.paused = false;
}
tick(delta) {
if (this.paused) {
return;
}
if (this._animationEndFlag) {
this._animationEndFlag = false;
this.emit('animationStart');
}
const scaledDelta = delta * this._playSpeed;
this._currentTime += scaledDelta;
this.forEachAccessAnimate((animate, i) => {
if (animate.status === AnimateStatus.END) {
this.removeAnimate(animate, true);
}
else if (animate.status === AnimateStatus.RUNNING || animate.status === AnimateStatus.INITIAL) {
animate.advance(scaledDelta);
}
});
if (this._animateCount === 0) {
this._animationEndFlag = true;
this.emit('animationEnd');
}
}
clear() {
this.forEachAccessAnimate(animate => {
animate.release();
});
this.head = null;
this.tail = null;
this.animateMap.clear();
this._animateCount = 0;
this._totalDuration = 0;
}
removeAnimate(animate, release = true) {
const node = this.animateMap.get(animate);
if (!node) {
return;
}
if (release) {
animate._onRemove && animate._onRemove.forEach(cb => cb());
animate.release();
}
if (node.prev) {
node.prev.next = node.next;
}
else {
this.head = node.next;
}
if (node.next) {
node.next.prev = node.prev;
}
else {
this.tail = node.prev;
}
this.animateMap.delete(animate);
this._animateCount--;
if (animate.getStartTime() + animate.getDuration() >= this._totalDuration) {
this.recalculateTotalDuration();
}
return;
}
recalculateTotalDuration() {
this._totalDuration = 0;
this.forEachAccessAnimate(animate => {
this._totalDuration = Math.max(this._totalDuration, animate.getStartTime() + animate.getDuration());
});
}
getTotalDuration() {
return this._totalDuration;
}
getPlaySpeed() {
return this._playSpeed;
}
setPlaySpeed(speed) {
this._playSpeed = speed;
}
getPlayState() {
if (this.paused) {
return 'paused';
}
if (this.animateCount === 0) {
return 'stopped';
}
return 'playing';
}
setStartTime(time) {
this._startTime = time;
}
getStartTime() {
return this._startTime;
}
getCurrentTime() {
return this._currentTime;
}
setCurrentTime(time) {
this._currentTime = time;
}
}
const defaultTimeline = new DefaultTimeline();
defaultTimeline.isGlobal = true;
class Animate {
constructor(id = Generator.GenAutoIncrementId(), timeline = defaultTimeline, slience) {
this.id = id;
this.status = AnimateStatus.INITIAL;
this._timeline = timeline;
timeline.addAnimate(this);
this.slience = slience;
this._startTime = 0;
this._duration = 0;
this._totalDuration = 0;
this._loopCount = 0;
this._currentLoop = 0;
this._bounce = false;
this._firstStep = null;
this._lastStep = null;
this._startProps = {};
this._endProps = {};
this._preventAttrs = new Set();
this.currentTime = 0;
this.interpolateUpdateFunction = null;
this.priority = 0;
}
getStartProps() {
return this._startProps;
}
getEndProps() {
return this._endProps;
}
setTimeline(timeline) {
this._timeline = timeline;
}
getTimeline() {
return this._timeline;
}
get timeline() {
return this._timeline;
}
bind(target) {
this.target = target;
if (!this.target.animates) {
this.target.animates = new Map();
}
this.target.animates.set(this.id, this);
this.onRemove(() => {
this.stop();
this.target.animates.delete(this.id);
});
if (this.target.onAnimateBind && !this.slience) {
this.target.onAnimateBind(this);
}
if (!this.target.animationAttribute) {
this.target.animationAttribute = {};
}
return this;
}
to(props, duration = 300, easing = 'linear') {
const step = new Step(AnimateStepType.to, props, duration, easing);
step.bind(this.target, this);
this.updateStepAfterAppend(step);
return this;
}
wait(delay) {
const step = new WaitStep(AnimateStepType.wait, {}, delay, 'linear');
step.bind(this.target, this);
this.updateStepAfterAppend(step);
return this;
}
updateStepAfterAppend(step) {
if (!this._firstStep) {
this._firstStep = step;
this._lastStep = step;
}
else {
this._lastStep.append(step);
this._lastStep = step;
}
this.parseStepProps(step);
this.updateDuration();
}
parseStepProps(step) {
if (!this._lastStep) {
return;
}
step.propKeys = step.propKeys || Object.keys(step.props);
Object.keys(this._endProps).forEach(key => {
var _a;
step.props[key] = (_a = step.props[key]) !== null && _a !== void 0 ? _a : this._endProps[key];
});
step.propKeys.forEach(key => {
this._endProps[key] = step.props[key];
});
}
reSyncProps() {
if (!this._lastStep) {
return;
}
this._endProps = {};
let currentStep = this._firstStep;
while (currentStep) {
Object.keys(this._endProps).forEach(key => {
var _a;
currentStep.props[key] = (_a = currentStep.props[key]) !== null && _a !== void 0 ? _a : this._endProps[key];
});
currentStep.propKeys.forEach(key => {
this._endProps[key] = currentStep.props[key];
});
currentStep = currentStep.next;
}
}
from(props, duration = 300, easing = 'linear') {
const step = new Step(AnimateStepType.from, props, duration, easing);
if (!this._firstStep) {
this._firstStep = step;
this._lastStep = step;
}
else {
this._lastStep.append(step);
this._lastStep = step;
}
this.updateDuration();
return this;
}
play(customAnimate) {
customAnimate.bind(this.target, this);
this.updateStepAfterAppend(customAnimate);
return this;
}
pause() {
if (this.status === AnimateStatus.RUNNING) {
this.status = AnimateStatus.PAUSED;
}
}
resume() {
if (this.status === AnimateStatus.PAUSED) {
this.status = AnimateStatus.RUNNING;
}
}
onStart(cb) {
var _a;
if (cb) {
if (!this._onStart) {
this._onStart = [];
}
this._onStart.push(cb);
}
else {
(_a = this._onStart) === null || _a === void 0 ? void 0 : _a.forEach(cb => cb());
Object.keys(this._endProps).forEach(key => {
this._startProps[key] = this.target.getComputedAttribute(key);
});
}
}
onEnd(cb) {
var _a;
if (cb) {
if (!this._onEnd) {
this._onEnd = [];
}
this._onEnd.push(cb);
}
else {
(_a = this._onEnd) === null || _a === void 0 ? void 0 : _a.forEach(cb => cb());
}
}
onFrame(cb) {
if (cb) {
if (!this._onFrame) {
this._onFrame = [];
}
this._onFrame.push(cb);
}
}
onRemove(cb) {
var _a;
if (cb) {
if (!this._onRemove) {
this._onRemove = [];
}
this._onRemove.push(cb);
}
else {
(_a = this._onRemove) === null || _a === void 0 ? void 0 : _a.forEach(cb => cb());
}
}
preventAttr(key) {
this._preventAttrs.add(key);
delete this._startProps[key];
delete this._endProps[key];
let step = this._firstStep;
while (step) {
step.deleteSelfAttr(key);
step = step.next;
}
}
preventAttrs(keys) {
keys.forEach(key => this._preventAttrs.add(key));
}
validAttr(key) {
return !this._preventAttrs.has(key);
}
runCb(cb) {
var _a;
(_a = this._lastStep) === null || _a === void 0 ? void 0 : _a.onEnd(cb);
return this;
}
startAt(t) {
this._startTime = t;
return this;
}
customInterpolate(key, ratio, from, to, target, ret) {
return false;
}
getFromValue() {
return this._startProps;
}
getToValue() {
return this._endProps;
}
stop(type) {
let step = this._firstStep;
while (step) {
step.stop();
step = step.next;
}
if (this.status !== AnimateStatus.END) {
this.onEnd();
}
this.status = AnimateStatus.END;
if (!this.target) {
return;
}
if (type === 'start') {
this.target.setAttributes(this._startProps);
}
else if (type === 'end') {
this.target.setAttributes(this._endProps);
}
else if (type) {
this.target.setAttributes(type);
}
}
release() {
this.status = AnimateStatus.END;
if (this._onRemove) {
this._onRemove.forEach(cb => cb());
}
this._onStart = [];
this._onFrame = [];
this._onEnd = [];
this._onRemove = [];
}
getDuration() {
return this._duration;
}
getStartTime() {
return this._startTime;
}
afterAll(list) {
if (!list || list.length === 0) {
return this;
}
let maxEndTime = 0;
list.forEach(animate => {
const endTime = animate.getStartTime() + animate.getTotalDuration();
maxEndTime = Math.max(maxEndTime, endTime);
});
return this.startAt(maxEndTime);
}
after(animate) {
if (!animate) {
return this;
}
const endTime = animate.getStartTime() + animate.getTotalDuration();
return this.startAt(endTime);
}
parallel(animate) {
if (!animate) {
return this;
}
this.startAt(animate.getStartTime());
return this;
}
loop(n) {
if (n === true) {
n = Infinity;
}
else if (n === false) {
n = 0;
}
this._loopCount = n;
this.updateDuration();
return this;
}
bounce(b) {
this._bounce = b;
return this;
}
advance(delta) {
var _a, _b, _c;
if (this.status === AnimateStatus.END) {
console.warn('aaa 动画已经结束,不能推进');
return;
}
const nextTime = this.currentTime + delta;
if (nextTime < this._startTime) {
this.currentTime = nextTime;
return;
}
if (nextTime >= this._startTime + this._totalDuration) {
(_a = this._lastStep) === null || _a === void 0 ? void 0 : _a.onUpdate(true, 1, {});
(_b = this._lastStep) === null || _b === void 0 ? void 0 : _b.onEnd();
this.onEnd();
this.status = AnimateStatus.END;
return;
}
this.status = AnimateStatus.RUNNING;
if (this.currentTime <= this._startTime) {
this.onStart();
}
this.currentTime = nextTime;
let cycleTime = nextTime - this._startTime;
let newLoop = false;
let bounceTime = false;
if (this._loopCount > 0) {
cycleTime = (nextTime - this._startTime) % this._duration;
const currentLoop = Math.floor((nextTime - this._startTime) / this._duration);
newLoop = currentLoop > this._currentLoop;
this._currentLoop = currentLoop;
bounceTime = this._bounce && currentLoop % 2 === 1;
if (bounceTime) {
cycleTime = this._duration - cycleTime;
}
}
if (newLoop && !bounceTime) {
this.target.setAttributes(this._startProps);
}
let targetStep = null;
if (this._lastStep === this._firstStep) {
targetStep = this._firstStep;
}
else {
let currentStep = this._firstStep;
while (currentStep) {
const stepStartTime = currentStep.getStartTime();
const stepDuration = currentStep.getDuration();
const stepEndTime = stepStartTime + stepDuration;
if (cycleTime >= stepStartTime && cycleTime <= stepEndTime) {
targetStep = currentStep;
break;
}
currentStep = currentStep.next;
}
}
if (!targetStep) {
return;
}
if (targetStep !== this.lastRunStep) {
(_c = this.lastRunStep) === null || _c === void 0 ? void 0 : _c.onEnd();
}
this.lastRunStep = targetStep;
const stepStartTime = targetStep.getStartTime();
const stepDuration = targetStep.getDuration();
const ratio = (cycleTime - stepStartTime) / stepDuration;
const isEnd = ratio >= 1;
targetStep.update(isEnd, ratio, {});
if (isEnd) {
targetStep.onEnd();
this.lastRunStep = null;
}
}
updateDuration() {
if (!this._lastStep) {
this._duration = 0;
return;
}
this._duration = this._lastStep.getStartTime() + this._lastStep.getDuration();
this._totalDuration = this._duration * (this._loopCount + 1);
}
getTotalDuration() {
return this._totalDuration;
}
getLoop() {
return this._loopCount;
}
}
const performanceRAF = new PerformanceRAF();
class RAFTickHandler {
constructor() {
this.released = false;
}
tick(interval, cb) {
performanceRAF.addAnimationFrameCb(() => {
if (this.released) {
return;
}
return cb(this);
});
}
release() {
this.released = true;
}
getTime() {
return Date.now();
}
}
class DefaultTicker extends EventEmitter {
constructor(stage) {
super();
this.timelines = [];
this.frameTimeHistory = [];
this.handleTick = (handler, params) => {
const { once = false } = params !== null && params !== void 0 ? params : {};
if (this.ifCanStop()) {
this.stop();
return false;
}
const currentTime = handler.getTime();
this._lastTickTime = currentTime;
if (this.lastFrameTime < 0) {
this.lastFrameTime = currentTime - this.interval + this.timeOffset;
this.frameTimeHistory.push(this.lastFrameTime);
}
const delta = currentTime - this.lastFrameTime;
const skip = this.checkSkip(delta);
if (!skip) {
this._handlerTick(delta);
this.lastFrameTime = currentTime;
this.frameTimeHistory.push(this.lastFrameTime);
}
if (!once) {
handler.tick(this.interval, this.handleTick);
}
return !skip;
};
this._handlerTick = (delta) => {
if (this.status !== STATUS.RUNNING) {
return;
}
this.tickCounts++;
this.timelines.forEach(timeline => {
timeline.tick(delta);
});
this.emit('tick', delta);
};
this.init();
this.lastFrameTime = -1;
this.tickCounts = 0;
this.stage = stage;
this.autoStop = true;
this.interval = 16;
this.computeTimeOffsetAndJitter();
}
bindStage(stage) {
this.stage = stage;
}
computeTimeOffsetAndJitter() {
this.timeOffset = Math.floor(Math.random() * this.interval);
this._jitter = Math.min(Math.max(this.interval * 0.2, 6), this.interval * 0.7);
}
init() {
this.interval = 16;
this.status = STATUS.INITIAL;
application.global.hooks.onSetEnv.tap('graph-ticker', () => {
this.initHandler(false);
});
if (application.global.env) {
this.initHandler(false);
}
}
addTimeline(timeline) {
this.timelines.push(timeline);
}
remTimeline(timeline) {
this.timelines = this.timelines.filter(t => t !== timeline);
}
getTimelines() {
return this.timelines;
}
initHandler(force = false) {
this.setupTickHandler(force);
}
setupTickHandler(force = false) {
if (!force && this.tickerHandler) {
return true;
}
const handler = new RAFTickHandler();
if (this.tickerHandler) {
this.tickerHandler.release();
}
this.tickerHandler = handler;
return true;
}
setInterval(interval) {
this.interval = interval;
this.computeTimeOffsetAndJitter();
}
getInterval() {
return this.interval;
}
setFPS(fps) {
this.setInterval(Math.floor(1000 / fps));
}
getFPS() {
return 1000 / this.interval;
}
tick(interval) {
this.tickerHandler.tick(interval, (handler) => {
return this.handleTick(handler, { once: true });
});
}
tickTo(t) {
if (!this.tickerHandler.tickTo) {
return;
}
this.tickerHandler.tickTo(t, (handler) => {
this.handleTick(handler, { once: true });
});
}
pause() {
if (this.status === STATUS.INITIAL) {
return false;
}
this.status = STATUS.PAUSE;
return true;
}
resume() {
if (this.status === STATUS.INITIAL) {
return false;
}
this.status = STATUS.RUNNING;
return true;
}
ifCanStop() {
if (this.autoStop) {
if (!this.timelines.length) {
return true;
}
if (this.timelines.every(timeline => !timeline.isRunning())) {
return true;
}
}
return false;
}
start(force = false) {
if (this.status === STATUS.RUNNING) {
return false;
}
if (!this.tickerHandler) {
return false;
}
if (!force) {
if (this.status === STATUS.PAUSE) {
return false;
}
if (this.ifCanStop()) {
return false;
}
}
this.status = STATUS.RUNNING;
this.tickerHandler.tick(0, this.handleTick);
return true;
}
stop() {
this.status = STATUS.INITIAL;
this.setupTickHandler(true);
this.lastFrameTime = -1;
}
trySyncTickStatus() {
if (this.status === STATUS.INITIAL && this.timelines.some(timeline => timeline.isRunning())) {
this.start();
}
else if (this.status === STATUS.RUNNING && this.timelines.every(timeline => !timeline.isRunning())) {
this.stop();
}
}
release() {
var _a;
this.stop();
this.timelines = [];
(_a = this.tickerHandler) === null || _a === void 0 ? void 0 : _a.release();
this.tickerHandler = null;
this.lastFrameTime = -1;
}
checkSkip(delta) {
var _a, _b, _c;
if (((_c = (_b = (_a = this.stage) === null || _a === void 0 ? void 0 : _a.params) === null || _b === void 0 ? void 0 : _b.optimize) === null || _c === void 0 ? void 0 : _c.tickRenderMode) === 'performance') {
return false;
}
const skip = delta < this.interval + (Math.random() - 0.5) * 2 * this._jitter;
return skip;
}
}
class ManualTickHandler {
constructor() {
this.released = false;
this.currentTime = -1;
}
tick(interval, cb) {
if (this.currentTime < 0) {
this.currentTime = 0;
}
this.currentTime += interval;
cb(this);
}
release() {
this.released = true;
}
getTime() {
return this.currentTime;
}
tickTo(time, cb) {
if (this.currentTime < 0) {
this.currentTime = 0;
}
const interval = time - this.currentTime;
this.tick(interval, cb);
}
}
class ManualTicker extends DefaultTicker {
constructor(stage) {
super(stage);
this.lastFrameTime = 0;
this.status = STATUS.RUNNING;
}
setupTickHandler() {
const handler = new ManualTickHandler();
if (this.tickerHandler) {
this.tickerHandler.release();
}
this.tickerHandler = handler;
return true;
}
checkSkip(delta) {
return false;
}
getTime() {
return this.tickerHandler.getTime();
}
tickAt(time) {
this.tickTo(time);
}
start(force = false) {
if (this.status === STATUS.RUNNING) {
return false;
}
if (!this.tickerHandler) {
return false;
}
if (!force) {
if (this.status === STATUS.PAUSE) {
return false;
}
if (this.ifCanStop()) {
return false;
}
}
this.status = STATUS.RUNNING;
return true;
}
}
function generatorPathEasingFunc(path) {
const customPath = new CustomPath2D();
customPath.setCtx(new CurveContext(customPath));
customPath.fromString(path, 0, 0, 1, 1);
return (x) => {
return customPath.getYAt(x);
};
}
class AnimationTransitionRegistry {
constructor() {
this.transitions = new Map();
this.registerDefaultTransitions();
}
static getInstance() {
if (!AnimationTransitionRegistry.instance) {
AnimationTransitionRegistry.instance = new AnimationTransitionRegistry();
}
return AnimationTransitionRegistry.instance;
}
registerDefaultTransitions() {
this.registerTransition('appear', '*', () => ({
allowTransition: true,
stopOriginalTransition: false
}));
this.registerTransition('appear', 'appear', () => ({
allowTransition: false,
stopOriginalTransition: false
}));
this.registerTransition('appear', 'disappear', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('appear', 'exit', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('normal', '*', () => ({
allowTransition: true,
stopOriginalTransition: false
}));
this.registerTransition('normal', 'normal', () => ({
allowTransition: false,
stopOriginalTransition: false
}));
this.registerTransition('normal', 'disappear', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('normal', 'exit', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('exit', '*', () => ({
allowTransition: false,
stopOriginalTransition: false
}));
this.registerTransition('exit', 'disappear', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('exit', 'enter', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('exit', 'exit', () => ({
allowTransition: false,
stopOriginalTransition: false
}));
this.registerTransition('enter', '*', () => ({
allowTransition: true,
stopOriginalTransition: false
}));
this.registerTransition('enter', 'enter', () => ({
allowTransition: false,
stopOriginalTransition: false
}));
this.registerTransition('enter', 'disappear', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('enter', 'exit', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('disappear', '*', () => ({
allowTransition: false,
stopOriginalTransition: false
}));
this.registerTransition('disappear', 'appear', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('update', '*', () => ({
allowTransition: true,
stopOriginalTransition: false
}));
this.registerTransition('update', 'disappear', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('update', 'exit', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('state', '*', () => ({
allowTransition: true,
stopOriginalTransition: false
}));
this.registerTransition('state', 'disappear', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
this.registerTransition('state', 'exit', () => ({
allowTransition: true,
stopOriginalTransition: true
}));
}
isTransitionAllowed(fromState, toState, graphic) {
var _a, _b, _c, _d;
let func = (_a = this.transitions.get(fromState)) === null || _a === void 0 ? void 0 : _a.get(toState);
if (func) {
return func(graphic, fromState);
}
func = (_b = this.transitions.get(fromState)) === null || _b === void 0 ? void 0 : _b.get('*');
if (func) {
return func(graphic, fromState);
}
func = (_c = this.transitions.get('*')) === null || _c === void 0 ? void 0 : _c.get(toState);
if (func) {
return func(graphic, fromState);
}
func = (_d = this.transitions.get('*')) === null || _d === void 0 ? void 0 : _d.get('*');
if (func) {
return func(graphic, fromState);
}
return {
allowTransition: true,
stopOriginalTransition: true
};
}
registerTransition(fromState, toState, transition) {
let fromStateMap = this.transitions.get(fromState);
if (!fromStateMap) {
fromStateMap = new Map();
this.transitions.set(fromState, fromStateMap);
}
fromStateMap.set(toState, transition);
}
}
const transitionRegistry = AnimationTransitionRegistry.getInstance();
class AnimateExecutor {
static registerBuiltInAnimate(name, animate) {
AnimateExecutor.builtInAnimateMap[name] = animate;
}
constructor(target) {
this._