UNPKG

@visactor/vgrammar-core

Version:

VGrammar is a visual grammar library

264 lines (256 loc) 14.7 kB
import { array, isNil, isNumber } from "@visactor/vutils"; import { Animator } from "./animator"; import { invokeAnimateSpec, normalizeAnimationConfig, normalizeStateAnimationConfig } from "./config"; import { DefaultAnimationParameters, ImmediateAnimationState } from "../constants"; import { invokeFunctionType } from "../../parse/util"; import { Arranger } from "./arranger"; import { DiffState, HOOK_EVENT } from "../enums"; export class Animate { constructor(mark, config) { this.state = null, this.immediateConfigs = [], this.isEnabled = !0, this.disabledStates = [], this.animators = new Map, this.elementRecorder = new WeakMap, this.timelineCount = {}, this.mark = mark, this.configs = normalizeAnimationConfig(null != config ? config : {}); } getAnimationConfigs(animationState) { var _a; return this.isEnabled ? (null !== (_a = this.configs) && void 0 !== _a ? _a : []).filter((config => config.state === animationState)) : []; } updateConfig(config) { this.configs = normalizeAnimationConfig(null != config ? config : {}); } updateState(state) { this.state = state; } animate() { if (!this.isEnabled || !this.configs || !this.configs.length) return; const elements = this.mark.getAllElements(), parameters = this.mark.parameters(); return elements.forEach((element => { var _a; element.isReserved && element.diffState !== DiffState.exit && (element.isReserved = !1); const prevElementState = null === (_a = this.elementRecorder.get(element)) || void 0 === _a ? void 0 : _a.prevState; (this.configs.some((config => prevElementState !== element.diffState && config.state === prevElementState && config.timeline.controlOptions.stopWhenStateChange)) || element.diffState === DiffState.exit && prevElementState !== element.diffState) && this.clearElementAnimation(element, !1); })), this.configs.forEach((config => { this.animateByTimeline(config, elements, parameters); })), this.mark.cleanExitElements(), this; } runAnimationByState(animationState) { if (!this.isEnabled) return; const stateConfigs = this.configs.filter((config => config.state === animationState)), elements = this.mark.getAllElements(), parameters = this.mark.parameters(), animators = stateConfigs.reduce(((animators, config) => animators.concat(this.animateByTimeline(config, elements, parameters, !0))), []); return new Arranger(animators); } stopAnimationByState(animationState) { const animators = this.animators.get(animationState); return animators && animators.forEach((animator => animator.stop("end"))), this; } pauseAnimationByState(animationState) { const animators = this.animators.get(animationState); return animators && animators.forEach((animator => animator.pause())), this; } resumeAnimationByState(animationState) { const animators = this.animators.get(animationState); return animators && animators.forEach((animator => animator.resume())), this; } run(config) { if (!this.isEnabled) return; const parsedConfigs = normalizeStateAnimationConfig(ImmediateAnimationState, config, this.immediateConfigs.length); this.immediateConfigs = this.immediateConfigs.concat(parsedConfigs); const elements = this.mark.getAllElements(), parameters = this.mark.parameters(), animators = parsedConfigs.reduce(((animators, config) => animators.concat(this.animateByTimeline(config, elements, parameters, !0))), []); return new Arranger(animators); } stop() { return this.animators.forEach((animators => { animators.forEach((animator => animator.stop("end"))); })), this; } pause() { return this.animators.forEach((stateAnimators => stateAnimators.forEach((animator => animator.pause())))), this; } resume() { return this.animators.forEach((stateAnimators => stateAnimators.forEach((animator => animator.resume())))), this; } reverse() { return this; } restart() { return this; } record() { return this; } recordEnd() { return this; } isAnimating() { let isAnimating = !1; return this.animators.forEach((animators => { isAnimating = isAnimating || animators.some((animator => animator.isAnimating)); })), isAnimating; } isElementAnimating(element) { var _a; const stateAnimationCounts = null === (_a = this.elementRecorder.get(element)) || void 0 === _a ? void 0 : _a.count; return isNil(stateAnimationCounts) || Object.values(stateAnimationCounts).every((count => 0 === count)); } getAnimatorCount() { let count = 0; return this.animators.forEach((animators => count += animators.length)), count; } getAllAnimators() { const allAnimators = []; return this.animators.forEach((animators => { allAnimators.push(...animators); })), allAnimators; } getElementAnimators(element, animationState) { var _a; const elements = array(element); let animators = []; return animationState ? animators = null !== (_a = this.animators.get(animationState)) && void 0 !== _a ? _a : [] : this.animators.forEach((stateAnimators => { animators = animators.concat(stateAnimators); })), animators.filter((animator => elements.includes(animator.element))); } enable() { return this.isEnabled = !0, this; } disable() { return this.isEnabled = !1, this.stop(), this.animators.clear(), this; } enableAnimationState(state) { const states = array(state); return this.disabledStates = this.disabledStates.filter((state => !states.includes(state))), this; } disableAnimationState(state) { const states = array(state); return this.disabledStates = this.disabledStates.concat(states), this; } release() { this.stop(), this.animators.clear(), this.configs = null, this.animators = null, this.elementRecorder = null, this.timelineCount = null; } animateByTimeline(config, elements, parameters, forceState = !1) { var _a; const animators = [], animatedElements = elements.filter((element => { const checkExit = !(element.isReserved && element.diffState === DiffState.exit), state = this.getAnimationState(element), checkDisabled = !this.disabledStates.includes(state), checkState = forceState || state === config.state, checkPartitioner = !config.timeline.partitioner || config.timeline.partitioner(element.getDatum(), element, parameters); return checkExit && checkDisabled && checkState && checkPartitioner; })); if (animatedElements.length) { isNil(this.timelineCount[config.id]) && (this.timelineCount[config.id] = 0), config.timeline.sort && animatedElements.sort(((elementA, elementB) => config.timeline.sort(elementA.getDatum(), elementB.getDatum(), elementA, elementB, parameters))); const animationParameters = { width: this.mark.view.width(), height: this.mark.view.height(), group: null !== (_a = this.mark.group) && void 0 !== _a ? _a : null, mark: this.mark, view: this.mark.view, elementCount: animatedElements.length, elementIndex: 0 }; animatedElements.forEach((element => { var _a; (null !== (_a = this.animators.get(config.state)) && void 0 !== _a ? _a : []).filter((animator => animator.element === element && animator.animationOptions.id === config.id)).forEach((animator => { animator.stop(null, !1), this.handleAnimatorEnd(animator, !1); })); })), animatedElements.forEach(((element, index) => { animationParameters.elementIndex = index; const mergedParameters = Object.assign({ [DefaultAnimationParameters]: animationParameters }, parameters), animationUnit = this.getAnimationUnit(config.timeline, element, index, animatedElements.length, mergedParameters); animators.push(this.animateElement(config, animationUnit, element, animationParameters, mergedParameters)); })); } return animators; } animateElement(config, animationUnit, element, animationParameters, parameters) { var _a, _b; const animator = new Animator(element, animationUnit, config); if (animator.animate(animationParameters, parameters), !animator.isAnimating) return; element.diffState === DiffState.exit && (element.isReserved = !0); const isFirstAnimator = 0 === this.timelineCount[config.id]; this.timelineCount[config.id] += 1; const elementRecord = null !== (_a = this.elementRecorder.get(element)) && void 0 !== _a ? _a : { prevState: config.state, count: {} }; elementRecord.prevState = config.state, elementRecord.count[config.state] = (null !== (_b = elementRecord.count[config.state]) && void 0 !== _b ? _b : 0) + 1, this.elementRecorder.set(element, elementRecord); const stateData = this.animators.get(config.state); stateData ? stateData.push(animator) : this.animators.set(config.state, [ animator ]), animator.callback((() => { this.handleAnimatorEnd(animator); })); const animationEvent = { mark: this.mark, animationState: config.state, animationConfig: config.originConfig }; return isFirstAnimator && this.mark.emit(HOOK_EVENT.ANIMATION_START, animationEvent), this.mark.emit(HOOK_EVENT.ELEMENT_ANIMATION_START, animationEvent, element), animator; } getAnimationState(element) { const customState = invokeFunctionType(this.state, this.mark.parameters(), element.getDatum(), element); return null != customState ? customState : element.diffState; } getAnimationUnit(timeline, element, index, elementCount, parameters) { const timeSlices = [], startTime = invokeAnimateSpec(timeline.startTime, element, parameters), totalTime = invokeAnimateSpec(timeline.totalTime, element, parameters), oneByOne = invokeAnimateSpec(timeline.oneByOne, element, parameters), loop = invokeAnimateSpec(timeline.loop, element, parameters); let loopTime = 0; timeline.timeSlices.forEach((timeSlice => { var _a; const delay = invokeAnimateSpec(timeSlice.delay, element, parameters), delayAfter = invokeAnimateSpec(timeSlice.delayAfter, element, parameters), duration = null !== (_a = invokeAnimateSpec(timeSlice.duration, element, parameters)) && void 0 !== _a ? _a : totalTime / elementCount, effects = array(timeSlice.effects).map((effect => Object.assign({}, effect, { customParameters: invokeAnimateSpec(effect.customParameters, element, parameters) }))); timeSlices.push({ effects: effects, duration: duration, delay: delay, delayAfter: delayAfter }), loopTime += delay + duration + delayAfter; })); const oneByOneDelay = isNumber(oneByOne) ? oneByOne : !0 === oneByOne ? loopTime : 0; return { initialDelay: startTime, loopCount: isNumber(loop) ? loop : !0 === loop ? 1 / 0 : 1, loopDelay: oneByOneDelay * index, loopDelayAfter: oneByOneDelay * (elementCount - index - 1), loopAnimateDuration: loopTime, loopDuration: loopTime + oneByOneDelay * (elementCount - 1), totalTime: totalTime, timeSlices: timeSlices }; } clearElementAnimation(element, clearElement = !0) { this.animators.forEach((animators => { animators.forEach((animator => { animator.element === element && (animator.animationOptions.state === DiffState.exit ? animator.stop("start", !1) : animator.stop("end", !1), this.handleAnimatorEnd(animator, clearElement)); })); })), this.elementRecorder.delete(element); } clearAllElements() { const elements = this.mark.getAllElements(); elements && elements.forEach(((element, i) => { this.clearElement(element, i === elements.length - 1); })); } clearElement(element, updateMark = !0) { this.clearElementAnimation(element), element.getGraphicItem() && (element.clearGraphicAttributes(), element.diffState === DiffState.exit && (element.isReserved = !1), updateMark && this.mark.cleanExitElements()); } handleAnimatorEnd(animator, clearElement = !0) { const element = animator.element, animationOptions = animator.animationOptions, animationState = animationOptions.state, isImmediateAnimation = animationState === ImmediateAnimationState, stateAnimationCounts = this.elementRecorder.get(element).count; stateAnimationCounts[animationState] -= 1, this.animators.set(animationState, this.animators.get(animationState).filter((ani => ani !== animator))), 0 === this.animators.get(animationState).length && this.animators.delete(animationState), this.timelineCount[animationOptions.id] -= 1; const isLastAnimator = 0 === this.timelineCount[animationOptions.id], originAnimationConfig = isImmediateAnimation ? this.immediateConfigs.find((config => config.id === animationOptions.id)).originConfig : this.configs.find((config => config.id === animationOptions.id)).originConfig; isLastAnimator && (delete this.timelineCount[animationOptions.id], isImmediateAnimation && (this.immediateConfigs = this.immediateConfigs.filter((config => config.id !== animationOptions.id)))), clearElement && (0 === Object.keys(this.timelineCount).length ? this.clearAllElements() : animationState === DiffState.exit && 0 === stateAnimationCounts[DiffState.exit] && this.clearElement(element)); const animationEvent = { mark: this.mark, animationState: animationState, animationConfig: originAnimationConfig }; isLastAnimator && this.mark.emit(HOOK_EVENT.ANIMATION_END, animationEvent), this.mark.emit(HOOK_EVENT.ELEMENT_ANIMATION_END, animationEvent, element); } } //# sourceMappingURL=animate.js.map