algoframe
Version:
A TypeScript comprehensive and modulated libary for doing algebraic animation with requestAnimatioFrame natively, even with Bezier and Sequences
462 lines (461 loc) • 19 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Sequence = exports.KeyChanger = void 0;
const utils_1 = require("../utils");
const utils_2 = require("./utils");
__exportStar(require("./utils"), exports);
// Classes
class KeyChanger {
constructor(duration, easing = 'linear', keyframes) {
this.keyframes = keyframes;
this.next = null;
this.current = null;
this.adaptative = false;
this.object = Symbol();
this.duration =
typeof duration === 'number'
? Math.floor(duration)
: (_ => {
this.adaptative = true;
return 1;
})();
this.run = [];
this.easing = (0, utils_1.passPreset)(easing);
this.init(keyframes);
}
nextTime() {
var _a;
if (!this.run.length) {
this.next = null;
return;
}
if (this.run.length > 1) {
this.current = this.run.reduce((previousValue, currentValue) => {
return currentValue.time(this.duration) <
previousValue.time(this.duration)
? currentValue
: previousValue;
});
this.next =
this.run
.filter(v => v.time(this.duration) !== this.current.time(this.duration))
.reduce((previousValue, currentValue) => currentValue.time(this.duration) <
previousValue.time(this.duration)
? currentValue
: previousValue) || this.current;
// console.log(this.current?.time(1), this.next.time(1), this.run);
}
else {
this.restart();
}
(_a = this.changer) === null || _a === void 0 ? void 0 : _a.call(this);
this.run.shift();
}
restart() {
while (this.run.length) {
this.run.pop().triggered = false;
}
this.reset();
}
static rProgressValue(object, progress, end) {
return (progress - object.time(1)) / (end - object.time(1));
}
static rProgress(object, progress, end) {
let res = KeyChanger.rProgressValue(object, progress, end);
object.obj.reset();
object.obj.nextTime();
return res;
}
// This is called when in this.test(), this.current is of type nestedKeyframe, so treat de return as a nested timeline call.
currentAsSequence(object, progress, end) {
// console.log((progress - object.time(1)) / (end - object.time(1)));
const rProgress = KeyChanger.rProgress(object, progress, end);
let res;
if (rProgress <= 1) {
// console.log(object.obj);
res = object.obj.test(rProgress, undefined, true);
return res;
}
}
static lerp(x, y, a) {
const lerp = x * (1 - a) + y * a;
return lerp;
}
test(progress, miliseconds = false, runAdaptative = false, nextValue) {
progress = progress <= 1 ? progress : 1;
let next = nextValue ? nextValue : this.next;
if (this.adaptative && !runAdaptative) {
throw new Error('Adaptitive timed sequences cannot be played in first place');
}
if (miliseconds && !runAdaptative)
progress = progress * this.duration;
else if (miliseconds)
throw new Error('miliseconds mode not allowe when adaptative');
if (next && this.current) {
let past = this.current.obj;
let pastFinally = past === null || past === void 0 ? void 0 : past.callFinally.bind(past);
let callPast = false;
while (next.time(1) <= progress && !(next.time(1) === 1)) {
this.nextTime(); //bug-proof
next = this.next;
callPast = true;
}
callPast && (pastFinally === null || pastFinally === void 0 ? void 0 : pastFinally());
if ((0, utils_2.isSimple)(next) && (0, utils_2.isSimple)(this.current)) {
if (this.current.time(1) > progress) {
this.reset();
this.nextTime();
}
progress = Math.min(this.easing(progress), miliseconds ? this.duration : 1);
const a = next.time(1) - this.current.time(1);
const trace = progress / a;
const kProgress = progress < progress - a
? trace
: (progress - this.current.time(1)) / a;
// console.log(String([this.current.time(1), next.time(1)]));
const lerp = KeyChanger.lerp(this.current.value, next.value, next.hold ? 0 : kProgress);
// if (lerp < 0) {
// debugger;
// }
// debugger;
// console.log(this.current, next);
return lerp;
}
else if ((0, utils_2.isComplex)(next) && (0, utils_2.isSimple)(this.current)) {
// return (this.current as nestedKeyframe).obj.test(progress - this.current.time);
const nextValueFromObj = new utils_2.valueKeyframe(KeyChanger.getAbsoluteStartValue(next.obj), next.time(1), 'ratio');
nextValueFromObj.duration = this.duration;
pastFinally === null || pastFinally === void 0 ? void 0 : pastFinally();
return this.test(progress, undefined, undefined, nextValueFromObj
// this.next.obj.run[0].value
);
}
else if ((0, utils_2.isComplex)(this.current) && (0, utils_2.isComplex)(next)) {
// this.nextTime();
// debugger;
const res = this.currentAsSequence(this.current, progress, this.next ? this.next.time(1) : 1);
return res;
}
else if ((0, utils_2.isComplex)(this.current) && ((0, utils_2.isSimple)(next) || !next)) {
// console.log(progress.toFixed(2));
return this.currentAsSequence(this.current, progress, next ? next.time(1) : 1);
}
}
}
isLastKeyframe(time) {
return ((!this.current.triggered &&
time >= this.next.time(1) - this.current.time(1) &&
this.run[this.run.length - 1].time(1) !== 1) ||
(time >= this.run[this.run.length - 1].time(1) &&
this.run[this.run.length - 1].time(1) === 1));
}
getKeyframeForTime(time, miliseconds = false) {
// Asegurarse de que el tiempo no sea negativo
if (time < 0)
throw new Error('Time cannot be negative');
// Si el tiempo es mayor que 1, lo limitamos a 1
time = time <= 1 ? time : 1;
let next = this.next;
// Si el tiempo está en milisegundos, lo convertimos a una escala de 0 a 1
if (miliseconds)
time = time * this.duration;
// Si hay un keyframe siguiente y un keyframe actual
if (next && this.current) {
// Mientras el tiempo del keyframe siguiente sea menor o igual al tiempo dado
// y el tiempo del keyframe siguiente no sea 1 (el final de la secuencia)
while (next.time(1) <= time && !(next.time(1) === 1)) {
// Avanzamos al siguiente keyframe
this.nextTime();
next = this.next;
}
// Comprobamos si el keyframe actual es el último de la secuencia
let end = this.isLastKeyframe(time);
// Si el tiempo dado es igual al tiempo del keyframe siguiente
if (time === (next === null || next === void 0 ? void 0 : next.time(miliseconds ? this.duration : 1))) {
this.next.triggered = true;
// Devolvemos el keyframe siguiente y si es el final de la secuencia
return {
keyframe: next,
end,
};
}
this.current.triggered = true;
// Si el tiempo dado no es igual al tiempo del keyframe siguiente
// Devolvemos el keyframe actual y si es el final de la secuencia
return {
keyframe: this.current,
end,
};
}
else {
this.current.triggered = true;
// Si no hay un keyframe siguiente o un keyframe actual
// Avanzamos al siguiente keyframe y devolvemos el keyframe actual
// y false para indicar que no es el final de la secuencia
this.nextTime();
return {
keyframe: this.current,
end: false,
};
}
}
static getAbsoluteStartValue(sequence) {
let last = sequence.current;
while (last instanceof utils_2.nestedKeyframe) {
last = sequence.current;
}
return last.value;
}
static getAbsoluteEndKeyframe(sequence) {
let last = sequence.run[sequence.run.length - 1];
while (last instanceof utils_2.nestedKeyframe) {
last = sequence.run[sequence.run.length - 1];
}
return last;
}
}
exports.KeyChanger = KeyChanger;
// TODO:
// 1. Nested Sequence instances DONE
// Adaptative Sequence duration DONE
// P.D.: That's not the as AlgoFrame.timeline, which each timing 'sequence' has its own function rather a numeric value in a Sequence
class Sequence extends KeyChanger {
constructor(duration, keyframes, easing = 'linear', Ocallback = null, ofinallyCallback = null) {
super(duration, easing, keyframes);
this.keyframes = keyframes;
this.Ocallback = Ocallback;
this.ofinallyCallback = ofinallyCallback;
this.type = 'simple';
this.taken = [];
this.callback = null;
this.finallyTriggered = false;
this.callback = (all) => {
var _a, _b;
const { progress } = all;
let { keyframe: currentKeyframe, end: next } = this.getKeyframeForTime(progress);
if (!currentKeyframe)
return;
if (next && !this.finallyTriggered) {
return requestAnimationFrame(this.callFinally.bind(this));
}
if (currentKeyframe instanceof utils_2.nestedKeyframe) {
// let rProg = KeyChanger.rProgress(currentKeyframe, progress, 1);
return (_b = (_a = currentKeyframe.obj).callback) === null || _b === void 0 ? void 0 : _b.call(_a, all);
}
else {
return Ocallback === null || Ocallback === void 0 ? void 0 : Ocallback.bind(this)(all);
}
};
// Pushes and Checks if all events are of type nestedKeyframe or _keyframe
}
callFinally(ts) {
var _a;
if (this.finallyTriggered)
return;
this.finallyTriggered = true;
let { keyframe: currentKeyframe } = this.getKeyframeForTime(1);
if (currentKeyframe instanceof utils_2.nestedKeyframe) {
if (!currentKeyframe)
return;
currentKeyframe.obj.callFinally(ts);
}
(_a = this.ofinallyCallback) === null || _a === void 0 ? void 0 : _a.call(this);
}
init(keyframes) {
// if (window['debug']) debugger;
this.type = 'simple';
keyframes.forEach(k => {
if (k.type == 'ratio') {
k.timing = k.timing * this.duration;
k.type = 'miliseconds';
}
});
this.taken = [];
const zero = keyframes[0];
const final = keyframes[keyframes.length - 1];
zero.duration = this.duration;
final.duration = this.duration;
if (zero.time(1) > 0) {
// this.taken.push(0);
const first = zero instanceof utils_2.valueKeyframe
? new utils_2.valueKeyframe(zero.value, 0)
: new utils_2.nestedKeyframe(zero.obj, 0);
first.duration = this.duration;
this.keyframes.unshift(first);
this.run.push(first);
}
if (final.time(1) < 1) {
// debugger
if (final instanceof utils_2.nestedKeyframe) {
console.warn('Nested keyframe at the end of the sequence is not recommended');
}
else {
const last = new utils_2.valueKeyframe(final.value, 1, 'ratio');
last.duration = this.duration;
this.keyframes.push(last);
this.run.push(last);
}
}
this.keyframes.forEach((k, i) => {
k.duration = this.duration;
k = k;
const timing = k.time(this.duration);
if (timing > this.duration)
throw new Error('Keyframe timing overflow');
if (this.taken.includes(timing))
throw new Error('It must not have repeated times');
this.taken.push(k.time(1));
if (k instanceof utils_2.nestedKeyframe)
this.type = 'nested';
this.run.push(k);
});
if (!this.type)
throw new Error('No events/keyframes provided');
if (this.keyframes[0] instanceof utils_2.valueKeyframe) {
}
try {
this.nextTime();
}
catch (e) {
debugger;
throw new Error('Identical time signatures on keyframes are not allowed on a single animation channel');
}
}
static passKeyframe(k) {
if (k instanceof utils_2.nestedKeyframe || k instanceof utils_2.valueKeyframe)
return k;
return this.is_value(k)
? new utils_2.valueKeyframe(k.value, k.timing, k.type)
: new utils_2.nestedKeyframe(k.obj, k.timing, k.type);
}
static is_value(object) {
return 'val' in object;
}
addKeyframes(
/**
* Adds a new keyframe to the entire set,
*
* @remarks
* To apply new keyframes, must do .reset() before
*
* @param keyframe - A valid AlgoFrame's keyframe object
*/
method = 'push', ...keyframes) {
keyframes.forEach(keyframe => {
const nkeyframe = Sequence.passKeyframe(keyframe);
this.keyframes[method](nkeyframe);
});
const { max: duration } = (0, utils_2.timeIntervals)(this.keyframes);
this.keyframes.forEach(k => {
if (k.type == 'ratio') {
k.timing = k.timing * duration;
k.type = 'miliseconds';
}
});
this.duration = duration;
this.init(this.keyframes);
return this;
}
extendToSequence(seq, safe = {
mode: 'shift',
}) {
// i.e. concatanate sequences
// You need to specify if you want to replace the last keyframe of the current this Sequence so the animation can make sense, or, in otherwise, add a apdding to the this animation finish
if (seq.object === this.object)
throw new Error('Cannot reextend to my own self');
let safePad = safe.value * 2;
safePad = safePad ? safePad : 0;
if (safePad) {
seq.duration += 1;
}
seq.keyframes.forEach((k, i) => {
const safing = i < seq.keyframes.length - 1 ? 1 : 0;
const safeOffset = safePad ? safing : 0;
if (k.type === 'ratio') {
k.timing = k.time(seq.duration + this.duration);
k.type = 'miliseconds';
}
else {
const durationRatio = k.duration / (seq.duration + k.duration);
const timingOffset = (k.duration + this.duration + safeOffset * safePad) * durationRatio;
k.timing = k.timing + Math.ceil(timingOffset);
}
k.duration +=
this.duration +
Math.floor(safePad * (k.duration / (seq.duration + k.duration)));
if (!safing && safe) {
k.timing += 1;
}
});
if (safe && !safePad) {
// safeShift
this.keyframes.pop();
}
this.keyframes.forEach((k, i) => {
k.duration +=
this.duration +
Math.ceil(safePad * (k.duration / (seq.duration + k.duration)));
});
this.keyframes.forEach(k => {
// console.log(k.duration);
});
/* const display = (seq: Sequence) =>
seq.keyframes.map(k => [k.time(k.duration), k.duration]);
console.log(display(seq), display(this)); */
// console.log(seq.keyframes);
this.addKeyframes('push', ...seq.keyframes.sort((a, b) => a.timing - b.timing));
// console.log(this);
return this;
}
reset() {
this.keyframes.forEach(k => this.run.push(k));
this.finallyTriggered = false;
}
clone() {
const keyframes = this.keyframes.map(k => {
if ((0, utils_2.isComplex)(k)) {
const copy = (0, utils_2.replicate)(k);
copy.obj = copy.obj.clone();
return copy;
}
return (0, utils_2.replicate)(k);
});
let copy = new Sequence(this.duration, keyframes, this.easing, this.callback, this.ofinallyCallback);
return copy;
}
reverseKeyframes(keyframes = this.keyframes) {
return keyframes
.map((kf, i) => {
let replacement;
if (kf instanceof utils_2.nestedKeyframe) {
replacement = new utils_2.nestedKeyframe(kf.obj, this.duration - kf.timing, kf.type, kf.delay);
}
else {
replacement = new utils_2.valueKeyframe(kf.value, this.duration - kf.timing, kf.type, kf.delay);
}
// replacement.duration = kf.duration;
return replacement;
})
.reverse();
}
extendToReverse(safe) {
let copy = new Sequence(this.duration + 1, this.reverseKeyframes(), this.easing, this.callback, this.ofinallyCallback);
this.extendToSequence(copy, safe);
return this;
}
}
exports.Sequence = Sequence;