recipe.js

import { Seed } from './seed.js';

const defaults = {
    onEnd: () => { }
};

/**
 * A Recipe object can contain any number of [spices]{@link Spice} which will be interpolated <i>sequentially</i>, this is, one after another.
 * @example
import { Mixer, Recipe, Spice } from 'paprika-tween';
const spice1 = new Spice({ ... });
const spice2 = new Spice({ ... });
const recipe = new Recipe({ onEnd: () => cancelAnimationFrame(rafID) });
recipe.add(spice1, spice2);
const mixer = new Mixer();
mixer.add(recipe)
     .start();
function loop(timestamp) {
    mixer.frame(timestamp);
    rafID = requestAnimationFrame(loop);
}
let rafID = requestAnimationFrame(loop);
 * @since 1.0.0
 */
export class Recipe extends Seed {
    /**
     * Creates a new Recipe instance with the given options.
     * @param {Object} options
     * @param {function} [options.onEnd] - Function called when the last {@link Spice} reaches the end of the interpolation.
     * It receives the current instance as an argument.
     * @since 1.0.0
     */
    constructor(options) {
        super();
        Object.assign(this, defaults, options);
        this.spices = [];
    }
    /**
     * Adds one or more [spices]{@link Spice}.
     * @param {...Spice} rest - Instances of {@link Spice} objects.
     * @returns {Recipe} - The current instance of the Recipe.
     * @since 1.0.0
    * @example
import { Recipe, Spice } from 'paprika-tween';
const spice1 = new Spice({ ... });
const spice2 = new Spice({ ... });
new Recipe().add(spice1, spice2);
     */
    add(...rest) {
        for (let i = 0; i < rest.length; i++) {
            this.spices.push(rest[i]);
        }
        return this;
    }
    /**
     * Sets the starting time at the <code>time</code> argument. The first animatable object in the Recipe will start
     * at this time.<br>
     * If <code>time</code> is not provided, the timestamp from
     * [performance.now()]{@link https://developer.mozilla.org/en-US/docs/Web/API/Performance/now}
     * will be used instead.
     * @param {(DOMHighResTimeStamp|number)} [time] - The initial number from where to start the animation.
     * @since 1.0.0
    * @example
import { Recipe } from 'paprika-tween';
const recipe = new Recipe();
recipe.start(1000);
     */
    start(time) {
        let spice;
        let duration = 0;
        for (let i = 0; i < this.spices.length; i++) {
            spice = this.spices[i];
            if (!this.elapsed) {
                spice.delay += duration;
                duration = spice.duration + spice.delay;
            }
            spice.start(time);
        }
        this._startTime = this.spices[0]._startTime;
        if (!this.elapsed) {
            this.duration = duration;
        }
        return this;
    }
    /**
     * Moves the interpolation of the properties of the active spice in the recipe by the given time, which is
     * relative to the starting time.<br>
     * If <code>time</code> is not provided, the timestamp from
     * [performance.now()]{@link https://developer.mozilla.org/en-US/docs/Web/API/Performance/now}
     * will be used instead.
     * @param {(DOMHighResTimeStamp|number)} [time] - The amount of time to interpolate since the animations started.
     * @since 1.0.0
    * @example
import { Recipe, Spice } from 'paprika-tween';
const spice1 = new Spice({
    ...,
    duration: 1700
});
const spice2 = new Spice({
    ...,
    duration: 2000
});
const recipe = new Recipe().add(spice1, spice2)
      .start();
recipe.frame(performance.now() + 1800);
     */
    frame(time) {
        if (!this.spices.length) {
            return;
        }
        time ??= performance.now();
        let elapsed = this.elapse(time);
        // Don't render if the elapsed time has not changed
        if (this.elapsed === elapsed) {
            return;
        }
        this.elapsed = elapsed;
        for (let i = 0; i < this.spices.length; i++) {
            this.spices[i].frame(time);
        }
        if (elapsed === 1) {
            this.onEnd(this);
        }
    }
    /**
     * Disposes the spices in the recipe and removes its callback functions, making the instance eligible
     * for the garbage collector.
     * @since 1.0.0
     */
    dispose() {
        for (let i = this.spices.length - 1; i !== -1; i--) {
            this.spices[i].dispose();
        }
        this.spices.length = 0;
        this.onEnd = null;
    }
}