motion
Version:
The Motion library for the web
127 lines (124 loc) • 4.24 kB
JavaScript
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 };