UNPKG

@visactor/vrender-animate

Version:

This module provides a graph-based animation system for VRender.

1,558 lines (1,547 loc) 281 kB
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._