UNPKG

@visactor/vrender-animate

Version:

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

385 lines (380 loc) 25.4 kB
import { AnimateStatus } from "@visactor/vrender-core/event/constant"; import { isArray, isFunction, isValidNumber } from "@visactor/vutils"; import { getCustomType } from "./utils"; import { commitAnimationStaticAttrs } from "../custom/static-truth"; export class AnimateExecutor { static registerBuiltInAnimate(name, animate) { AnimateExecutor.builtInAnimateMap[name] = animate; } constructor(target) { this._animates = [], this._startCallbacks = [], this._endCallbacks = [], this._started = !1, this._activeCount = 0, this._activeConfigList = null, this._target = target; } get started() { return this._started; } onStart(cb) { cb ? (this._startCallbacks.push(cb), this._started && this._activeCount > 0 && cb()) : this._startCallbacks.forEach((cb => { cb(); })); } onEnd(cb) { cb ? this._endCallbacks.push(cb) : this._endCallbacks.forEach((cb => { cb(); })); } getActiveAttrKeys() { const keys = []; for (let i = 0; i < this._animates.length; i++) { const animate = this._animates[i]; if (animate.status === AnimateStatus.END) continue; const endProps = animate.getEndProps(); if (endProps) for (const key in endProps) Object.prototype.hasOwnProperty.call(endProps, key) && keys.indexOf(key) < 0 && keys.push(key); } return keys; } hasActiveAttrs() { for (let i = 0; i < this._animates.length; i++) { const animate = this._animates[i]; if (animate.status === AnimateStatus.END) continue; const endProps = animate.getEndProps(); if (endProps) for (const key in endProps) if (Object.prototype.hasOwnProperty.call(endProps, key)) return !0; } return !1; } preventAttrs(keys) { if (!(null == keys ? void 0 : keys.length)) return this.hasActiveAttrs(); for (let i = 0; i < this._animates.length; i++) { const animate = this._animates[i]; animate.status !== AnimateStatus.END && animate.preventAttrs(keys); } return this.hasActiveAttrs(); } _trackAnimation(animate) { this._animates.push(animate), this._activeCount++, 1 !== this._activeCount || this._started || (this._started = !0, this.onStart()), animate.onEnd((() => { this._activeCount--; const index = this._animates.indexOf(animate); index >= 0 && this._animates.splice(index, 1), 0 === this._activeCount && this._started && (this._started = !1, this.onEnd()); })); } parseParams(params, isTimeline, child) { var _a, _b; const totalTime = this.resolveValue(params.totalTime, child, void 0), startTime = this.resolveValue(params.startTime, child, 0), parsedParams = Object.assign({}, params); parsedParams.oneByOneDelay = 0, parsedParams.startTime = startTime, parsedParams.totalTime = totalTime; const oneByOne = this.resolveValue(params.oneByOne, child, !1); if (isTimeline) { const timeSlices = parsedParams.timeSlices; isArray(timeSlices) || (parsedParams.timeSlices = [ timeSlices ]); let sliceTime = 0; parsedParams.timeSlices = parsedParams.timeSlices.map((slice => { const delay = this.resolveValue(slice.delay, child, 0), delayAfter = this.resolveValue(slice.delayAfter, child, 0), duration = this.resolveValue(slice.duration, child, 300); return sliceTime += delay + duration + delayAfter, Object.assign(Object.assign({}, slice), { delay: delay, delayAfter: delayAfter, duration: duration }); })); let oneByOneDelay = 0; oneByOne && (oneByOneDelay = "number" == typeof oneByOne ? oneByOne : oneByOne ? sliceTime : 0), parsedParams.oneByOneDelay = oneByOneDelay; let scale = 1; if (totalTime) { const _totalTime = sliceTime + oneByOneDelay * (this._target.count - 2); scale = totalTime ? totalTime / _totalTime : 1; } parsedParams.timeSlices = parsedParams.timeSlices.map((slice => { let effects = slice.effects; return Array.isArray(effects) || (effects = [ effects ]), Object.assign(Object.assign({}, slice), { delay: slice.delay * scale, delayAfter: slice.delayAfter * scale, duration: slice.duration * scale, effects: effects.map((effect => { var _a, _b; const custom = null !== (_a = effect.custom) && void 0 !== _a ? _a : AnimateExecutor.builtInAnimateMap[null !== (_b = effect.type) && void 0 !== _b ? _b : "fromTo"], customType = getCustomType(custom); return Object.assign(Object.assign({}, effect), { custom: custom, customType: customType }); })) }); })), parsedParams.oneByOneDelay = oneByOneDelay * scale, parsedParams.startTime = startTime * scale; } else { const delay = this.resolveValue(params.delay, child, 0), delayAfter = this.resolveValue(params.delayAfter, child, 0), duration = this.resolveValue(params.duration, child, 300); let oneByOneDelay = 0; oneByOne && (oneByOneDelay = "number" == typeof oneByOne ? oneByOne : oneByOne ? delay + delayAfter + duration : 0), parsedParams.oneByOneDelay = oneByOneDelay, parsedParams.custom = null !== (_a = params.custom) && void 0 !== _a ? _a : AnimateExecutor.builtInAnimateMap[null !== (_b = params.type) && void 0 !== _b ? _b : "fromTo"]; const customType = getCustomType(parsedParams.custom); parsedParams.customType = customType; const _totalTime = delay + delayAfter + duration + oneByOneDelay * (this._target.count - 2), scale = isValidNumber(totalTime) ? totalTime / _totalTime : 1; parsedParams.delay = delay * scale, parsedParams.delayAfter = delayAfter * scale, parsedParams.duration = duration * scale, parsedParams.oneByOneDelay = oneByOneDelay * scale, parsedParams.startTime = startTime; } return parsedParams; } execute(params) { if (Array.isArray(params)) { const prevConfigList = this._activeConfigList; this._activeConfigList = params; try { params.forEach((param => this._execute(param))); } finally { this._activeConfigList = prevConfigList; } } else this._execute(params); } _execute(params) { if (params.selfOnly) return this._executeItem(params, this._target, 0, 1); const isTimeline = "timeSlices" in params; let filteredChildren; isTimeline && params.partitioner && (filteredChildren = (null != filteredChildren ? filteredChildren : this._target.getChildren()).filter((child => { var _a, _b; return params.partitioner(null === (_b = null === (_a = child.context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0], child, {}); }))), isTimeline && params.sort && (filteredChildren = null != filteredChildren ? filteredChildren : this._target.getChildren(), filteredChildren.sort(((a, b) => { var _a, _b, _c, _d; return params.sort(null === (_b = null === (_a = a.context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0], null === (_d = null === (_c = b.context) || void 0 === _c ? void 0 : _c.data) || void 0 === _d ? void 0 : _d[0], a, b, {}); }))); const cb = isTimeline ? (child, index, count) => { const parsedParams = this.parseParams(params, isTimeline, child), animate = this.executeTimelineItem(parsedParams, child, index, count); animate && this._trackAnimation(animate); } : (child, index, count) => { const parsedParams = this.parseParams(params, isTimeline, child), animate = this.executeTypeConfigItem(parsedParams, child, index, count); animate && this._trackAnimation(animate); }; filteredChildren ? filteredChildren.forEach(((child, index) => cb(child, index, filteredChildren.length))) : this._target.count <= 1 ? cb(this._target, 0, 1) : this._target.forEachChildren(((child, index) => cb(child, index, this._target.count - 1))); } executeTypeConfigItem(params, graphic, index, count) { var _a, _b, _c, _d, _e, _f, _g, _h; this.syncFinalAttrsFromContext(graphic); const {type: type = "fromTo", channel: channel, customParameters: customParameters, easing: easing = "linear", delay: delay = 0, delayAfter: delayAfter = 0, duration: duration = 300, startTime: startTime = 0, oneByOneDelay: oneByOneDelay = 0, loop: loop, bounce: bounce, priority: priority = 0, options: options, custom: custom, customType: customType, controlOptions: controlOptions} = params, animate = graphic.animate(); animate.priority = priority; const delayValue = isFunction(delay) ? delay(null === (_b = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0], graphic, {}) : delay, datum = null === (_d = null === (_c = graphic.context) || void 0 === _c ? void 0 : _c.data) || void 0 === _d ? void 0 : _d[0], indexKey = null === (_e = graphic.context) || void 0 === _e ? void 0 : _e.indexKey; datum && indexKey && (index = null !== (_f = datum[indexKey]) && void 0 !== _f ? _f : index), animate.startAt(startTime); const wait = index * oneByOneDelay + delayValue; wait > 0 && animate.wait(wait); let parsedFromProps = null, props = params.to, from = params.from; const commitAttrOutChannel = this.shouldCommitAttrOutChannel(type); props || (parsedFromProps || (parsedFromProps = this.createPropsFromChannel(channel, graphic, commitAttrOutChannel, this.getAttrOutChannelExcludedKeys(graphic))), props = parsedFromProps.props), from || (parsedFromProps || (parsedFromProps = this.createPropsFromChannel(channel, graphic, commitAttrOutChannel, this.getAttrOutChannelExcludedKeys(graphic))), from = parsedFromProps.from), parsedFromProps.attrOutChannel && graphic.setAttributes(parsedFromProps.attrOutChannel), this._handleRunAnimate(animate, custom, customType, from, props, duration, easing, customParameters, controlOptions, options, type, graphic), this.commitAnimatedUpdateDiffAttrsOnEnd(animate, graphic, type, props); let totalDelay = 0; oneByOneDelay && (totalDelay = oneByOneDelay * (count - index - 1)); const delayAfterValue = isFunction(delayAfter) ? delayAfter(null === (_h = null === (_g = graphic.context) || void 0 === _g ? void 0 : _g.data) || void 0 === _h ? void 0 : _h[0], graphic, {}) : delayAfter; return delayAfterValue > 0 && (totalDelay += delayAfterValue), totalDelay > 0 && animate.wait(totalDelay), loop && loop > 0 && animate.loop(loop), bounce && animate.bounce(!0), animate; } _handleRunAnimate(animate, custom, customType, from, props, duration, easing, customParameters, controlOptions, options, type, graphic) { var _a, _b, _c, _d, _e, _f, _g, _h; if (custom && customType) { customType = getCustomType(custom); let customParams = this.resolveValue(customParameters, graphic); "function" == typeof customParams && (customParams = customParams(null === (_b = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0], graphic, {})), customParams = Object.assign({ width: (null === (_c = graphic.stage) || void 0 === _c ? void 0 : _c.width) || 0, height: (null === (_d = graphic.stage) || void 0 === _d ? void 0 : _d.height) || 0, group: this._target.parent }, customParams); const objOptions = isFunction(options) ? options.call(null, null !== (_f = customParams && (null === (_e = customParams.data) || void 0 === _e ? void 0 : _e[0])) && void 0 !== _f ? _f : null === (_h = null === (_g = graphic.context) || void 0 === _g ? void 0 : _g.data) || void 0 === _h ? void 0 : _h[0], graphic, customParams) : options; customParams.options = objOptions, customParams.controlOptions = controlOptions, 1 === customType ? this.createCustomAnimation(animate, custom, from, props, duration, easing, customParams) : 2 === customType && this.createCustomInterpolatorAnimation(animate, custom, props, duration, easing, customParams); } else "to" === type ? animate.to(props, duration, easing) : "from" === type && animate.from(props, duration, easing); } executeTimelineItem(params, graphic, index, count) { var _a, _b, _c, _d; this.syncFinalAttrsFromContext(graphic); const {timeSlices: timeSlices, startTime: startTime = 0, loop: loop, bounce: bounce, oneByOneDelay: oneByOneDelay, priority: priority, controlOptions: controlOptions} = params, datum = null === (_b = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0], indexKey = null === (_c = graphic.context) || void 0 === _c ? void 0 : _c.indexKey; datum && indexKey && (index = null !== (_d = datum[indexKey]) && void 0 !== _d ? _d : index); const animate = graphic.animate(); animate.priority = priority, animate.startAt(startTime), animate.wait(index * oneByOneDelay), loop && loop > 0 && animate.loop(loop), bounce && animate.bounce(!0); return (Array.isArray(timeSlices) ? timeSlices : [ timeSlices ]).forEach((slice => { this.applyTimeSliceToAnimate(slice, animate, graphic, controlOptions); })), oneByOneDelay && animate.wait(oneByOneDelay * (count - index - 1)), animate; } applyTimeSliceToAnimate(slice, animate, graphic, controlOptions) { var _a, _b, _c, _d; const {effects: effects, duration: duration = 300, delay: delay = 0, delayAfter: delayAfter = 0} = slice, delayValue = isFunction(delay) ? delay(null === (_b = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0], graphic, {}) : delay, delayAfterValue = isFunction(delayAfter) ? delayAfter(null === (_d = null === (_c = graphic.context) || void 0 === _c ? void 0 : _c.data) || void 0 === _d ? void 0 : _d[0], graphic, {}) : delayAfter; delayValue > 0 && animate.wait(delayValue); (Array.isArray(effects) ? effects : [ effects ]).forEach((effect => { var _a; const {type: type = "fromTo", channel: channel, customParameters: customParameters, easing: easing = "linear", options: options} = effect; let parsedFromProps = null, props = effect.to, from = effect.from; const commitAttrOutChannel = this.shouldCommitAttrOutChannel(type); props || (parsedFromProps || (parsedFromProps = this.createPropsFromChannel(channel, graphic, commitAttrOutChannel, this.getAttrOutChannelExcludedKeys(graphic))), props = parsedFromProps.props), from || (parsedFromProps || (parsedFromProps = this.createPropsFromChannel(channel, graphic, commitAttrOutChannel, this.getAttrOutChannelExcludedKeys(graphic))), from = parsedFromProps.from), parsedFromProps.attrOutChannel && graphic.setAttributes(parsedFromProps.attrOutChannel); const custom = null !== (_a = effect.custom) && void 0 !== _a ? _a : AnimateExecutor.builtInAnimateMap[type], customType = effect.custom ? effect.customType : getCustomType(custom); this._handleRunAnimate(animate, custom, customType, from, props, duration, easing, customParameters, controlOptions, options, type, graphic); })), delayAfterValue > 0 && animate.wait(delayAfterValue); } createCustomInterpolatorAnimation(animate, interpolator, props, duration, easing, customParams) { const from = {}, to = props; Object.keys(to).forEach((key => { from[key] = animate.target.getComputedAttribute(key); })), animate.interpolateUpdateFunction = (from, to, ratio, step, target) => { interpolator(ratio, from, to, step, target, animate.target, customParams); }, animate.to(props, duration, easing); } shouldCommitAttrOutChannel(type) { return "update" !== type; } commitAnimatedUpdateDiffAttrsOnEnd(animate, graphic, type, props) { if ("update" === type || !props || !this.isUpdateDiffContext(graphic)) return; const keys = this.collectAnimatedUpdateDiffAttrKeys(graphic, props); (null == keys ? void 0 : keys.length) && animate.onEnd((() => { commitAnimationStaticAttrs(graphic, keys, animate, props); })); } isUpdateDiffContext(graphic) { const context = graphic.context; return "update" === (null == context ? void 0 : context.diffState) || "update" === (null == context ? void 0 : context.animationState); } collectAnimatedUpdateDiffAttrKeys(graphic, props) { var _a; const diffAttrs = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.diffAttrs; if (!diffAttrs) return null; let keys = null; for (const key in props) Object.prototype.hasOwnProperty.call(props, key) && Object.prototype.hasOwnProperty.call(diffAttrs, key) && void 0 !== diffAttrs[key] && (null != keys || (keys = []), keys.push(key)); return keys; } createCustomAnimation(animate, CustomAnimateConstructor, from, props, duration, easing, customParams) { const customAnimate = new CustomAnimateConstructor(from, props, duration, easing, customParams); animate.play(customAnimate); } createPropsFromChannel(channel, graphic, includeAttrOutChannel = !0, excludedAttrOutKeys) { var _a; const props = {}; let from = null; if (!channel) return { from: from, props: props, attrOutChannel: null }; const attrOutChannel = includeAttrOutChannel ? {} : null; let hasAttrs = !1; const diffAttrs = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.diffAttrs; if (Array.isArray(channel) && (channel = channel.reduce(((res, key) => (void 0 === diffAttrs[key] || (res[key] = { to: diffAttrs[key] }), res)), {})), Object.keys(channel).forEach((key => { var _a, _b, _c, _d; const config = channel[key]; void 0 !== config.to && ("function" == typeof config.to ? props[key] = config.to(null === (_b = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0], graphic, {}) : props[key] = config.to), void 0 !== config.from && (from || (from = {}), "function" == typeof config.from ? from[key] = config.from(null === (_d = null === (_c = graphic.context) || void 0 === _c ? void 0 : _c.data) || void 0 === _d ? void 0 : _d[0], graphic, {}) : from[key] = config.from); })), diffAttrs && attrOutChannel) for (const key in diffAttrs) { const value = diffAttrs[key]; if (void 0 === value) continue; props.hasOwnProperty(key) || !!(null == from ? void 0 : from.hasOwnProperty(key)) || (null == excludedAttrOutKeys ? void 0 : excludedAttrOutKeys[key]) || (attrOutChannel[key] = value, hasAttrs = !0); } return { from: from, props: props, attrOutChannel: hasAttrs ? attrOutChannel : null }; } resolveValue(value, graphic, defaultValue) { var _a, _b; return void 0 === value ? defaultValue : "function" == typeof value && graphic ? value(null === (_b = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0], graphic, {}) : value; } getAttrOutChannelExcludedKeys(graphic) { var _a; const configList = this._activeConfigList, diffAttrs = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.diffAttrs; if (!configList || configList.length <= 1 || !diffAttrs) return null; let excludedKeys = null; for (let i = 0; i < configList.length; i++) excludedKeys = this.collectAnimatedDiffKeys(configList[i], graphic, diffAttrs, excludedKeys); return excludedKeys; } collectAnimatedDiffKeys(config, graphic, diffAttrs, keys) { if ("timeSlices" in config) { const slices = Array.isArray(config.timeSlices) ? config.timeSlices : [ config.timeSlices ]; for (let i = 0; i < slices.length; i++) { const effects = Array.isArray(slices[i].effects) ? slices[i].effects : [ slices[i].effects ]; for (let j = 0; j < effects.length; j++) keys = this.collectAnimatedDiffKeysFromEffect(effects[j], graphic, diffAttrs, keys); } return keys; } return this.collectAnimatedDiffKeysFromEffect(config, graphic, diffAttrs, keys); } collectAnimatedDiffKeysFromEffect(effect, graphic, diffAttrs, keys) { var _a; if ("update" === (null !== (_a = effect.type) && void 0 !== _a ? _a : "fromTo")) { const options = this.resolveValue(effect.options, graphic, void 0), excludeChannels = null == options ? void 0 : options.excludeChannels; for (const key in diffAttrs) Object.prototype.hasOwnProperty.call(diffAttrs, key) && void 0 !== diffAttrs[key] && !this.includesChannel(excludeChannels, key) && (null != keys || (keys = {}), keys[key] = !0); return keys; } return keys = this.collectExplicitAnimatedKeys(effect.to, diffAttrs, keys), keys = this.collectExplicitAnimatedKeys(effect.from, diffAttrs, keys), this.collectChannelAnimatedKeys(effect.channel, diffAttrs, keys); } collectExplicitAnimatedKeys(attrs, diffAttrs, keys) { if (!attrs) return keys; for (const key in attrs) Object.prototype.hasOwnProperty.call(attrs, key) && Object.prototype.hasOwnProperty.call(diffAttrs, key) && void 0 !== diffAttrs[key] && (null != keys || (keys = {}), keys[key] = !0); return keys; } collectChannelAnimatedKeys(channel, diffAttrs, keys) { if (!channel) return keys; if (Array.isArray(channel)) { for (let i = 0; i < channel.length; i++) { const key = channel[i]; Object.prototype.hasOwnProperty.call(diffAttrs, key) && void 0 !== diffAttrs[key] && (null != keys || (keys = {}), keys[key] = !0); } return keys; } for (const key in channel) if (Object.prototype.hasOwnProperty.call(channel, key) && Object.prototype.hasOwnProperty.call(diffAttrs, key) && void 0 !== diffAttrs[key]) { const config = channel[key]; void 0 === config.to && void 0 === config.from || (null != keys || (keys = {}), keys[key] = !0); } return keys; } includesChannel(channels, key) { if (!(null == channels ? void 0 : channels.length)) return !1; for (let i = 0; i < channels.length; i++) if (channels[i] === key) return !0; return !1; } syncFinalAttrsFromContext(graphic) { var _a; const finalAttrs = null === (_a = graphic.context) || void 0 === _a ? void 0 : _a.finalAttrs; finalAttrs && graphic.setFinalAttributes(finalAttrs); } executeItem(params, graphic, index = 0, count = 1) { if (Array.isArray(params)) { const prevConfigList = this._activeConfigList; this._activeConfigList = params; try { return params.map((param => this._executeItem(param, graphic, index, count))).filter(Boolean); } finally { this._activeConfigList = prevConfigList; } } return [ this._executeItem(params, graphic, index, count) ].filter(Boolean); } _executeItem(params, graphic, index = 0, count = 1) { if (!graphic) return null; const isTimeline = "timeSlices" in params; let animate = null; const parsedParams = this.parseParams(params, isTimeline); return animate = isTimeline ? this.executeTimelineItem(parsedParams, graphic, index, count) : this.executeTypeConfigItem(parsedParams, graphic, index, count), animate && this._trackAnimation(animate), animate; } stop(type, callEnd = !0) { for (;this._animates.length > 0; ) { const animate = this._animates.pop(); !1 === callEnd && (animate.status = AnimateStatus.END, animate.__skipRestoreStaticAttributeOnRemove = !0), null == animate || animate.stop(type); } this._animates = [], this._activeCount = 0, this._started && (this._started = !1, callEnd && this.onEnd()); } } AnimateExecutor.builtInAnimateMap = {}; //# sourceMappingURL=animate-executor.js.map