UNPKG

animejs

Version:

JavaScript animation engine

329 lines (306 loc) 10.9 kB
/** * Anime.js - timeline - CJS * @version v4.3.6 * @license MIT * @copyright 2026 - Julian Garnier */ 'use strict'; var globals = require('../core/globals.cjs'); var consts = require('../core/consts.cjs'); var helpers = require('../core/helpers.cjs'); var values = require('../core/values.cjs'); var targets = require('../core/targets.cjs'); var render = require('../core/render.cjs'); var styles = require('../core/styles.cjs'); var composition = require('../animation/composition.cjs'); var animation = require('../animation/animation.cjs'); var timer = require('../timer/timer.cjs'); var parser = require('../easings/eases/parser.cjs'); var position = require('./position.cjs'); /** * @import { * TargetsParam, * Callback, * Tickable, * TimerParams, * AnimationParams, * Target, * Renderable, * TimelineParams, * DefaultsParams, * TimelinePosition, * StaggerFunction, * } from '../types/index.js' */ /** * @import { * WAAPIAnimation, * } from '../waapi/waapi.js' */ /** * @param {Timeline} tl * @return {Number} */ function getTimelineTotalDuration(tl) { return helpers.clampInfinity(((tl.iterationDuration + tl._loopDelay) * tl.iterationCount) - tl._loopDelay) || consts.minValue; } /** * @overload * @param {TimerParams} childParams * @param {Timeline} tl * @param {Number} timePosition * @return {Timeline} * * @overload * @param {AnimationParams} childParams * @param {Timeline} tl * @param {Number} timePosition * @param {TargetsParam} targets * @param {Number} [index] * @param {Number} [length] * @return {Timeline} * * @param {TimerParams|AnimationParams} childParams * @param {Timeline} tl * @param {Number} timePosition * @param {TargetsParam} [targets] * @param {Number} [index] * @param {Number} [length] */ function addTlChild(childParams, tl, timePosition, targets, index, length) { const isSetter = helpers.isNum(childParams.duration) && /** @type {Number} */(childParams.duration) <= consts.minValue; // Offset the tl position with -minValue for 0 duration animations or .set() calls in order to align their end value with the defined position const adjustedPosition = isSetter ? timePosition - consts.minValue : timePosition; if (tl.composition) render.tick(tl, adjustedPosition, 1, 1, consts.tickModes.AUTO); const tlChild = targets ? new animation.JSAnimation(targets,/** @type {AnimationParams} */(childParams), tl, adjustedPosition, false, index, length) : new timer.Timer(/** @type {TimerParams} */(childParams), tl, adjustedPosition); if (tl.composition) tlChild.init(true); // TODO: Might be better to insert at a position relative to startTime? helpers.addChild(tl, tlChild); helpers.forEachChildren(tl, (/** @type {Renderable} */child) => { const childTLOffset = child._offset + child._delay; const childDur = childTLOffset + child.duration; if (childDur > tl.iterationDuration) tl.iterationDuration = childDur; }); tl.duration = getTimelineTotalDuration(tl); return tl; } let TLId = 0; class Timeline extends timer.Timer { /** * @param {TimelineParams} [parameters] */ constructor(parameters = {}) { super(/** @type {TimerParams&TimelineParams} */(parameters), null, 0); ++TLId; /** @type {String|Number} */ this.id = !helpers.isUnd(parameters.id) ? parameters.id : TLId; /** @type {Number} */ this.duration = 0; // TL duration starts at 0 and grows when adding children /** @type {Record<String, Number>} */ this.labels = {}; const defaultsParams = parameters.defaults; const globalDefaults = globals.globals.defaults; /** @type {DefaultsParams} */ this.defaults = defaultsParams ? helpers.mergeObjects(defaultsParams, globalDefaults) : globalDefaults; /** @type {Boolean} */ this.composition = values.setValue(parameters.composition, true); /** @type {Callback<this>} */ this.onRender = parameters.onRender || globalDefaults.onRender; const tlPlaybackEase = values.setValue(parameters.playbackEase, globalDefaults.playbackEase); this._ease = tlPlaybackEase ? parser.parseEase(tlPlaybackEase) : null; /** @type {Number} */ this.iterationDuration = 0; } /** * @overload * @param {TargetsParam} a1 * @param {AnimationParams} a2 * @param {TimelinePosition|StaggerFunction<Number|String>} [a3] * @return {this} * * @overload * @param {TimerParams} a1 * @param {TimelinePosition} [a2] * @return {this} * * @param {TargetsParam|TimerParams} a1 * @param {TimelinePosition|AnimationParams} a2 * @param {TimelinePosition|StaggerFunction<Number|String>} [a3] */ add(a1, a2, a3) { const isAnim = helpers.isObj(a2); const isTimer = helpers.isObj(a1); if (isAnim || isTimer) { this._hasChildren = true; if (isAnim) { const childParams = /** @type {AnimationParams} */(a2); // Check for function for children stagger positions if (helpers.isFnc(a3)) { const staggeredPosition = a3; const parsedTargetsArray = targets.parseTargets(/** @type {TargetsParam} */(a1)); // Store initial duration before adding new children that will change the duration const tlDuration = this.duration; // Store initial _iterationDuration before adding new children that will change the duration const tlIterationDuration = this.iterationDuration; // Store the original id in order to add specific indexes to the new animations ids const id = childParams.id; let i = 0; /** @type {Number} */ const parsedLength = (parsedTargetsArray.length); parsedTargetsArray.forEach((/** @type {Target} */target) => { // Create a new parameter object for each staggered children const staggeredChildParams = { ...childParams }; // Reset the duration of the timeline iteration before each stagger to prevent wrong start value calculation this.duration = tlDuration; this.iterationDuration = tlIterationDuration; if (!helpers.isUnd(id)) staggeredChildParams.id = id + '-' + i; addTlChild( staggeredChildParams, this, position.parseTimelinePosition(this, staggeredPosition(target, i, parsedLength, this)), target, i, parsedLength ); i++; }); } else { addTlChild( childParams, this, position.parseTimelinePosition(this, a3), /** @type {TargetsParam} */(a1), ); } } else { // It's a Timer addTlChild( /** @type TimerParams */(a1), this, position.parseTimelinePosition(this,a2), ); } if (this.composition) this.init(true); return this; } } /** * @overload * @param {Tickable} [synced] * @param {TimelinePosition} [position] * @return {this} * * @overload * @param {globalThis.Animation} [synced] * @param {TimelinePosition} [position] * @return {this} * * @overload * @param {WAAPIAnimation} [synced] * @param {TimelinePosition} [position] * @return {this} * * @param {Tickable|WAAPIAnimation|globalThis.Animation} [synced] * @param {TimelinePosition} [position] */ sync(synced, position) { if (helpers.isUnd(synced) || synced && helpers.isUnd(synced.pause)) return this; synced.pause(); const duration = +(/** @type {globalThis.Animation} */(synced).effect ? /** @type {globalThis.Animation} */(synced).effect.getTiming().duration : /** @type {Tickable} */(synced).duration); // Forces WAAPI Animation to persist; otherwise, they will stop syncing on finish. if (!helpers.isUnd(synced) && !helpers.isUnd(/** @type {WAAPIAnimation} */(synced).persist)) { /** @type {WAAPIAnimation} */(synced).persist = true; } return this.add(synced, { currentTime: [0, duration], duration, delay: 0, ease: 'linear', playbackEase: 'linear' }, position); } /** * @param {TargetsParam} targets * @param {AnimationParams} parameters * @param {TimelinePosition} [position] * @return {this} */ set(targets, parameters, position) { if (helpers.isUnd(parameters)) return this; parameters.duration = consts.minValue; parameters.composition = consts.compositionTypes.replace; return this.add(targets, parameters, position); } /** * @param {Callback<Timer>} callback * @param {TimelinePosition} [position] * @return {this} */ call(callback, position) { if (helpers.isUnd(callback) || callback && !helpers.isFnc(callback)) return this; return this.add({ duration: 0, delay: 0, onComplete: () => callback(this) }, position); } /** * @param {String} labelName * @param {TimelinePosition} [position] * @return {this} * */ label(labelName, position$1) { if (helpers.isUnd(labelName) || labelName && !helpers.isStr(labelName)) return this; this.labels[labelName] = position.parseTimelinePosition(this, position$1); return this; } /** * @param {TargetsParam} targets * @param {String} [propertyName] * @return {this} */ remove(targets$1, propertyName) { composition.removeTargetsFromRenderable(targets.parseTargets(targets$1), this, propertyName); return this; } /** * @param {Number} newDuration * @return {this} */ stretch(newDuration) { const currentDuration = this.duration; if (currentDuration === helpers.normalizeTime(newDuration)) return this; const timeScale = newDuration / currentDuration; const labels = this.labels; helpers.forEachChildren(this, (/** @type {JSAnimation} */child) => child.stretch(child.duration * timeScale)); for (let labelName in labels) labels[labelName] *= timeScale; return super.stretch(newDuration); } /** * @return {this} */ refresh() { helpers.forEachChildren(this, (/** @type {JSAnimation|Timer} */child) => { if (/** @type {JSAnimation} */(child).refresh) /** @type {JSAnimation} */(child).refresh(); }); return this; } /** * @return {this} */ revert() { super.revert(); helpers.forEachChildren(this, (/** @type {JSAnimation|Timer} */child) => child.revert, true); return styles.cleanInlineStyles(this); } /** * @typedef {this & {then: null}} ResolvedTimeline */ /** * @param {Callback<ResolvedTimeline>} [callback] * @return Promise<this> */ then(callback) { return super.then(callback); } } /** * @param {TimelineParams} [parameters] * @return {Timeline} */ const createTimeline = parameters => new Timeline(parameters).init(); exports.Timeline = Timeline; exports.createTimeline = createTimeline;