UNPKG

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
"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;