UNPKG

motion

Version:

The Motion library for the web

127 lines (124 loc) 4.24 kB
import { defaults } from '../dom/utils/defaults.es.js'; import { isEasingList } from '../dom/utils/easing.es.js'; import { getEasingFunction } from './easing/get-function.es.js'; import { slowInterpolateNumbers } from './utils/interpolate.es.js'; class Animation { constructor(output, keyframes, // TODO Merge in defaults { easing = defaults.easing, duration = defaults.duration, delay = defaults.delay, endDelay = defaults.endDelay, offset, repeat = defaults.repeat, direction = "normal", }) { this.startTime = 0; this.rate = 1; this.t = 0; this.cancelT = 0; this.playState = "idle"; this.finished = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); const totalDuration = duration * (repeat + 1); const interpolate = slowInterpolateNumbers(keyframes, offset, isEasingList(easing) ? easing.map(getEasingFunction) : getEasingFunction(easing)); this.tick = (timestamp) => { if (this.playState === "finished") { const latest = interpolate(1); output(latest); this.resolve(latest); return; } if (this.pauseTime) { timestamp = this.pauseTime; } let t = (timestamp - this.startTime) * this.rate; this.t = t; // Convert to seconds t /= 1000; // Rebase on delay t = Math.max(t - delay, 0); const progress = t / duration; // TODO progress += iterationStart let currentIteration = Math.floor(progress); let iterationProgress = progress % 1.0; if (!iterationProgress && progress >= 1) { iterationProgress = 1; } if (iterationProgress === 1) { currentIteration--; } // Reverse progress const iterationIsOdd = currentIteration % 2; if (direction === "reverse" || (direction === "alternate" && iterationIsOdd) || (direction === "alternate-reverse" && !iterationIsOdd)) { iterationProgress = 1 - iterationProgress; } const interpolationIsFinished = t >= totalDuration; const interpolationProgress = interpolationIsFinished ? 1 : Math.min(iterationProgress, 1); const latest = interpolate(interpolationProgress); output(latest); const isFinished = t >= totalDuration + endDelay; if (isFinished) { this.playState = "finished"; this.resolve(latest); } else if (this.playState !== "idle") { requestAnimationFrame(this.tick); } }; this.play(); } play() { const now = performance.now(); this.playState = "running"; if (this.pauseTime) { this.startTime = now - (this.pauseTime - this.startTime); } else if (!this.startTime) { this.startTime = now; } this.pauseTime = undefined; requestAnimationFrame(this.tick); } pause() { this.playState = "paused"; this.pauseTime = performance.now(); } finish() { this.playState = "finished"; this.tick(0); } cancel() { this.playState = "idle"; this.tick(this.cancelT); this.reject(false); } reverse() { this.rate *= -1; } commitStyles() { this.cancelT = this.t; } get currentTime() { return this.t; } set currentTime(t) { if (this.pauseTime || this.rate === 0) { this.pauseTime = t; } else { this.startTime = performance.now() - t / this.rate; } } get playbackRate() { return this.rate; } set playbackRate(rate) { this.rate = rate; } } function animateNumber(output, keyframes = [0, 1], options = {}) { return new Animation(output, keyframes, options); } export { Animation, animateNumber };