UNPKG

animejs

Version:

JavaScript animation engine

1,527 lines (1,323 loc) 366 kB
/** * Anime.js - ESM bundle * @version v4.3.6 * @license MIT * @copyright 2026 - Julian Garnier */ // Global types /** * @typedef {Object} DefaultsParams * @property {Number|String} [id] * @property {PercentageKeyframes|DurationKeyframes} [keyframes] * @property {EasingParam} [playbackEase] * @property {Number} [playbackRate] * @property {Number} [frameRate] * @property {Number|Boolean} [loop] * @property {Boolean} [reversed] * @property {Boolean} [alternate] * @property {Boolean} [persist] * @property {Boolean|ScrollObserver} [autoplay] * @property {Number|FunctionValue} [duration] * @property {Number|FunctionValue} [delay] * @property {Number} [loopDelay] * @property {EasingParam|FunctionValue} [ease] * @property {'none'|'replace'|'blend'|compositionTypes} [composition] * @property {(v: any) => any} [modifier] * @property {Callback<Tickable>} [onBegin] * @property {Callback<Tickable>} [onBeforeUpdate] * @property {Callback<Tickable>} [onUpdate] * @property {Callback<Tickable>} [onLoop] * @property {Callback<Tickable>} [onPause] * @property {Callback<Tickable>} [onComplete] * @property {Callback<Renderable>} [onRender] */ /** @typedef {JSAnimation|Timeline} Renderable */ /** @typedef {Timer|Renderable} Tickable */ /** @typedef {Timer&JSAnimation&Timeline} CallbackArgument */ /** @typedef {Animatable|Tickable|WAAPIAnimation|Draggable|ScrollObserver|TextSplitter|Scope|AutoLayout} Revertible */ // Stagger types /** * @template T * @callback StaggerFunction * @param {Target} [target] * @param {Number} [index] * @param {Number} [length] * @param {Timeline} [tl] * @return {T} */ /** * @typedef {Object} StaggerParams * @property {Number|String} [start] * @property {Number|'first'|'center'|'last'|'random'} [from] * @property {Boolean} [reversed] * @property {Array.<Number>} [grid] * @property {('x'|'y')} [axis] * @property {String|((target: Target, i: Number, length: Number) => Number)} [use] * @property {Number} [total] * @property {EasingParam} [ease] * @property {TweenModifier} [modifier] */ // Targets types /** @typedef {HTMLElement|SVGElement} DOMTarget */ /** @typedef {Record<String, any>} JSTarget */ /** @typedef {DOMTarget|JSTarget} Target */ /** @typedef {Target|NodeList|String} TargetSelector */ /** @typedef {DOMTarget|NodeList|String} DOMTargetSelector */ /** @typedef {Array.<DOMTargetSelector>|DOMTargetSelector} DOMTargetsParam */ /** @typedef {Array.<DOMTarget>} DOMTargetsArray */ /** @typedef {Array.<JSTarget>|JSTarget} JSTargetsParam */ /** @typedef {Array.<JSTarget>} JSTargetsArray */ /** @typedef {Array.<TargetSelector>|TargetSelector} TargetsParam */ /** @typedef {Array.<Target>} TargetsArray */ // Eases types /** * @callback EasingFunction * @param {Number} time * @return {Number} */ /** * @typedef {('linear'|'none'|'in'|'out'|'inOut'|'inQuad'|'outQuad'|'inOutQuad'|'inCubic'|'outCubic'|'inOutCubic'|'inQuart'|'outQuart'|'inOutQuart'|'inQuint'|'outQuint'|'inOutQuint'|'inSine'|'outSine'|'inOutSine'|'inCirc'|'outCirc'|'inOutCirc'|'inExpo'|'outExpo'|'inOutExpo'|'inBounce'|'outBounce'|'inOutBounce'|'inBack'|'outBack'|'inOutBack'|'inElastic'|'outElastic'|'inOutElastic'|'out(p = 1.675)'|'inOut(p = 1.675)'|'inBack(overshoot = 1.7)'|'outBack(overshoot = 1.7)'|'inOutBack(overshoot = 1.7)'|'inElastic(amplitude = 1, period = .3)'|'outElastic(amplitude = 1, period = .3)'|'inOutElastic(amplitude = 1, period = .3)')} EaseStringParamNames */ /** * @typedef {('ease'|'ease-in'|'ease-out'|'ease-in-out'|'linear(0, 0.25, 1)'|'steps'|'steps(6, start)'|'step-start'|'step-end'|'cubic-bezier(0.42, 0, 1, 1)') } WAAPIEaseStringParamNames */ /** * @callback PowerEasing * @param {Number|String} [power=1.675] * @return {EasingFunction} */ /** * @callback BackEasing * @param {Number|String} [overshoot=1.7] * @return {EasingFunction} */ /** * @callback ElasticEasing * @param {Number|String} [amplitude=1] * @param {Number|String} [period=.3] * @return {EasingFunction} */ /** @typedef {PowerEasing|BackEasing|ElasticEasing} EasingFunctionWithParams */ // A hack to get both ease names suggestions AND allow any strings // https://github.com/microsoft/TypeScript/issues/29729#issuecomment-460346421 /** @typedef {(String & {})|EaseStringParamNames|EasingFunction|Spring} EasingParam */ /** @typedef {(String & {})|EaseStringParamNames|WAAPIEaseStringParamNames|EasingFunction|Spring} WAAPIEasingParam */ // Spring types /** * @typedef {Object} SpringParams * @property {Number} [mass=1] - Mass, default 1 * @property {Number} [stiffness=100] - Stiffness, default 100 * @property {Number} [damping=10] - Damping, default 10 * @property {Number} [velocity=0] - Initial velocity, default 0 * @property {Number} [bounce=0] - Initial bounce, default 0 * @property {Number} [duration=0] - The perceived duration, default 0 * @property {Callback<JSAnimation>} [onComplete] - Callback function called when the spring currentTime hits the perceived duration */ // Callback types /** * @template T * @callback Callback * @param {T} self - Returns itself * @param {PointerEvent} [e] * @return {*} */ /** * @template {object} T * @typedef {Object} TickableCallbacks * @property {Callback<T>} [onBegin] * @property {Callback<T>} [onBeforeUpdate] * @property {Callback<T>} [onUpdate] * @property {Callback<T>} [onLoop] * @property {Callback<T>} [onPause] * @property {Callback<T>} [onComplete] */ /** * @template {object} T * @typedef {Object} RenderableCallbacks * @property {Callback<T>} [onRender] */ // Timer types /** * @typedef {Object} TimerOptions * @property {Number|String} [id] * @property {TweenParamValue} [duration] * @property {TweenParamValue} [delay] * @property {Number} [loopDelay] * @property {Boolean} [reversed] * @property {Boolean} [alternate] * @property {Boolean|Number} [loop] * @property {Boolean|ScrollObserver} [autoplay] * @property {Number} [frameRate] * @property {Number} [playbackRate] */ /** * @typedef {TimerOptions & TickableCallbacks<Timer>} TimerParams */ // Tween types /** * @callback FunctionValue * @param {Target} target - The animated target * @param {Number} index - The target index * @param {Number} length - The total number of animated targets * @return {Number|String|TweenObjectValue|EasingParam|Array.<Number|String|TweenObjectValue>} */ /** * @callback TweenModifier * @param {Number} value - The animated value * @return {Number|String} */ /** @typedef {[Number, Number, Number, Number]} ColorArray */ /** * @typedef {Object} Tween * @property {Number} id * @property {JSAnimation} parent * @property {String} property * @property {Target} target * @property {String|Number} _value * @property {Function|null} _toFunc * @property {Function|null} _fromFunc * @property {EasingFunction} _ease * @property {Array.<Number>} _fromNumbers * @property {Array.<Number>} _toNumbers * @property {Array.<String>} _strings * @property {Number} _fromNumber * @property {Number} _toNumber * @property {Array.<Number>} _numbers * @property {Number} _number * @property {String} _unit * @property {TweenModifier} _modifier * @property {Number} _currentTime * @property {Number} _delay * @property {Number} _updateDuration * @property {Number} _startTime * @property {Number} _changeDuration * @property {Number} _absoluteStartTime * @property {tweenTypes} _tweenType * @property {valueTypes} _valueType * @property {Number} _composition * @property {Number} _isOverlapped * @property {Number} _isOverridden * @property {Number} _renderTransforms * @property {String} _inlineValue * @property {Tween} _prevRep * @property {Tween} _nextRep * @property {Tween} _prevAdd * @property {Tween} _nextAdd * @property {Tween} _prev * @property {Tween} _next */ /** * @typedef TweenDecomposedValue * @property {Number} t - Type * @property {Number} n - Single number value * @property {String} u - Value unit * @property {String} o - Value operator * @property {Array.<Number>} d - Array of Numbers (in case of complex value type) * @property {Array.<String>} s - Strings (in case of complex value type) */ /** @typedef {{_head: null|Tween, _tail: null|Tween}} TweenPropertySiblings */ /** @typedef {Record<String, TweenPropertySiblings>} TweenLookups */ /** @typedef {WeakMap.<Target, TweenLookups>} TweenReplaceLookups */ /** @typedef {Map.<Target, TweenLookups>} TweenAdditiveLookups */ // JSAnimation types /** * @typedef {Number|String|FunctionValue|EasingParam} TweenParamValue */ /** * @typedef {TweenParamValue|[TweenParamValue, TweenParamValue]} TweenPropValue */ /** * @typedef {(String & {})|'none'|'replace'|'blend'|compositionTypes} TweenComposition */ /** * @typedef {Object} TweenParamsOptions * @property {TweenParamValue} [duration] * @property {TweenParamValue} [delay] * @property {EasingParam|FunctionValue} [ease] * @property {TweenModifier} [modifier] * @property {TweenComposition} [composition] */ /** * @typedef {Object} TweenValues * @property {TweenParamValue} [from] * @property {TweenPropValue} [to] * @property {TweenPropValue} [fromTo] */ /** * @typedef {TweenParamsOptions & TweenValues} TweenKeyValue */ /** * @typedef {Array.<TweenKeyValue|TweenPropValue>} ArraySyntaxValue */ /** * @typedef {TweenParamValue|ArraySyntaxValue|TweenKeyValue} TweenOptions */ /** * @typedef {Partial<{to: TweenParamValue|Array.<TweenParamValue>; from: TweenParamValue|Array.<TweenParamValue>; fromTo: TweenParamValue|Array.<TweenParamValue>;}>} TweenObjectValue */ /** * @typedef {Object} PercentageKeyframeOptions * @property {EasingParam} [ease] */ /** * @typedef {Record<String, TweenParamValue>} PercentageKeyframeParams */ /** * @typedef {Record<String, PercentageKeyframeParams & PercentageKeyframeOptions>} PercentageKeyframes */ /** * @typedef {Array<Record<String, TweenOptions | TweenModifier | boolean> & TweenParamsOptions>} DurationKeyframes */ /** * @typedef {Object} AnimationOptions * @property {PercentageKeyframes|DurationKeyframes} [keyframes] * @property {EasingParam} [playbackEase] */ // TODO: Currently setting TweenModifier to the intersected Record<> makes the FunctionValue type target param any if only one parameter is set /** * @typedef {Record<String, TweenOptions | Callback<JSAnimation> | TweenModifier | boolean | PercentageKeyframes | DurationKeyframes | ScrollObserver> & TimerOptions & AnimationOptions & TweenParamsOptions & TickableCallbacks<JSAnimation> & RenderableCallbacks<JSAnimation>} AnimationParams */ // Timeline types /** * Accepts:<br> * - `Number` - Absolute position in milliseconds (e.g., `500` places element at exactly 500ms)<br> * - `'+=Number'` - Addition: Position element X ms after the last element (e.g., `'+=100'`)<br> * - `'-=Number'` - Subtraction: Position element X ms before the last element's end (e.g., `'-=100'`)<br> * - `'*=Number'` - Multiplier: Position element at a fraction of the total duration (e.g., `'*=.5'` for halfway)<br> * - `'<'` - Previous end: Position element at the end position of the previous element<br> * - `'<<'` - Previous start: Position element at the start position of the previous element<br> * - `'<<+=Number'` - Combined: Position element relative to previous element's start (e.g., `'<<+=250'`)<br> * - `'label'` - Label: Position element at a named label position (e.g., `'My Label'`) * * @typedef {Number|`+=${Number}`|`-=${Number}`|`*=${Number}`|'<'|'<<'|`<<+=${Number}`|`<<-=${Number}`|String} TimelinePosition */ /** * Accepts:<br> * - `Number` - Absolute position in milliseconds (e.g., `500` places animation at exactly 500ms)<br> * - `'+=Number'` - Addition: Position animation X ms after the last animation (e.g., `'+=100'`)<br> * - `'-=Number'` - Subtraction: Position animation X ms before the last animation's end (e.g., `'-=100'`)<br> * - `'*=Number'` - Multiplier: Position animation at a fraction of the total duration (e.g., `'*=.5'` for halfway)<br> * - `'<'` - Previous end: Position animation at the end position of the previous animation<br> * - `'<<'` - Previous start: Position animation at the start position of the previous animation<br> * - `'<<+=Number'` - Combined: Position animation relative to previous animation's start (e.g., `'<<+=250'`)<br> * - `'label'` - Label: Position animation at a named label position (e.g., `'My Label'`)<br> * - `stagger(String|Nummber)` - Stagger multi-elements animation positions (e.g., 10, 20, 30...) * * @typedef {TimelinePosition | StaggerFunction<Number|String>} TimelineAnimationPosition */ /** * @typedef {Object} TimelineOptions * @property {DefaultsParams} [defaults] * @property {EasingParam} [playbackEase] * @property {Boolean} [composition] */ /** * @typedef {TimerOptions & TimelineOptions & TickableCallbacks<Timeline> & RenderableCallbacks<Timeline>} TimelineParams */ // WAAPIAnimation types /** * @typedef {String|Number|Array<String>|Array<Number>} WAAPITweenValue */ /** * @callback WAAPIFunctionValue * @param {DOMTarget} target - The animated target * @param {Number} index - The target index * @param {Number} length - The total number of animated targets * @return {WAAPITweenValue|WAAPIEasingParam} */ /** * @typedef {WAAPITweenValue|WAAPIFunctionValue|Array<String|Number|WAAPIFunctionValue>} WAAPIKeyframeValue */ /** * @typedef {Object} WAAPITweenOptions * @property {WAAPIKeyframeValue} [to] * @property {WAAPIKeyframeValue} [from] * @property {Number|WAAPIFunctionValue} [duration] * @property {Number|WAAPIFunctionValue} [delay] * @property {WAAPIEasingParam} [ease] * @property {CompositeOperation} [composition] */ /** * @typedef {Object} WAAPIAnimationOptions * @property {Number|Boolean} [loop] * @property {Boolean} [Reversed] * @property {Boolean} [Alternate] * @property {Boolean|ScrollObserver} [autoplay] * @property {Number} [playbackRate] * @property {Number|WAAPIFunctionValue} [duration] * @property {Number|WAAPIFunctionValue} [delay] * @property {WAAPIEasingParam|WAAPIFunctionValue} [ease] * @property {CompositeOperation} [composition] * @property {Boolean} [persist] * @property {Callback<WAAPIAnimation>} [onComplete] */ /** * @typedef {Record<String, WAAPIKeyframeValue | WAAPIAnimationOptions | Boolean | ScrollObserver | Callback<WAAPIAnimation> | WAAPIEasingParam | WAAPITweenOptions> & WAAPIAnimationOptions} WAAPIAnimationParams */ // Animatable types /** * @callback AnimatablePropertySetter * @param {Number|Array.<Number>} to * @param {Number} [duration] * @param {EasingParam} [ease] * @return {AnimatableObject} */ /** * @callback AnimatablePropertyGetter * @return {Number|Array.<Number>} */ /** * @typedef {AnimatablePropertySetter & AnimatablePropertyGetter} AnimatableProperty */ /** * @typedef {Animatable & Record<String, AnimatableProperty>} AnimatableObject */ /** * @typedef {Object} AnimatablePropertyParamsOptions * @property {String} [unit] * @property {TweenParamValue} [duration] * @property {EasingParam} [ease] * @property {TweenModifier} [modifier] * @property {TweenComposition} [composition] */ /** * @typedef {Record<String, TweenParamValue | EasingParam | TweenModifier | TweenComposition | AnimatablePropertyParamsOptions> & AnimatablePropertyParamsOptions} AnimatableParams */ // Scope types /** * @typedef {Object} ReactRef * @property {HTMLElement|SVGElement|null} [current] */ /** * @typedef {Object} AngularRef * @property {HTMLElement|SVGElement} [nativeElement] */ /** * @typedef {Object} ScopeParams * @property {DOMTargetSelector|ReactRef|AngularRef} [root] * @property {DefaultsParams} [defaults] * @property {Record<String, String>} [mediaQueries] */ /** * @template T * @callback ScopedCallback * @param {Scope} scope * @return {T} */ /** * @callback ScopeCleanupCallback * @param {Scope} [scope] */ /** * @callback ScopeConstructorCallback * @param {Scope} [scope] * @return {ScopeCleanupCallback|void} */ /** * @callback ScopeMethod * @param {...*} args * @return {ScopeCleanupCallback|void} */ // Scroll types /** * @typedef {String|Number} ScrollThresholdValue */ /** * @typedef {Object} ScrollThresholdParam * @property {ScrollThresholdValue} [target] * @property {ScrollThresholdValue} [container] */ /** * @callback ScrollObserverAxisCallback * @param {ScrollObserver} self * @return {'x'|'y'} */ /** * @callback ScrollThresholdCallback * @param {ScrollObserver} self * @return {ScrollThresholdValue|ScrollThresholdParam} */ /** * @typedef {Object} ScrollObserverParams * @property {Number|String} [id] * @property {Boolean|Number|String|EasingParam} [sync] * @property {TargetsParam} [container] * @property {TargetsParam} [target] * @property {'x'|'y'|ScrollObserverAxisCallback|((observer: ScrollObserver) => 'x'|'y'|ScrollObserverAxisCallback)} [axis] * @property {ScrollThresholdValue|ScrollThresholdParam|ScrollThresholdCallback|((observer: ScrollObserver) => ScrollThresholdValue|ScrollThresholdParam|ScrollThresholdCallback)} [enter] * @property {ScrollThresholdValue|ScrollThresholdParam|ScrollThresholdCallback|((observer: ScrollObserver) => ScrollThresholdValue|ScrollThresholdParam|ScrollThresholdCallback)} [leave] * @property {Boolean|((observer: ScrollObserver) => Boolean)} [repeat] * @property {Boolean} [debug] * @property {Callback<ScrollObserver>} [onEnter] * @property {Callback<ScrollObserver>} [onLeave] * @property {Callback<ScrollObserver>} [onEnterForward] * @property {Callback<ScrollObserver>} [onLeaveForward] * @property {Callback<ScrollObserver>} [onEnterBackward] * @property {Callback<ScrollObserver>} [onLeaveBackward] * @property {Callback<ScrollObserver>} [onUpdate] * @property {Callback<ScrollObserver>} [onResize] * @property {Callback<ScrollObserver>} [onSyncComplete] */ // Draggable types /** * @typedef {Object} DraggableAxisParam * @property {String} [mapTo] * @property {TweenModifier} [modifier] * @property {TweenComposition} [composition] * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap] */ /** * @typedef {Object} DraggableCursorParams * @property {String} [onHover] * @property {String} [onGrab] */ /** * @typedef {Object} DraggableDragThresholdParams * @property {Number} [mouse] * @property {Number} [touch] */ /** * @typedef {Object} DraggableParams * @property {DOMTargetSelector} [trigger] * @property {DOMTargetSelector|Array<Number>|((draggable: Draggable) => DOMTargetSelector|Array<Number>)} [container] * @property {Boolean|DraggableAxisParam} [x] * @property {Boolean|DraggableAxisParam} [y] * @property {TweenModifier} [modifier] * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap] * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [containerPadding] * @property {Number|((draggable: Draggable) => Number)} [containerFriction] * @property {Number|((draggable: Draggable) => Number)} [releaseContainerFriction] * @property {Number|((draggable: Draggable) => Number)} [dragSpeed] * @property {Number|DraggableDragThresholdParams|((draggable: Draggable) => Number|DraggableDragThresholdParams)} [dragThreshold] * @property {Number|((draggable: Draggable) => Number)} [scrollSpeed] * @property {Number|((draggable: Draggable) => Number)} [scrollThreshold] * @property {Number|((draggable: Draggable) => Number)} [minVelocity] * @property {Number|((draggable: Draggable) => Number)} [maxVelocity] * @property {Number|((draggable: Draggable) => Number)} [velocityMultiplier] * @property {Number} [releaseMass] * @property {Number} [releaseStiffness] * @property {Number} [releaseDamping] * @property {Boolean} [releaseDamping] * @property {EasingParam} [releaseEase] * @property {Boolean|DraggableCursorParams|((draggable: Draggable) => Boolean|DraggableCursorParams)} [cursor] * @property {Callback<Draggable>} [onGrab] * @property {Callback<Draggable>} [onDrag] * @property {Callback<Draggable>} [onRelease] * @property {Callback<Draggable>} [onUpdate] * @property {Callback<Draggable>} [onSettle] * @property {Callback<Draggable>} [onSnap] * @property {Callback<Draggable>} [onResize] * @property {Callback<Draggable>} [onAfterResize] */ // Text types /** * @typedef {Object} SplitTemplateParams * @property {false|String} [class] * @property {Boolean|'hidden'|'clip'|'visible'|'scroll'|'auto'} [wrap] * @property {Boolean|'top'|'right'|'bottom'|'left'|'center'} [clone] */ /** * @typedef {Boolean|String} SplitValue */ /** * @callback SplitFunctionValue * @param {Node|HTMLElement} [value] * @return String */ /** * @typedef {Object} TextSplitterParams * @property {SplitValue|SplitTemplateParams|SplitFunctionValue} [lines] * @property {SplitValue|SplitTemplateParams|SplitFunctionValue} [words] * @property {SplitValue|SplitTemplateParams|SplitFunctionValue} [chars] * @property {Boolean} [accessible] * @property {Boolean} [includeSpaces] * @property {Boolean} [debug] */ // SVG types /** * @typedef {SVGGeometryElement & { * setAttribute(name: 'draw', value: `${number} ${number}`): void; * draw: `${number} ${number}`; * }} DrawableSVGGeometry */ // Environments // TODO: Do we need to check if we're running inside a worker ? const isBrowser = typeof window !== 'undefined'; /** @typedef {Window & {AnimeJS: Array} & {AnimeJSDevTools: any}|null} AnimeJSWindow /** @type {AnimeJSWindow} */ const win = isBrowser ? /** @type {AnimeJSWindow} */(/** @type {unknown} */(window)) : null; /** @type {Document|null} */ const doc = isBrowser ? document : null; // Enums /** @enum {Number} */ const tweenTypes = { OBJECT: 0, ATTRIBUTE: 1, CSS: 2, TRANSFORM: 3, CSS_VAR: 4, }; /** @enum {Number} */ const valueTypes = { NUMBER: 0, UNIT: 1, COLOR: 2, COMPLEX: 3, }; /** @enum {Number} */ const tickModes = { NONE: 0, AUTO: 1, FORCE: 2, }; /** @enum {Number} */ const compositionTypes = { replace: 0, none: 1, blend: 2, }; // Cache symbols const isRegisteredTargetSymbol = Symbol(); const isDomSymbol = Symbol(); const isSvgSymbol = Symbol(); const transformsSymbol = Symbol(); const morphPointsSymbol = Symbol(); const proxyTargetSymbol = Symbol(); // Numbers const minValue = 1e-11; const maxValue = 1e12; const K = 1e3; const maxFps = 240; // Strings const emptyString = ''; const cssVarPrefix = 'var('; const shortTransforms = /*#__PURE__*/ (() => { const map = new Map(); map.set('x', 'translateX'); map.set('y', 'translateY'); map.set('z', 'translateZ'); return map; })(); const validTransforms = [ 'translateX', 'translateY', 'translateZ', 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'matrix', 'matrix3d', 'perspective', ]; const transformsFragmentStrings = /*#__PURE__*/ validTransforms.reduce((a, v) => ({...a, [v]: v + '('}), {}); // Functions /** @return {void} */ const noop = () => {}; // Regex const validRgbHslRgx = /\)\s*[-.\d]/; const hexTestRgx = /(^#([\da-f]{3}){1,2}$)|(^#([\da-f]{4}){1,2}$)/i; const rgbExecRgx = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i; const rgbaExecRgx = /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(-?\d+|-?\d*.\d+)\s*\)/i; const hslExecRgx = /hsl\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*\)/i; const hslaExecRgx = /hsla\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)\s*\)/i; // export const digitWithExponentRgx = /[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?/g; const digitWithExponentRgx = /[-+]?\d*\.?\d+(?:e[-+]?\d)?/gi; // export const unitsExecRgx = /^([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)+([a-z]+|%)$/i; const unitsExecRgx = /^([-+]?\d*\.?\d+(?:e[-+]?\d+)?)([a-z]+|%)$/i; const lowerCaseRgx = /([a-z])([A-Z])/g; const transformsExecRgx = /(\w+)(\([^)]+\)+)/g; // Match inline transforms with cacl() values, returns the value wrapped in () const relativeValuesExecRgx = /(\*=|\+=|-=)/; const cssVariableMatchRgx = /var\(\s*(--[\w-]+)(?:\s*,\s*([^)]+))?\s*\)/; /** @type {DefaultsParams} */ const defaults = { id: null, keyframes: null, playbackEase: null, playbackRate: 1, frameRate: maxFps, loop: 0, reversed: false, alternate: false, autoplay: true, persist: false, duration: K, delay: 0, loopDelay: 0, ease: 'out(2)', composition: compositionTypes.replace, modifier: v => v, onBegin: noop, onBeforeUpdate: noop, onUpdate: noop, onLoop: noop, onPause: noop, onComplete: noop, onRender: noop, }; const scope = { /** @type {Scope} */ current: null, /** @type {Document|DOMTarget} */ root: doc, }; const globals = { /** @type {DefaultsParams} */ defaults, /** @type {Number} */ precision: 4, /** @type {Number} equals 1 in ms mode, 0.001 in s mode */ timeScale: 1, /** @type {Number} */ tickThreshold: 200, }; const devTools = isBrowser && win.AnimeJSDevTools; const globalVersions = { version: '4.3.6', engine: null }; if (isBrowser) { if (!win.AnimeJS) win.AnimeJS = []; win.AnimeJS.push(globalVersions); } // Strings /** * @param {String} str * @return {String} */ const toLowerCase = str => str.replace(lowerCaseRgx, '$1-$2').toLowerCase(); /** * Prioritize this method instead of regex when possible * @param {String} str * @param {String} sub * @return {Boolean} */ const stringStartsWith = (str, sub) => str.indexOf(sub) === 0; // Note: Date.now is used instead of performance.now since it is precise enough for timings calculations, performs slightly faster and works in Node.js environement. const now = Date.now; // Types checkers const isArr = Array.isArray; /**@param {any} a @return {a is Record<String, any>} */ const isObj = a => a && a.constructor === Object; /**@param {any} a @return {a is Number} */ const isNum = a => typeof a === 'number' && !isNaN(a); /**@param {any} a @return {a is String} */ const isStr = a => typeof a === 'string'; /**@param {any} a @return {a is Function} */ const isFnc = a => typeof a === 'function'; /**@param {any} a @return {a is undefined} */ const isUnd = a => typeof a === 'undefined'; /**@param {any} a @return {a is null | undefined} */ const isNil = a => isUnd(a) || a === null; /**@param {any} a @return {a is SVGElement} */ const isSvg = a => isBrowser && a instanceof SVGElement; /**@param {any} a @return {Boolean} */ const isHex = a => hexTestRgx.test(a); /**@param {any} a @return {Boolean} */ const isRgb = a => stringStartsWith(a, 'rgb'); /**@param {any} a @return {Boolean} */ const isHsl = a => stringStartsWith(a, 'hsl'); /**@param {any} a @return {Boolean} */ // Make sure boxShadow syntax like 'rgb(255, 0, 0) 0px 0px 6px 0px' is not a valid color type const isCol = a => isHex(a) || ((isRgb(a) || isHsl(a)) && (a[a.length - 1] === ')' || !validRgbHslRgx.test(a))); /**@param {any} a @return {Boolean} */ const isKey = a => !globals.defaults.hasOwnProperty(a); // SVG // Consider the following as CSS animation // CSS opacity animation has better default values (opacity: 1 instead of 0)) // rotate is more commonly intended to be used as a transform const svgCssReservedProperties = ['opacity', 'rotate', 'overflow', 'color']; /** * @param {Target} el * @param {String} propertyName * @return {Boolean} */ const isValidSVGAttribute = (el, propertyName) => { if (svgCssReservedProperties.includes(propertyName)) return false; if (el.getAttribute(propertyName) || propertyName in el) { if (propertyName === 'scale') { // Scale const elParentNode = /** @type {SVGGeometryElement} */(/** @type {DOMTarget} */(el).parentNode); // Only consider scale as a valid SVG attribute on filter element return elParentNode && elParentNode.tagName === 'filter'; } return true; } }; // Number /** * @param {Number|String} str * @return {Number} */ const parseNumber = str => isStr(str) ? parseFloat(/** @type {String} */(str)) : /** @type {Number} */(str); // Math const pow = Math.pow; const sqrt = Math.sqrt; const sin = Math.sin; const cos = Math.cos; const abs = Math.abs; const exp = Math.exp; const ceil = Math.ceil; const floor = Math.floor; const asin = Math.asin; const max = Math.max; const atan2 = Math.atan2; const PI = Math.PI; const _round = Math.round; /** * Clamps a value between min and max bounds * * @param {Number} v - Value to clamp * @param {Number} min - Minimum boundary * @param {Number} max - Maximum boundary * @return {Number} */ const clamp$1 = (v, min, max) => v < min ? min : v > max ? max : v; const powCache = {}; /** * Rounds a number to specified decimal places * * @param {Number} v - Value to round * @param {Number} decimalLength - Number of decimal places * @return {Number} */ const round$1 = (v, decimalLength) => { if (decimalLength < 0) return v; if (!decimalLength) return _round(v); let p = powCache[decimalLength]; if (!p) p = powCache[decimalLength] = 10 ** decimalLength; return _round(v * p) / p; }; /** * Snaps a value to nearest increment or array value * * @param {Number} v - Value to snap * @param {Number|Array<Number>} increment - Step size or array of snap points * @return {Number} */ const snap$1 = (v, increment) => isArr(increment) ? increment.reduce((closest, cv) => (abs(cv - v) < abs(closest - v) ? cv : closest)) : increment ? _round(v / increment) * increment : v; /** * Linear interpolation between two values * * @param {Number} start - Starting value * @param {Number} end - Ending value * @param {Number} factor - Interpolation factor in the range [0, 1] * @return {Number} The interpolated value */ const lerp$1 = (start, end, factor) => start + (end - start) * factor; /** * Replaces infinity with maximum safe value * * @param {Number} v - Value to check * @return {Number} */ const clampInfinity = v => v === Infinity ? maxValue : v === -Infinity ? -maxValue : v; /** * Normalizes time value with minimum threshold * * @param {Number} v - Time value to normalize * @return {Number} */ const normalizeTime = v => v <= minValue ? minValue : clampInfinity(round$1(v, 11)); // Arrays /** * @template T * @param {T[]} a * @return {T[]} */ const cloneArray = a => isArr(a) ? [ ...a ] : a; // Objects /** * @template T * @template U * @param {T} o1 * @param {U} o2 * @return {T & U} */ const mergeObjects = (o1, o2) => { const merged = /** @type {T & U} */({ ...o1 }); for (let p in o2) { const o1p = /** @type {T & U} */(o1)[p]; merged[p] = isUnd(o1p) ? /** @type {T & U} */(o2)[p] : o1p; } return merged; }; // Linked lists /** * @param {Object} parent * @param {Function} callback * @param {Boolean} [reverse] * @param {String} [prevProp] * @param {String} [nextProp] * @return {void} */ const forEachChildren = (parent, callback, reverse, prevProp = '_prev', nextProp = '_next') => { let next = parent._head; let adjustedNextProp = nextProp; if (reverse) { next = parent._tail; adjustedNextProp = prevProp; } while (next) { const currentNext = next[adjustedNextProp]; callback(next); next = currentNext; } }; /** * @param {Object} parent * @param {Object} child * @param {String} [prevProp] * @param {String} [nextProp] * @return {void} */ const removeChild = (parent, child, prevProp = '_prev', nextProp = '_next') => { const prev = child[prevProp]; const next = child[nextProp]; prev ? prev[nextProp] = next : parent._head = next; next ? next[prevProp] = prev : parent._tail = prev; child[prevProp] = null; child[nextProp] = null; }; /** * @param {Object} parent * @param {Object} child * @param {Function} [sortMethod] * @param {String} prevProp * @param {String} nextProp * @return {void} */ const addChild = (parent, child, sortMethod, prevProp = '_prev', nextProp = '_next') => { let prev = parent._tail; while (prev && sortMethod && sortMethod(prev, child)) prev = prev[prevProp]; const next = prev ? prev[nextProp] : parent._head; prev ? prev[nextProp] = child : parent._head = child; next ? next[prevProp] = child : parent._tail = child; child[prevProp] = prev; child[nextProp] = next; }; /** * @param {DOMTarget} target * @param {String} propName * @param {Object} animationInlineStyles * @return {String} */ const parseInlineTransforms = (target, propName, animationInlineStyles) => { const inlineTransforms = target.style.transform; let inlinedStylesPropertyValue; if (inlineTransforms) { const cachedTransforms = target[transformsSymbol]; let t; while (t = transformsExecRgx.exec(inlineTransforms)) { const inlinePropertyName = t[1]; // const inlinePropertyValue = t[2]; const inlinePropertyValue = t[2].slice(1, -1); cachedTransforms[inlinePropertyName] = inlinePropertyValue; if (inlinePropertyName === propName) { inlinedStylesPropertyValue = inlinePropertyValue; // Store the new parsed inline styles if animationInlineStyles is provided if (animationInlineStyles) { animationInlineStyles[propName] = inlinePropertyValue; } } } } return inlineTransforms && !isUnd(inlinedStylesPropertyValue) ? inlinedStylesPropertyValue : stringStartsWith(propName, 'scale') ? '1' : stringStartsWith(propName, 'rotate') || stringStartsWith(propName, 'skew') ? '0deg' : '0px'; }; /** * RGB / RGBA Color value string -> RGBA values array * @param {String} rgbValue * @return {ColorArray} */ const rgbToRgba = rgbValue => { const rgba = rgbExecRgx.exec(rgbValue) || rgbaExecRgx.exec(rgbValue); const a = !isUnd(rgba[4]) ? +rgba[4] : 1; return [ +rgba[1], +rgba[2], +rgba[3], a ] }; /** * HEX3 / HEX3A / HEX6 / HEX6A Color value string -> RGBA values array * @param {String} hexValue * @return {ColorArray} */ const hexToRgba = hexValue => { const hexLength = hexValue.length; const isShort = hexLength === 4 || hexLength === 5; return [ +('0x' + hexValue[1] + hexValue[isShort ? 1 : 2]), +('0x' + hexValue[isShort ? 2 : 3] + hexValue[isShort ? 2 : 4]), +('0x' + hexValue[isShort ? 3 : 5] + hexValue[isShort ? 3 : 6]), ((hexLength === 5 || hexLength === 9) ? +(+('0x' + hexValue[isShort ? 4 : 7] + hexValue[isShort ? 4 : 8]) / 255).toFixed(3) : 1) ] }; /** * @param {Number} p * @param {Number} q * @param {Number} t * @return {Number} */ const hue2rgb = (p, q, t) => { if (t < 0) t += 1; if (t > 1) t -= 1; return t < 1 / 6 ? p + (q - p) * 6 * t : t < 1 / 2 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p; }; /** * HSL / HSLA Color value string -> RGBA values array * @param {String} hslValue * @return {ColorArray} */ const hslToRgba = hslValue => { const hsla = hslExecRgx.exec(hslValue) || hslaExecRgx.exec(hslValue); const h = +hsla[1] / 360; const s = +hsla[2] / 100; const l = +hsla[3] / 100; const a = !isUnd(hsla[4]) ? +hsla[4] : 1; let r, g, b; if (s === 0) { r = g = b = l; } else { const q = l < .5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; r = round$1(hue2rgb(p, q, h + 1 / 3) * 255, 0); g = round$1(hue2rgb(p, q, h) * 255, 0); b = round$1(hue2rgb(p, q, h - 1 / 3) * 255, 0); } return [r, g, b, a]; }; /** * All in one color converter that converts a color string value into an array of RGBA values * @param {String} colorString * @return {ColorArray} */ const convertColorStringValuesToRgbaArray = colorString => { return isRgb(colorString) ? rgbToRgba(colorString) : isHex(colorString) ? hexToRgba(colorString) : isHsl(colorString) ? hslToRgba(colorString) : [0, 0, 0, 1]; }; /** * @template T, D * @param {T|undefined} targetValue * @param {D} defaultValue * @return {T|D} */ const setValue = (targetValue, defaultValue) => { return isUnd(targetValue) ? defaultValue : targetValue; }; /** * @param {TweenPropValue} value * @param {Target} target * @param {Number} index * @param {Number} total * @param {Object} [store] * @return {any} */ const getFunctionValue = (value, target, index, total, store) => { let func; if (isFnc(value)) { func = () => { const computed = /** @type {Function} */(value)(target, index, total); // Fallback to 0 if the function returns undefined / NaN / null / false / 0 return !isNaN(+computed) ? +computed : computed || 0; }; } else if (isStr(value) && stringStartsWith(value, cssVarPrefix)) { func = () => { const match = value.match(cssVariableMatchRgx); const cssVarName = match[1]; const fallbackValue = match[2]; let computed = getComputedStyle(/** @type {HTMLElement} */(target))?.getPropertyValue(cssVarName); // Use fallback if CSS variable is not set or empty if ((!computed || computed.trim() === emptyString) && fallbackValue) { computed = fallbackValue.trim(); } return computed || 0; }; } else { return value; } if (store) store.func = func; return func(); }; /** * @param {Target} target * @param {String} prop * @return {tweenTypes} */ const getTweenType = (target, prop) => { return !target[isDomSymbol] ? tweenTypes.OBJECT : // Handle SVG attributes target[isSvgSymbol] && isValidSVGAttribute(target, prop) ? tweenTypes.ATTRIBUTE : // Handle CSS Transform properties differently than CSS to allow individual animations validTransforms.includes(prop) || shortTransforms.get(prop) ? tweenTypes.TRANSFORM : // CSS variables stringStartsWith(prop, '--') ? tweenTypes.CSS_VAR : // All other CSS properties prop in /** @type {DOMTarget} */(target).style ? tweenTypes.CSS : // Handle other DOM Attributes prop in target ? tweenTypes.OBJECT : tweenTypes.ATTRIBUTE; }; /** * @param {DOMTarget} target * @param {String} propName * @param {Object} animationInlineStyles * @return {String} */ const getCSSValue = (target, propName, animationInlineStyles) => { const inlineStyles = target.style[propName]; if (inlineStyles && animationInlineStyles) { animationInlineStyles[propName] = inlineStyles; } const value = inlineStyles || getComputedStyle(target[proxyTargetSymbol] || target).getPropertyValue(propName); return value === 'auto' ? '0' : value; }; /** * @param {Target} target * @param {String} propName * @param {tweenTypes} [tweenType] * @param {Object|void} [animationInlineStyles] * @return {String|Number} */ const getOriginalAnimatableValue = (target, propName, tweenType, animationInlineStyles) => { const type = !isUnd(tweenType) ? tweenType : getTweenType(target, propName); return type === tweenTypes.OBJECT ? target[propName] || 0 : type === tweenTypes.ATTRIBUTE ? /** @type {DOMTarget} */(target).getAttribute(propName) : type === tweenTypes.TRANSFORM ? parseInlineTransforms(/** @type {DOMTarget} */(target), propName, animationInlineStyles) : type === tweenTypes.CSS_VAR ? getCSSValue(/** @type {DOMTarget} */(target), propName, animationInlineStyles).trimStart() : getCSSValue(/** @type {DOMTarget} */(target), propName, animationInlineStyles); }; /** * @param {Number} x * @param {Number} y * @param {String} operator * @return {Number} */ const getRelativeValue = (x, y, operator) => { return operator === '-' ? x - y : operator === '+' ? x + y : x * y; }; /** @return {TweenDecomposedValue} */ const createDecomposedValueTargetObject = () => { return { /** @type {valueTypes} */ t: valueTypes.NUMBER, n: 0, u: null, o: null, d: null, s: null, } }; /** * @param {String|Number} rawValue * @param {TweenDecomposedValue} targetObject * @return {TweenDecomposedValue} */ const decomposeRawValue = (rawValue, targetObject) => { /** @type {valueTypes} */ targetObject.t = valueTypes.NUMBER; targetObject.n = 0; targetObject.u = null; targetObject.o = null; targetObject.d = null; targetObject.s = null; if (!rawValue) return targetObject; const num = +rawValue; if (!isNaN(num)) { // It's a number targetObject.n = num; return targetObject; } else { // let str = /** @type {String} */(rawValue).trim(); let str = /** @type {String} */(rawValue); // Parsing operators (+=, -=, *=) manually is much faster than using regex here if (str[1] === '=') { targetObject.o = str[0]; str = str.slice(2); } // Skip exec regex if the value type is complex or color to avoid long regex backtracking const unitMatch = str.includes(' ') ? false : unitsExecRgx.exec(str); if (unitMatch) { // Has a number and a unit targetObject.t = valueTypes.UNIT; targetObject.n = +unitMatch[1]; targetObject.u = unitMatch[2]; return targetObject; } else if (targetObject.o) { // Has an operator (+=, -=, *=) targetObject.n = +str; return targetObject; } else if (isCol(str)) { // Is a color targetObject.t = valueTypes.COLOR; targetObject.d = convertColorStringValuesToRgbaArray(str); return targetObject; } else { // Is a more complex string (generally svg coords, calc() or filters CSS values) const matchedNumbers = str.match(digitWithExponentRgx); targetObject.t = valueTypes.COMPLEX; targetObject.d = matchedNumbers ? matchedNumbers.map(Number) : []; targetObject.s = str.split(digitWithExponentRgx) || []; return targetObject; } } }; /** * @param {Tween} tween * @param {TweenDecomposedValue} targetObject * @return {TweenDecomposedValue} */ const decomposeTweenValue = (tween, targetObject) => { targetObject.t = tween._valueType; targetObject.n = tween._toNumber; targetObject.u = tween._unit; targetObject.o = null; targetObject.d = cloneArray(tween._toNumbers); targetObject.s = cloneArray(tween._strings); return targetObject; }; const decomposedOriginalValue = createDecomposedValueTargetObject(); /** * @param {Tickable} tickable * @param {Number} time * @param {Number} muteCallbacks * @param {Number} internalRender * @param {tickModes} tickMode * @return {Number} */ const render = (tickable, time, muteCallbacks, internalRender, tickMode) => { const parent = tickable.parent; const duration = tickable.duration; const completed = tickable.completed; const iterationDuration = tickable.iterationDuration; const iterationCount = tickable.iterationCount; const _currentIteration = tickable._currentIteration; const _loopDelay = tickable._loopDelay; const _reversed = tickable._reversed; const _alternate = tickable._alternate; const _hasChildren = tickable._hasChildren; const tickableDelay = tickable._delay; const tickablePrevAbsoluteTime = tickable._currentTime; // TODO: rename ._currentTime to ._absoluteCurrentTime const tickableEndTime = tickableDelay + iterationDuration; const tickableAbsoluteTime = time - tickableDelay; const tickablePrevTime = clamp$1(tickablePrevAbsoluteTime, -tickableDelay, duration); const tickableCurrentTime = clamp$1(tickableAbsoluteTime, -tickableDelay, duration); const deltaTime = tickableAbsoluteTime - tickablePrevAbsoluteTime; const isCurrentTimeAboveZero = tickableCurrentTime > 0; const isCurrentTimeEqualOrAboveDuration = tickableCurrentTime >= duration; const isSetter = duration <= minValue; const forcedTick = tickMode === tickModes.FORCE; let isOdd = 0; let iterationElapsedTime = tickableAbsoluteTime; // Render checks // Used to also check if the children have rendered in order to trigger the onRender callback on the parent timer let hasRendered = 0; // Execute the "expensive" iterations calculations only when necessary if (iterationCount > 1) { // bitwise NOT operator seems to be generally faster than Math.floor() across browsers const currentIteration = ~~(tickableCurrentTime / (iterationDuration + (isCurrentTimeEqualOrAboveDuration ? 0 : _loopDelay))); tickable._currentIteration = clamp$1(currentIteration, 0, iterationCount); // Prevent the iteration count to go above the max iterations when reaching the end of the animation if (isCurrentTimeEqualOrAboveDuration) tickable._currentIteration--; isOdd = tickable._currentIteration % 2; iterationElapsedTime = tickableCurrentTime % (iterationDuration + _loopDelay) || 0; } // Checks if exactly one of _reversed and (_alternate && isOdd) is true const isReversed = _reversed ^ (_alternate && isOdd); const _ease = /** @type {Renderable} */(tickable)._ease; let iterationTime = isCurrentTimeEqualOrAboveDuration ? isReversed ? 0 : duration : isReversed ? iterationDuration - iterationElapsedTime : iterationElapsedTime; if (_ease) iterationTime = iterationDuration * _ease(iterationTime / iterationDuration) || 0; const isRunningBackwards = (parent ? parent.backwards : tickableAbsoluteTime < tickablePrevAbsoluteTime) ? !isReversed : !!isReversed; tickable._currentTime = tickableAbsoluteTime; tickable._iterationTime = iterationTime; tickable.backwards = isRunningBackwards; if (isCurrentTimeAboveZero && !tickable.began) { tickable.began = true; if (!muteCallbacks && !(parent && (isRunningBackwards || !parent.began))) { tickable.onBegin(/** @type {CallbackArgument} */(tickable)); } } else if (tickableAbsoluteTime <= 0) { tickable.began = false; } // Only triggers onLoop for tickable without children, otherwise call the the onLoop callback in the tick function // Make sure to trigger the onLoop before rendering to allow .refresh() to pickup the current values if (!muteCallbacks && !_hasChildren && isCurrentTimeAboveZero && tickable._currentIteration !== _currentIteration) { tickable.onLoop(/** @type {CallbackArgument} */(tickable)); } if ( forcedTick || tickMode === tickModes.AUTO && ( time >= tickableDelay && time <= tickableEndTime || // Normal render time <= tickableDelay && tickablePrevTime > tickableDelay || // Playhead is before the animation start time so make sure the animation is at its initial state time >= tickableEndTime && tickablePrevTime !== duration // Playhead is after the animation end time so make sure the animation is at its end state ) || iterationTime >= tickableEndTime && tickablePrevTime !== duration || iterationTime <= tickableDelay && tickablePrevTime > 0 || time <= tickablePrevTime && tickablePrevTime === duration && completed || // Force a render if a seek occurs on an completed animation isCurrentTimeEqualOrAboveDuration && !completed && isSetter // This prevents 0 duration tickables to be skipped ) { if (isCurrentTimeAboveZero) { // Trigger onUpdate callback before rendering tickable.computeDeltaTime(tickablePrevTime); if (!muteCallbacks) tickable.onBeforeUpdate(/** @type {CallbackArgument} */(tickable)); } // Start tweens rendering if (!_hasChildren) { // Time has jumped more than globals.tickThreshold so consider this tick manual const forcedRender = forcedTick || (isRunningBackwards ? deltaTime * -1 : deltaTime) >= globals.tickThreshold; const absoluteTime = tickable._offset + (parent ? parent._offset : 0) + tickableDelay + iterationTime; // Only Animation can have tweens, Timer returns undefined let tween = /** @type {Tween} */(/** @type {JSAnimation} */(tickable)._head); let tweenTarget; let tweenStyle; let tweenTargetTransforms; let tweenTargetTransformsProperties; let tweenTransformsNeedUpdate = 0; while (tween) { const tweenComposition = tween._composition; const tweenCurrentTime = tween._currentTime; const tweenChangeDuration = tween._changeDuration; const tweenAbsEndTime = tween._absoluteStartTime + tween._changeDuration; const tweenNextRep = tween._nextRep; const tweenPrevRep = tween._prevRep; const tweenHasComposition = tweenComposition !== compositionTypes.none; if ((forcedRender || ( (tweenCurrentTime !== tweenChangeDuration || absoluteTime <= tweenAbsEndTime + (tweenNextRep ? tweenNextRep._delay : 0)) && (tweenCurrentTime !== 0 || absoluteTime >= tween._absoluteStartTime) )) && (!tweenHasComposition || ( !tween._isOverridden && (!tween._isOverlapped || absoluteTime <= tweenAbsEndTime) && (!tweenNextRep || (tweenNextRep._isOverridden || absoluteTime <= tweenNextRep._absoluteStartTime)) && (!tweenPrevRep || (tweenPrevRep._isOverridden || (absoluteTime >= (tweenPrevRep._absoluteStartTime + tweenPrevRep._changeDuration) + tween._delay))) )) ) { const tweenNewTime = tween._currentTime = clamp$1(iterationTime - tween._startTime, 0, tweenChangeDuration); const tweenProgress = tween._ease(tweenNewTime / tween._updateDuration); const tweenModifier = tween._modifier; const tweenValueType = tween._valueType; const tweenType = tween._tweenType; const tweenIsObject = tweenType === tweenTypes.OBJECT; const tweenIsNumber = tweenValueType === valueTypes.NUMBER; // Only round the in-between frames values if the final value is a string const tweenPrecision = (tweenIsNumber && tweenIsObject) || tweenProgress === 0 || tweenProgress === 1 ? -1 : globals.precision; // Recompose tween value /** @type {String|Number} */ let value; /** @type {Number} */ let number; if (tweenIsNumber) { value = number = /** @type {Number} */(tweenModifier(round$1(lerp$1(tween._fromNumber, tween._toNumber, tweenProgress), tweenPrecision ))); } else if (tweenValueType === valueTypes.UNIT) { // R