@nativescript-community/additween
Version:
Additive tweening library for smooth animations
142 lines • 4.94 kB
JavaScript
import { PlainObjectReducer } from "./PlainObjectReducer";
import { time } from '@nativescript/core/profiling';
if (!global.window) {
global.window = {
requestAnimationFrame,
cancelAnimationFrame,
performance: {
now: time
}
};
}
else if (!global.window.requestAnimationFrame) {
global.window.requestAnimationFrame = requestAnimationFrame;
global.window.cancelAnimationFrame = cancelAnimationFrame;
if (!global.window.performance) {
//@ts-ignore
global.window.performance = {
now: time
};
}
}
function noop() { }
function identity(t) {
return t;
}
export class AdditiveTweening {
constructor(options) {
let frame = 0, lastTargetState = null, currentState = null, animationStack = [];
const onRender = options.onRender || noop;
const stateReducer = options.stateReducer || PlainObjectReducer;
const onFinish = options.onFinish || noop;
const onCancel = options.onCancel || noop;
function filterOutdatedTargetsFromStack(time) {
const filteredStack = [];
for (let i = animationStack.length - 1; i >= 0; i--) {
if (animationStack[i].end > time) {
filteredStack.push(animationStack[i]);
}
}
animationStack = filteredStack;
}
function getCurrentState(time) {
let animation, remain;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let target = stateReducer.clone(lastTargetState);
for (let i = animationStack.length - 1; i >= 0; i--) {
animation = animationStack[i];
if (animation.end < time) {
continue;
}
remain = (animation.end - time) / animation.duration;
target = stateReducer.reduce(target, animation.toState, animation.fromState, animation.easing(remain));
}
return target;
}
function hasActiveAnimation(time) {
for (let i = animationStack.length - 1; i >= 0; i--) {
const animation = animationStack[i];
if (animation.end >= time) {
return true;
}
}
return false;
}
const animationStep = () => {
if (lastTargetState === null) {
return;
}
const time = this.now();
currentState = getCurrentState(time);
onRender(currentState);
if (hasActiveAnimation(time)) {
frame = this.scheduleAnimationFrame(animationStep);
}
else {
this.finish();
}
};
/**
* Returns true if any tweening is in process
* @returns {boolean}
*/
this.isTweening = function () {
return !!lastTargetState;
};
/**
* Immediately sets tweening to its final state.
*/
this.finish = function () {
if (lastTargetState !== null) {
onFinish(lastTargetState);
lastTargetState = null;
currentState = null;
}
};
/**
* Cancels all active tweening processes
*/
this.cancel = function () {
if (lastTargetState !== null) {
if (window.cancelAnimationFrame) {
window.cancelAnimationFrame(frame);
frame = 0;
}
onCancel();
lastTargetState = null;
currentState = null;
}
};
/**
* Starts new tweening process
* @param fromState
* @param toState
* @param {Number} duration Duration in ms
* @param {Function} easing An easing function. It should take a number from [0,1] range and return a number from [0,1] range.
*/
this.tween = function (fromState, toState, duration, easing) {
const time = this.now();
const animation = {
duration: duration,
end: time + duration,
fromState: lastTargetState === null ? fromState : lastTargetState,
toState: toState,
easing: easing || identity,
};
filterOutdatedTargetsFromStack(time);
animationStack.push(animation);
lastTargetState = toState;
frame = this.scheduleAnimationFrame(animationStep);
};
}
scheduleAnimationFrame(cb) {
return window.requestAnimationFrame(cb);
}
now() {
if (!this._now) {
this._now = window.performance?.now || Date.now;
}
return this._now();
}
}
//# sourceMappingURL=AdditiveTweening.js.map