From 827cf88c056e9049215b1191185003ed825053aa Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sat, 10 Sep 2022 18:49:40 -0500 Subject: [PATCH 001/108] initial undocumented types --- src/constants.ts | 2 +- src/util/animate.ts | 105 ++++++++++++++++++++++++++++++++------------ 2 files changed, 77 insertions(+), 30 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 59ac5f12ba0..b480d31f32e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,7 @@ import { TMat2D } from "./typedefs"; export { version as VERSION } from '../package.json'; -export function noop() {}; +export function noop(...args: unknown[]): any { }; export const halfPI = Math.PI / 2; export const twoMathPi = Math.PI * 2; export const PiBy180 = Math.PI / 180; diff --git a/src/util/animate.ts b/src/util/animate.ts index 4bb4d0b7a73..e22171af268 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -1,4 +1,3 @@ -//@ts-nocheck import { fabric } from '../../HEADER'; import { runningAnimations } from './animation_registry'; import { noop } from '../constants'; @@ -26,8 +25,55 @@ import { noop } from '../constants'; * * @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext */ +export type EasingFunction = (t: number, b: number, c: number, d: number) => number; +export type AbortFunction = (current: number | number[], valuePercent: number, timePercent: number) => boolean; + +export type OnChangeCallback = (x: number | number[], y: number, z: number) => void; + +export interface AnimationOptions { + onStart: VoidFunction; + onChange: OnChangeCallback; + onComplete: OnChangeCallback; + startValue: number | number[]; + endValue: number | number[]; + byValue: number | number[]; + easing: EasingFunction; + duration: number; + abort: AbortFunction; + delay: number; +} + +export interface AnimationCurrentState { + currentValue: number | number[]; + completionRate: number; + durationRate: number; +} -const defaultEasing = (t, b, c, d) => -c * Math.cos(t / d * (Math.PI / 2)) + c + b; +export interface AnimationContext extends AnimationOptions, AnimationCurrentState { + cancel: VoidFunction; +} + +/** + * Default easing + * @param t current time + * @param b + * @param c + * @param d duration + */ +const defaultEasing: EasingFunction = (t, b, c, d) => -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + +export const DefaultAnimationOptions: AnimationOptions = { + onStart: noop, + onChange: noop, + onComplete: noop, + startValue: 0, + endValue: 100, + byValue: 100, + easing: defaultEasing, + duration: 500, + abort: noop, + delay: 0 +}; /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. @@ -56,25 +102,26 @@ const defaultEasing = (t, b, c, d) => -c * Math.cos(t / d * (Math.PI / 2)) + c + * * @returns {CancelFunction} cancel function */ -export function animate(options = {}) { +export function animate(options: AnimationOptions = DefaultAnimationOptions) { let cancel = false; const { - startValue = 0, - duration = 500, - easing = defaultEasing, - onChange = noop, - abort = noop, - onComplete = noop, - endValue = 100, - delay = 0, + startValue, + duration, + easing, + onChange, + abort, + onComplete, + endValue, + delay, } = options; - const context = { + const context: AnimationContext = { ...options, + cancel: noop, currentValue: startValue, completionRate: 0, - durationRate: 0 + durationRate: 0, }; const removeFromRegistry = () => { @@ -88,14 +135,14 @@ export function animate(options = {}) { }; runningAnimations.push(context); - const runner = function (timestamp) { + const runner = function (timestamp: number) { const start = timestamp || +new Date(), finish = start + duration, isMany = Array.isArray(startValue), byValue = options.byValue || ( isMany ? - startValue.map((value, i) => endValue[i] - value) - : endValue - startValue + startValue.map((value, i) => (endValue as number[])[i] - value) + : (endValue as number) - startValue ); options.onStart && options.onStart(); @@ -106,12 +153,12 @@ export function animate(options = {}) { timePerc = currentTime / duration, current = isMany ? startValue.map( - (_value, i) => easing(currentTime, _value, byValue[i], duration) - ) : easing(currentTime, startValue, byValue, duration), - valuePerc = isMany ? Math.abs((current[0] - startValue[0]) / byValue[0]) - : Math.abs((current - startValue) / byValue); + (_value, i) => easing(currentTime, _value, (byValue as number[])[i], duration) + ) : easing(currentTime, startValue, byValue as number, duration), + valuePerc = isMany ? Math.abs(((current as number[])[0] - startValue[0]) / (byValue as number[])[0]) + : Math.abs(((current as number) - startValue) / (byValue as number)); // update context - context.currentValue = isMany ? current.slice() : current; + context.currentValue = isMany ? (current as number[]).slice() : current; context.completionRate = valuePerc; context.durationRate = timePerc; @@ -124,12 +171,12 @@ export function animate(options = {}) { } if (time > finish) { // update context - context.currentValue = isMany ? endValue.slice() : endValue; + context.currentValue = isMany ? (endValue as number[]).slice() : endValue; context.completionRate = 1; context.durationRate = 1; // execute callbacks - onChange(isMany ? endValue.slice() : endValue, 1, 1); - onComplete(endValue, 1, 1); + onChange(isMany ? (endValue as number[]).slice() : endValue, 1, 1); + onComplete(endValue as number, 1, 1); removeFromRegistry(); return; } @@ -151,7 +198,7 @@ export function animate(options = {}) { const _requestAnimFrame = fabric.window.requestAnimationFrame || - function(callback) { + function(callback: FrameRequestCallback) { return fabric.window.setTimeout(callback, 1000 / 60); }; @@ -165,10 +212,10 @@ const _cancelAnimFrame = * @param {Function} callback Callback to invoke * @param {DOMElement} element optional Element to associate with animation */ -export function requestAnimFrame(...args) { - return _requestAnimFrame.apply(fabric.window, args); +export function requestAnimFrame(callback: FrameRequestCallback) { + return _requestAnimFrame.apply(fabric.window, [callback]); } -export function cancelAnimFrame(...args) { - return _cancelAnimFrame.apply(fabric.window, args); +export function cancelAnimFrame(handle: number) { + return _cancelAnimFrame.apply(fabric.window, [handle]); } From 9224364f7d92645c5ff031cc2e3fac49c12f95d8 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sun, 11 Sep 2022 17:50:08 -0500 Subject: [PATCH 002/108] documenting types, type fixes, mergeWithDefaults for animate ran npm audit --- package-lock.json | 48 ++++++------- src/typedefs.ts | 4 +- src/util/anim_ease.ts | 87 ++++++++++++++---------- src/util/animate.ts | 138 +++++++++++++++++++++++++------------- src/util/animate_color.ts | 2 +- src/util/lang_object.ts | 9 +++ 6 files changed, 179 insertions(+), 109 deletions(-) diff --git a/package-lock.json b/package-lock.json index cad308ddbb5..7edabc123cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3976,9 +3976,9 @@ } }, "node_modules/inquirer-checkbox-plus-prompt/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, "engines": { "node": ">=4" @@ -5152,9 +5152,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/minipass": { @@ -5195,9 +5195,9 @@ } }, "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", "dev": true, "engines": { "node": "*" @@ -6788,9 +6788,9 @@ } }, "node_modules/terser": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz", - "integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", + "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.2", @@ -10508,9 +10508,9 @@ "dev": true }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true }, "chardet": { @@ -11361,9 +11361,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minipass": { @@ -11392,9 +11392,9 @@ "devOptional": true }, "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", "dev": true }, "ms": { @@ -12562,9 +12562,9 @@ } }, "terser": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz", - "integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", + "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.2", diff --git a/src/typedefs.ts b/src/typedefs.ts index e6b493ba53a..3afdec2427c 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -47,7 +47,7 @@ export const enum SupportedSVGUnit { } export type TMat2D = [number, number, number, number, number, number]; - + export type ModifierKey = 'altKey' | 'shiftKey' | 'ctrlKey'; /** @@ -64,3 +64,5 @@ export type TransformEvent = TEvent & T & { target: any } } + +export type WithReturnType any, TNewReturn> = (...a: Parameters) => TNewReturn; diff --git a/src/util/anim_ease.ts b/src/util/anim_ease.ts index 0361060e644..966df830172 100644 --- a/src/util/anim_ease.ts +++ b/src/util/anim_ease.ts @@ -1,10 +1,14 @@ import { twoMathPi, halfPI } from "../constants"; -type TEasingFunction = ( - currentTime: number, - startValue: number, - byValue: number, - duration: number) => number; +/** + * An easing function + * @param t current "time"/ms elapsed + * @param b start/"base" value + * @param c increment/"change"/"completion rate"/magnitude + * @param d "duration" + * @returns next value + */ +export type EasingFunction = (t: number, b: number, c: number, d: number) => number; /** * Easing functions @@ -12,6 +16,13 @@ type TEasingFunction = ( * @namespace fabric.util.ease */ +/** + * TODO: ask about docs for this, I don't understand it + * @param a + * @param c + * @param p + * @param s + */ const normalize = (a: number, c: number, p: number, s: number) => { if (a < Math.abs(c)) { a = c; @@ -33,17 +44,23 @@ const elastic = (a: number, s: number, p: number, t: number, d: number): number Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * twoMathPi / p); +/** + * Default sinusoidal easing + */ +export const defaultEasing: EasingFunction = (t, b, c, d) => -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + + /** * Cubic easing out * @memberOf fabric.util.ease */ -export const easeOutCubic:TEasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 2 + 1) + b; +export const easeOutCubic: EasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 2 + 1) + b; /** * Cubic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutCubic:TEasingFunction = (t, b, c, d) => { +export const easeInOutCubic: EasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { return c / 2 * t ** 3 + b; @@ -55,19 +72,19 @@ export const easeInOutCubic:TEasingFunction = (t, b, c, d) => { * Quartic easing in * @memberOf fabric.util.ease */ -export const easeInQuart:TEasingFunction = (t, b, c, d) => c * (t /= d) * t ** 3 + b; +export const easeInQuart: EasingFunction = (t, b, c, d) => c * (t /= d) * t ** 3 + b; /** * Quartic easing out * @memberOf fabric.util.ease */ -export const easeOutQuart:TEasingFunction = (t, b, c, d) => -c * ((t = t / d - 1) * t ** 3 - 1) + b; +export const easeOutQuart: EasingFunction = (t, b, c, d) => -c * ((t = t / d - 1) * t ** 3 - 1) + b; /** * Quartic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutQuart:TEasingFunction = (t, b, c, d) => { +export const easeInOutQuart: EasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { return c / 2 * t ** 4 + b; @@ -79,19 +96,19 @@ export const easeInOutQuart:TEasingFunction = (t, b, c, d) => { * Quintic easing in * @memberOf fabric.util.ease */ -export const easeInQuint:TEasingFunction = (t, b, c, d) => c * (t /= d) * t ** 4 + b; +export const easeInQuint: EasingFunction = (t, b, c, d) => c * (t /= d) * t ** 4 + b; /** * Quintic easing out * @memberOf fabric.util.ease */ -export const easeOutQuint:TEasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 4 + 1) + b; +export const easeOutQuint: EasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 4 + 1) + b; /** * Quintic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutQuint:TEasingFunction = (t, b, c, d) => { +export const easeInOutQuint: EasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { return c / 2 * t ** 5 + b; @@ -103,37 +120,37 @@ export const easeInOutQuint:TEasingFunction = (t, b, c, d) => { * Sinusoidal easing in * @memberOf fabric.util.ease */ -export const easeInSine:TEasingFunction = (t, b, c, d) => -c * Math.cos(t / d * halfPI) + c + b; +export const easeInSine: EasingFunction = (t, b, c, d) => -c * Math.cos(t / d * halfPI) + c + b; /** * Sinusoidal easing out * @memberOf fabric.util.ease */ -export const easeOutSine:TEasingFunction = (t, b, c, d) => c * Math.sin(t / d * halfPI) + b; +export const easeOutSine: EasingFunction = (t, b, c, d) => c * Math.sin(t / d * halfPI) + b; /** * Sinusoidal easing in and out * @memberOf fabric.util.ease */ -export const easeInOutSine:TEasingFunction = (t, b, c, d) => -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; +export const easeInOutSine: EasingFunction = (t, b, c, d) => -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; /** * Exponential easing in * @memberOf fabric.util.ease */ -export const easeInExpo:TEasingFunction = (t, b, c, d) => (t === 0) ? b : c * 2 ** (10 * (t / d - 1)) + b; +export const easeInExpo: EasingFunction = (t, b, c, d) => (t === 0) ? b : c * 2 ** (10 * (t / d - 1)) + b; /** * Exponential easing out * @memberOf fabric.util.ease */ -export const easeOutExpo:TEasingFunction = (t, b, c, d) => (t === d) ? b + c : c * -(2 ** (-10 * t / d) + 1) + b; +export const easeOutExpo: EasingFunction = (t, b, c, d) => (t === d) ? b + c : c * -(2 ** (-10 * t / d) + 1) + b; /** * Exponential easing in and out * @memberOf fabric.util.ease */ -export const easeInOutExpo:TEasingFunction = (t, b, c, d) => { +export const easeInOutExpo: EasingFunction = (t, b, c, d) => { if (t === 0) { return b; } @@ -151,19 +168,19 @@ export const easeInOutExpo:TEasingFunction = (t, b, c, d) => { * Circular easing in * @memberOf fabric.util.ease */ -export const easeInCirc:TEasingFunction = (t, b, c, d) => -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; +export const easeInCirc: EasingFunction = (t, b, c, d) => -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; /** * Circular easing out * @memberOf fabric.util.ease */ -export const easeOutCirc:TEasingFunction = (t, b, c, d) => c * Math.sqrt(1 - (t = t / d - 1) * t) + b; +export const easeOutCirc: EasingFunction = (t, b, c, d) => c * Math.sqrt(1 - (t = t / d - 1) * t) + b; /** * Circular easing in and out * @memberOf fabric.util.ease */ -export const easeInOutCirc:TEasingFunction = (t, b, c, d) => { +export const easeInOutCirc: EasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { return -c / 2 * (Math.sqrt(1 - t ** 2) - 1) + b; @@ -175,7 +192,7 @@ export const easeInOutCirc:TEasingFunction = (t, b, c, d) => { * Elastic easing in * @memberOf fabric.util.ease */ -export const easeInElastic:TEasingFunction = (t, b, c, d) => { +export const easeInElastic: EasingFunction = (t, b, c, d) => { const s = 1.70158, a = c; let p = 0; if (t === 0) { @@ -196,7 +213,7 @@ export const easeInElastic:TEasingFunction = (t, b, c, d) => { * Elastic easing out * @memberOf fabric.util.ease */ -export const easeOutElastic:TEasingFunction = (t, b, c, d) => { +export const easeOutElastic: EasingFunction = (t, b, c, d) => { const s = 1.70158, a = c; let p = 0; if (t === 0) { @@ -217,7 +234,7 @@ export const easeOutElastic:TEasingFunction = (t, b, c, d) => { * Elastic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutElastic:TEasingFunction = (t, b, c, d) => { +export const easeInOutElastic: EasingFunction = (t, b, c, d) => { const s = 1.70158, a = c; let p = 0; if (t === 0) { @@ -242,19 +259,19 @@ export const easeInOutElastic:TEasingFunction = (t, b, c, d) => { * Backwards easing in * @memberOf fabric.util.ease */ -export const easeInBack:TEasingFunction = (t, b, c, d, s = 1.70158) => c * (t /= d) * t * ((s + 1) * t - s) + b; +export const easeInBack: EasingFunction = (t, b, c, d, s = 1.70158) => c * (t /= d) * t * ((s + 1) * t - s) + b; /** * Backwards easing out * @memberOf fabric.util.ease */ -export const easeOutBack:TEasingFunction = (t, b, c, d, s = 1.70158) => c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; +export const easeOutBack: EasingFunction = (t, b, c, d, s = 1.70158) => c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; /** * Backwards easing in and out * @memberOf fabric.util.ease */ -export const easeInOutBack:TEasingFunction = (t, b, c, d, s = 1.70158) => { +export const easeInOutBack: EasingFunction = (t, b, c, d, s = 1.70158) => { t /= d / 2; if (t < 1) { return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; @@ -266,7 +283,7 @@ export const easeInOutBack:TEasingFunction = (t, b, c, d, s = 1.70158) => { * Bouncing easing out * @memberOf fabric.util.ease */ -export const easeOutBounce:TEasingFunction = (t, b, c, d) => { +export const easeOutBounce: EasingFunction = (t, b, c, d) => { if ((t /= d) < (1 / 2.75)) { return c * (7.5625 * t * t) + b; } @@ -285,13 +302,13 @@ export const easeOutBounce:TEasingFunction = (t, b, c, d) => { * Bouncing easing in * @memberOf fabric.util.ease */ - export const easeInBounce:TEasingFunction = (t, b, c, d) => c - easeOutBounce(d - t, 0, c, d) + b; + export const easeInBounce: EasingFunction = (t, b, c, d) => c - easeOutBounce(d - t, 0, c, d) + b; /** * Bouncing easing in and out * @memberOf fabric.util.ease */ -export const easeInOutBounce:TEasingFunction = (t, b, c, d) => +export const easeInOutBounce: EasingFunction = (t, b, c, d) => t < d / 2 ? easeInBounce(t * 2, 0, c, d) * 0.5 + b : easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; @@ -301,19 +318,19 @@ export const easeInOutBounce:TEasingFunction = (t, b, c, d) => * Quadratic easing in * @memberOf fabric.util.ease */ -export const easeInQuad:TEasingFunction = (t, b, c, d) => c * (t /= d) * t + b; +export const easeInQuad: EasingFunction = (t, b, c, d) => c * (t /= d) * t + b; /** * Quadratic easing out * @memberOf fabric.util.ease */ -export const easeOutQuad:TEasingFunction = (t, b, c, d) => -c * (t /= d) * (t - 2) + b; +export const easeOutQuad: EasingFunction = (t, b, c, d) => -c * (t /= d) * (t - 2) + b; /** * Quadratic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutQuad:TEasingFunction = (t, b, c, d) => { +export const easeInOutQuad: EasingFunction = (t, b, c, d) => { t /= (d / 2); if (t < 1) { return c / 2 * t ** 2 + b; @@ -325,4 +342,4 @@ export const easeInOutQuad:TEasingFunction = (t, b, c, d) => { * Cubic easing in * @memberOf fabric.util.ease */ -export const easeInCubic:TEasingFunction = (t, b, c, d) => c * (t /= d) * t * t + b; +export const easeInCubic: EasingFunction = (t, b, c, d) => c * (t /= d) * t * t + b; diff --git a/src/util/animate.ts b/src/util/animate.ts index e22171af268..8151d0c8bea 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -1,66 +1,109 @@ import { fabric } from '../../HEADER'; import { runningAnimations } from './animation_registry'; import { noop } from '../constants'; +import { WithReturnType } from "../typedefs"; +import { defaultEasing, EasingFunction } from "./anim_ease"; +import {mergeWithDefaults} from "./lang_object"; /** - * - * @typedef {Object} AnimationOptions - * Animation of a value or list of values. - * @property {Function} [onChange] Callback; invoked on every value change - * @property {Function} [onComplete] Callback; invoked when value change is completed - * @property {number | number[]} [startValue=0] Starting value - * @property {number | number[]} [endValue=100] Ending value - * @property {number | number[]} [byValue=100] Value to modify the property by - * @property {Function} [easing] Easing function - * @property {number} [duration=500] Duration of change (in ms) - * @property {Function} [abort] Additional function with logic. If returns true, animation aborts. - * @property {number} [delay] Delay of animation start (in ms) - * - * @typedef {() => void} CancelFunction - * - * @typedef {Object} AnimationCurrentState - * @property {number | number[]} currentValue value in range [`startValue`, `endValue`] - * @property {number} completionRate value in range [0, 1] - * @property {number} durationRate value in range [0, 1] - * - * @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext + * Callback called every frame + * @param t current "time"/ms elapsed. multivalue + * @param valueRatio ratio of current value to animation max value. [0, 1] + * @param timeRatio ratio of current ms to animation duration. [0, 1] + */ +export type OnChangeCallback = (t: number | number[], valueRatio: number, timeRatio: number) => void; + +/** + * Called to determine if animation should abort + * @returns truthy if animation should abort */ -export type EasingFunction = (t: number, b: number, c: number, d: number) => number; -export type AbortFunction = (current: number | number[], valuePercent: number, timePercent: number) => boolean; +export type AbortCallback = WithReturnType; -export type OnChangeCallback = (x: number | number[], y: number, z: number) => void; +/** + * Function used for canceling an animation + */ +export type CancelFunction = VoidFunction; +/** + * Animation of a value or list of values + */ export interface AnimationOptions { + /** + * Called when the animation starts + */ onStart: VoidFunction; + + /** + * Called at each frame of the animation + */ onChange: OnChangeCallback; + + /** + * Called after the last frame of the animation + */ onComplete: OnChangeCallback; + + /** + * Easing function + */ + easing: EasingFunction; + + /** + * Function called at each frame. + * If it returns true, abort + */ + abort: AbortCallback; + + /** + * Starting value(s) + */ startValue: number | number[]; + + /** + * Ending value(s) + */ endValue: number | number[]; + + /** + * Value(s) to increment/decrement the value(s) by + */ byValue: number | number[]; - easing: EasingFunction; + + /** + * Duration of the animation in ms + */ duration: number; - abort: AbortFunction; + + /** + * Delay to start the animation in ms + */ delay: number; } export interface AnimationCurrentState { + /** + * Current values + */ currentValue: number | number[]; + /** + * Same as valueRatio from @see OnChangeCallback + */ completionRate: number; + /** + * Same as completionRatio from @see OnChangeCallback + */ durationRate: number; } -export interface AnimationContext extends AnimationOptions, AnimationCurrentState { - cancel: VoidFunction; -} - /** - * Default easing - * @param t current time - * @param b - * @param c - * @param d duration + * Animation context */ -const defaultEasing: EasingFunction = (t, b, c, d) => -c * Math.cos(t / d * (Math.PI / 2)) + c + b; +export interface AnimationContext extends AnimationOptions, AnimationCurrentState { + /** + * Current function used to cancel the animation + */ + cancel: CancelFunction; +} export const DefaultAnimationOptions: AnimationOptions = { onStart: noop, @@ -102,7 +145,7 @@ export const DefaultAnimationOptions: AnimationOptions = { * * @returns {CancelFunction} cancel function */ -export function animate(options: AnimationOptions = DefaultAnimationOptions) { +export function animate(options: Partial = {}): CancelFunction { let cancel = false; const { @@ -114,11 +157,11 @@ export function animate(options: AnimationOptions = DefaultAnimationOptions) { onComplete, endValue, delay, - } = options; + } = mergeWithDefaults({ ...options }, DefaultAnimationOptions); - const context: AnimationContext = { - ...options, - cancel: noop, + const context: Partial = { + ...mergeWithDefaults({ ...options }, DefaultAnimationOptions), + cancel: noop, // placeholder currentValue: startValue, completionRate: 0, durationRate: 0, @@ -196,13 +239,13 @@ export function animate(options: AnimationOptions = DefaultAnimationOptions) { return context.cancel; } -const _requestAnimFrame = +const _requestAnimFrame: AnimationFrameProvider["requestAnimationFrame"] = fabric.window.requestAnimationFrame || function(callback: FrameRequestCallback) { return fabric.window.setTimeout(callback, 1000 / 60); }; -const _cancelAnimFrame = +const _cancelAnimFrame: AnimationFrameProvider["cancelAnimationFrame"] = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout; /** @@ -210,12 +253,11 @@ const _cancelAnimFrame = * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method * @memberOf fabric.util * @param {Function} callback Callback to invoke - * @param {DOMElement} element optional Element to associate with animation */ -export function requestAnimFrame(callback: FrameRequestCallback) { - return _requestAnimFrame.apply(fabric.window, [callback]); +export function requestAnimFrame(callback: FrameRequestCallback): number { + return _requestAnimFrame.bind(fabric.window)(callback); } -export function cancelAnimFrame(handle: number) { - return _cancelAnimFrame.apply(fabric.window, [handle]); +export function cancelAnimFrame(handle: number): void { + return _cancelAnimFrame.bind(fabric.window)(handle); } diff --git a/src/util/animate_color.ts b/src/util/animate_color.ts index 92772b2da81..ff66f707a97 100644 --- a/src/util/animate_color.ts +++ b/src/util/animate_color.ts @@ -52,7 +52,7 @@ export function animateColor( } = {} ) { const startColor = new Color(fromColor).getSource(), - endColor = new Color(toColor).getSource(), + endColor = new Color(toColor).getSource(); return animate({ ...restOfOptions, duration, diff --git a/src/util/lang_object.ts b/src/util/lang_object.ts index fea951b98c8..a17533a8264 100644 --- a/src/util/lang_object.ts +++ b/src/util/lang_object.ts @@ -65,3 +65,12 @@ export const extend = (destination, source, deep) => { //TODO: this function return an empty object if you try to clone null export const clone = (object: any, deep: boolean) => deep ? extend({ }, object, deep) : { ...object }; + +export function mergeWithDefaults(target: Partial, defaults: T): T { + for(const k in defaults) { + if(target[k] === undefined || target[k] === null) { + target[k] = defaults[k] + } + } + return target; +} From fc9300f9d16468b09237afe964aa2952d8ebf8db Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Fri, 16 Sep 2022 17:29:11 -0500 Subject: [PATCH 003/108] remove defaults obj for animate --- src/util/animate.ts | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/util/animate.ts b/src/util/animate.ts index 8151d0c8bea..9e37645d21d 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -31,7 +31,7 @@ export interface AnimationOptions { /** * Called when the animation starts */ - onStart: VoidFunction; + onStart?: VoidFunction; /** * Called at each frame of the animation @@ -45,6 +45,7 @@ export interface AnimationOptions { /** * Easing function + * @default [defaultEasing] */ easing: EasingFunction; @@ -61,21 +62,25 @@ export interface AnimationOptions { /** * Ending value(s) + * @default 100 */ endValue: number | number[]; /** * Value(s) to increment/decrement the value(s) by + * @default [endValue - startValue] */ byValue: number | number[]; /** * Duration of the animation in ms + * @default 500 */ duration: number; /** * Delay to start the animation in ms + * @default 0 */ delay: number; } @@ -105,19 +110,6 @@ export interface AnimationContext extends AnimationOptions, AnimationCurrentStat cancel: CancelFunction; } -export const DefaultAnimationOptions: AnimationOptions = { - onStart: noop, - onChange: noop, - onComplete: noop, - startValue: 0, - endValue: 100, - byValue: 100, - easing: defaultEasing, - duration: 500, - abort: noop, - delay: 0 -}; - /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. * @memberOf fabric.util @@ -149,18 +141,18 @@ export function animate(options: Partial = {}): CancelFunction let cancel = false; const { - startValue, - duration, - easing, - onChange, - abort, - onComplete, - endValue, - delay, - } = mergeWithDefaults({ ...options }, DefaultAnimationOptions); + startValue = 0, + duration = 500, + easing = defaultEasing, + onChange = noop, + abort = noop, + onComplete = noop, + endValue = 0, + delay = 0, + } = options; const context: Partial = { - ...mergeWithDefaults({ ...options }, DefaultAnimationOptions), + ...options, cancel: noop, // placeholder currentValue: startValue, completionRate: 0, From c263864715e86afc702d9d7327e9a3cbbd4c11ba Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Fri, 16 Sep 2022 17:36:41 -0500 Subject: [PATCH 004/108] run prettier --- src/constants.ts | 4 +- src/typedefs.ts | 31 ++++--- src/util/anim_ease.ts | 186 ++++++++++++++++++++++---------------- src/util/animate.ts | 69 ++++++++------ src/util/animate_color.ts | 31 ++++--- src/util/lang_object.ts | 35 +++---- 6 files changed, 197 insertions(+), 159 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index b480d31f32e..eab4c90ecc6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,7 @@ -import { TMat2D } from "./typedefs"; +import { TMat2D } from './typedefs'; export { version as VERSION } from '../package.json'; -export function noop(...args: unknown[]): any { }; +export function noop(...args: unknown[]): any {} export const halfPI = Math.PI / 2; export const twoMathPi = Math.PI * 2; export const PiBy180 = Math.PI / 180; diff --git a/src/typedefs.ts b/src/typedefs.ts index 3afdec2427c..08f34025f10 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -1,13 +1,13 @@ // https://www.typescriptlang.org/docs/handbook/utility-types.html interface NominalTag { - 'nominalTag': T; + nominalTag: T; } type Nominal = NominalTag & Type; -const enum Degree { } -const enum Radian { } +const enum Degree {} +const enum Radian {} export type TDegree = Nominal; export type TRadian = Nominal; @@ -15,7 +15,7 @@ export type TRadian = Nominal; export type TSize = { width: number; height: number; -} +}; export type Percent = `${number}%`; @@ -56,13 +56,16 @@ export type ModifierKey = 'altKey' | 'shiftKey' | 'ctrlKey'; export type PathData = (string | number)[][]; export type TEvent = { - e: E -} - -export type TransformEvent = TEvent & T & { - transform: { - target: any - } -} - -export type WithReturnType any, TNewReturn> = (...a: Parameters) => TNewReturn; + e: E; +}; + +export type TransformEvent = TEvent & + T & { + transform: { + target: any; + }; + }; + +export type WithReturnType any, TNewReturn> = ( + ...a: Parameters +) => TNewReturn; diff --git a/src/util/anim_ease.ts b/src/util/anim_ease.ts index 966df830172..5f732b2b0be 100644 --- a/src/util/anim_ease.ts +++ b/src/util/anim_ease.ts @@ -1,4 +1,4 @@ -import { twoMathPi, halfPI } from "../constants"; +import { twoMathPi, halfPI } from '../constants'; /** * An easing function @@ -8,7 +8,12 @@ import { twoMathPi, halfPI } from "../constants"; * @param d "duration" * @returns next value */ -export type EasingFunction = (t: number, b: number, c: number, d: number) => number; +export type EasingFunction = ( + t: number, + b: number, + c: number, + d: number +) => number; /** * Easing functions @@ -27,34 +32,38 @@ const normalize = (a: number, c: number, p: number, s: number) => { if (a < Math.abs(c)) { a = c; s = p / 4; - } - else { + } else { //handle the 0/0 case: if (c === 0 && a === 0) { - s = p / twoMathPi * Math.asin(1); - } - else { - s = p / twoMathPi * Math.asin(c / a); + s = (p / twoMathPi) * Math.asin(1); + } else { + s = (p / twoMathPi) * Math.asin(c / a); } } return { a, c, p, s }; -} +}; -const elastic = (a: number, s: number, p: number, t: number, d: number): number => a * - Math.pow(2, 10 * (t -= 1)) * - Math.sin((t * d - s) * twoMathPi / p); +const elastic = ( + a: number, + s: number, + p: number, + t: number, + d: number +): number => + a * Math.pow(2, 10 * (t -= 1)) * Math.sin(((t * d - s) * twoMathPi) / p); /** * Default sinusoidal easing */ -export const defaultEasing: EasingFunction = (t, b, c, d) => -c * Math.cos(t / d * (Math.PI / 2)) + c + b; - +export const defaultEasing: EasingFunction = (t, b, c, d) => + -c * Math.cos((t / d) * (Math.PI / 2)) + c + b; /** * Cubic easing out * @memberOf fabric.util.ease */ -export const easeOutCubic: EasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 2 + 1) + b; +export const easeOutCubic: EasingFunction = (t, b, c, d) => + c * ((t /= d - 1) * t ** 2 + 1) + b; /** * Cubic easing in and out @@ -63,22 +72,24 @@ export const easeOutCubic: EasingFunction = (t, b, c, d) => c * ((t /= d - 1) * export const easeInOutCubic: EasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { - return c / 2 * t ** 3 + b; + return (c / 2) * t ** 3 + b; } - return c / 2 * ((t -= 2) * t ** 2 + 2) + b; -} + return (c / 2) * ((t -= 2) * t ** 2 + 2) + b; +}; /** * Quartic easing in * @memberOf fabric.util.ease */ -export const easeInQuart: EasingFunction = (t, b, c, d) => c * (t /= d) * t ** 3 + b; +export const easeInQuart: EasingFunction = (t, b, c, d) => + c * (t /= d) * t ** 3 + b; /** * Quartic easing out * @memberOf fabric.util.ease */ -export const easeOutQuart: EasingFunction = (t, b, c, d) => -c * ((t = t / d - 1) * t ** 3 - 1) + b; +export const easeOutQuart: EasingFunction = (t, b, c, d) => + -c * ((t = t / d - 1) * t ** 3 - 1) + b; /** * Quartic easing in and out @@ -87,22 +98,24 @@ export const easeOutQuart: EasingFunction = (t, b, c, d) => -c * ((t = t / d - 1 export const easeInOutQuart: EasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { - return c / 2 * t ** 4 + b; + return (c / 2) * t ** 4 + b; } - return -c / 2 * ((t -= 2) * t ** 3 - 2) + b; -} + return (-c / 2) * ((t -= 2) * t ** 3 - 2) + b; +}; /** * Quintic easing in * @memberOf fabric.util.ease */ -export const easeInQuint: EasingFunction = (t, b, c, d) => c * (t /= d) * t ** 4 + b; +export const easeInQuint: EasingFunction = (t, b, c, d) => + c * (t /= d) * t ** 4 + b; /** * Quintic easing out * @memberOf fabric.util.ease */ -export const easeOutQuint: EasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 4 + 1) + b; +export const easeOutQuint: EasingFunction = (t, b, c, d) => + c * ((t /= d - 1) * t ** 4 + 1) + b; /** * Quintic easing in and out @@ -111,40 +124,45 @@ export const easeOutQuint: EasingFunction = (t, b, c, d) => c * ((t /= d - 1) * export const easeInOutQuint: EasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { - return c / 2 * t ** 5 + b; + return (c / 2) * t ** 5 + b; } - return c / 2 * ((t -= 2) * t ** 4 + 2) + b; -} + return (c / 2) * ((t -= 2) * t ** 4 + 2) + b; +}; /** * Sinusoidal easing in * @memberOf fabric.util.ease */ -export const easeInSine: EasingFunction = (t, b, c, d) => -c * Math.cos(t / d * halfPI) + c + b; +export const easeInSine: EasingFunction = (t, b, c, d) => + -c * Math.cos((t / d) * halfPI) + c + b; /** * Sinusoidal easing out * @memberOf fabric.util.ease */ -export const easeOutSine: EasingFunction = (t, b, c, d) => c * Math.sin(t / d * halfPI) + b; +export const easeOutSine: EasingFunction = (t, b, c, d) => + c * Math.sin((t / d) * halfPI) + b; /** * Sinusoidal easing in and out * @memberOf fabric.util.ease */ -export const easeInOutSine: EasingFunction = (t, b, c, d) => -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; +export const easeInOutSine: EasingFunction = (t, b, c, d) => + (-c / 2) * (Math.cos((Math.PI * t) / d) - 1) + b; /** * Exponential easing in * @memberOf fabric.util.ease */ -export const easeInExpo: EasingFunction = (t, b, c, d) => (t === 0) ? b : c * 2 ** (10 * (t / d - 1)) + b; +export const easeInExpo: EasingFunction = (t, b, c, d) => + t === 0 ? b : c * 2 ** (10 * (t / d - 1)) + b; /** * Exponential easing out * @memberOf fabric.util.ease */ -export const easeOutExpo: EasingFunction = (t, b, c, d) => (t === d) ? b + c : c * -(2 ** (-10 * t / d) + 1) + b; +export const easeOutExpo: EasingFunction = (t, b, c, d) => + t === d ? b + c : c * -(2 ** ((-10 * t) / d) + 1) + b; /** * Exponential easing in and out @@ -159,22 +177,24 @@ export const easeInOutExpo: EasingFunction = (t, b, c, d) => { } t /= d / 2; if (t < 1) { - return c / 2 * 2 ** (10 * (t - 1)) + b; + return (c / 2) * 2 ** (10 * (t - 1)) + b; } - return c / 2 * -(2 ** (-10 * --t) + 2) + b; -} + return (c / 2) * -(2 ** (-10 * --t) + 2) + b; +}; /** * Circular easing in * @memberOf fabric.util.ease */ -export const easeInCirc: EasingFunction = (t, b, c, d) => -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; +export const easeInCirc: EasingFunction = (t, b, c, d) => + -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; /** * Circular easing out * @memberOf fabric.util.ease */ -export const easeOutCirc: EasingFunction = (t, b, c, d) => c * Math.sqrt(1 - (t = t / d - 1) * t) + b; +export const easeOutCirc: EasingFunction = (t, b, c, d) => + c * Math.sqrt(1 - (t = t / d - 1) * t) + b; /** * Circular easing in and out @@ -183,17 +203,18 @@ export const easeOutCirc: EasingFunction = (t, b, c, d) => c * Math.sqrt(1 - (t export const easeInOutCirc: EasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { - return -c / 2 * (Math.sqrt(1 - t ** 2) - 1) + b; + return (-c / 2) * (Math.sqrt(1 - t ** 2) - 1) + b; } - return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; -} + return (c / 2) * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; +}; /** * Elastic easing in * @memberOf fabric.util.ease */ export const easeInElastic: EasingFunction = (t, b, c, d) => { - const s = 1.70158, a = c; + const s = 1.70158, + a = c; let p = 0; if (t === 0) { return b; @@ -207,14 +228,15 @@ export const easeInElastic: EasingFunction = (t, b, c, d) => { } const { a: normA, s: normS, p: normP } = normalize(a, c, p, s); return -elastic(normA, normS, normP, t, d) + b; -} +}; /** * Elastic easing out * @memberOf fabric.util.ease */ export const easeOutElastic: EasingFunction = (t, b, c, d) => { - const s = 1.70158, a = c; + const s = 1.70158, + a = c; let p = 0; if (t === 0) { return b; @@ -227,15 +249,20 @@ export const easeOutElastic: EasingFunction = (t, b, c, d) => { p = d * 0.3; } const { a: normA, s: normS, p: normP, c: normC } = normalize(a, c, p, s); - return normA * 2 ** (-10 * t) * Math.sin((t * d - normS) * (twoMathPi) / normP ) + normC + b; -} + return ( + normA * 2 ** (-10 * t) * Math.sin(((t * d - normS) * twoMathPi) / normP) + + normC + + b + ); +}; /** * Elastic easing in and out * @memberOf fabric.util.ease */ export const easeInOutElastic: EasingFunction = (t, b, c, d) => { - const s = 1.70158, a = c; + const s = 1.70158, + a = c; let p = 0; if (t === 0) { return b; @@ -251,21 +278,29 @@ export const easeInOutElastic: EasingFunction = (t, b, c, d) => { if (t < 1) { return -0.5 * elastic(normA, normS, normP, t, d) + b; } - return normA * Math.pow(2, -10 * (t -= 1)) * - Math.sin((t * d - normS) * (twoMathPi) / normP ) * 0.5 + normC + b; -} + return ( + normA * + Math.pow(2, -10 * (t -= 1)) * + Math.sin(((t * d - normS) * twoMathPi) / normP) * + 0.5 + + normC + + b + ); +}; /** * Backwards easing in * @memberOf fabric.util.ease */ -export const easeInBack: EasingFunction = (t, b, c, d, s = 1.70158) => c * (t /= d) * t * ((s + 1) * t - s) + b; +export const easeInBack: EasingFunction = (t, b, c, d, s = 1.70158) => + c * (t /= d) * t * ((s + 1) * t - s) + b; /** * Backwards easing out * @memberOf fabric.util.ease */ -export const easeOutBack: EasingFunction = (t, b, c, d, s = 1.70158) => c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; +export const easeOutBack: EasingFunction = (t, b, c, d, s = 1.70158) => + c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; /** * Backwards easing in and out @@ -274,45 +309,42 @@ export const easeOutBack: EasingFunction = (t, b, c, d, s = 1.70158) => c * ((t export const easeInOutBack: EasingFunction = (t, b, c, d, s = 1.70158) => { t /= d / 2; if (t < 1) { - return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + return (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b; } - return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; -} + return (c / 2) * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; +}; /** * Bouncing easing out * @memberOf fabric.util.ease */ export const easeOutBounce: EasingFunction = (t, b, c, d) => { - if ((t /= d) < (1 / 2.75)) { + if ((t /= d) < 1 / 2.75) { return c * (7.5625 * t * t) + b; + } else if (t < 2 / 2.75) { + return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b; + } else if (t < 2.5 / 2.75) { + return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b; + } else { + return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b; } - else if (t < (2 / 2.75)) { - return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; - } - else if (t < (2.5 / 2.75)) { - return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; - } - else { - return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; - } -} +}; /** * Bouncing easing in * @memberOf fabric.util.ease */ - export const easeInBounce: EasingFunction = (t, b, c, d) => c - easeOutBounce(d - t, 0, c, d) + b; +export const easeInBounce: EasingFunction = (t, b, c, d) => + c - easeOutBounce(d - t, 0, c, d) + b; /** * Bouncing easing in and out * @memberOf fabric.util.ease */ export const easeInOutBounce: EasingFunction = (t, b, c, d) => - t < d / 2 ? - easeInBounce(t * 2, 0, c, d) * 0.5 + b : - easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; - + t < d / 2 + ? easeInBounce(t * 2, 0, c, d) * 0.5 + b + : easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; /** * Quadratic easing in @@ -324,22 +356,24 @@ export const easeInQuad: EasingFunction = (t, b, c, d) => c * (t /= d) * t + b; * Quadratic easing out * @memberOf fabric.util.ease */ -export const easeOutQuad: EasingFunction = (t, b, c, d) => -c * (t /= d) * (t - 2) + b; +export const easeOutQuad: EasingFunction = (t, b, c, d) => + -c * (t /= d) * (t - 2) + b; /** * Quadratic easing in and out * @memberOf fabric.util.ease */ export const easeInOutQuad: EasingFunction = (t, b, c, d) => { - t /= (d / 2); + t /= d / 2; if (t < 1) { - return c / 2 * t ** 2 + b; + return (c / 2) * t ** 2 + b; } - return -c / 2 * ((--t) * (t - 2) - 1) + b; + return (-c / 2) * (--t * (t - 2) - 1) + b; }; /** * Cubic easing in * @memberOf fabric.util.ease */ -export const easeInCubic: EasingFunction = (t, b, c, d) => c * (t /= d) * t * t + b; +export const easeInCubic: EasingFunction = (t, b, c, d) => + c * (t /= d) * t * t + b; diff --git a/src/util/animate.ts b/src/util/animate.ts index 9e37645d21d..2c272094a57 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -1,9 +1,8 @@ import { fabric } from '../../HEADER'; import { runningAnimations } from './animation_registry'; import { noop } from '../constants'; -import { WithReturnType } from "../typedefs"; -import { defaultEasing, EasingFunction } from "./anim_ease"; -import {mergeWithDefaults} from "./lang_object"; +import { WithReturnType } from '../typedefs'; +import { defaultEasing, EasingFunction } from './anim_ease'; /** * Callback called every frame @@ -11,7 +10,11 @@ import {mergeWithDefaults} from "./lang_object"; * @param valueRatio ratio of current value to animation max value. [0, 1] * @param timeRatio ratio of current ms to animation duration. [0, 1] */ -export type OnChangeCallback = (t: number | number[], valueRatio: number, timeRatio: number) => void; +export type OnChangeCallback = ( + t: number | number[], + valueRatio: number, + timeRatio: number +) => void; /** * Called to determine if animation should abort @@ -103,7 +106,9 @@ export interface AnimationCurrentState { /** * Animation context */ -export interface AnimationContext extends AnimationOptions, AnimationCurrentState { +export interface AnimationContext + extends AnimationOptions, + AnimationCurrentState { /** * Current function used to cancel the animation */ @@ -137,7 +142,9 @@ export interface AnimationContext extends AnimationOptions, AnimationCurrentStat * * @returns {CancelFunction} cancel function */ -export function animate(options: Partial = {}): CancelFunction { +export function animate( + options: Partial = {} +): CancelFunction { let cancel = false; const { @@ -172,26 +179,31 @@ export function animate(options: Partial = {}): CancelFunction const runner = function (timestamp: number) { const start = timestamp || +new Date(), - finish = start + duration, - isMany = Array.isArray(startValue), - byValue = options.byValue || ( - isMany ? - startValue.map((value, i) => (endValue as number[])[i] - value) - : (endValue as number) - startValue - ); + finish = start + duration, + isMany = Array.isArray(startValue), + byValue = + options.byValue || + (isMany + ? startValue.map((value, i) => (endValue as number[])[i] - value) + : (endValue as number) - startValue); options.onStart && options.onStart(); (function tick(ticktime) { const time = ticktime || +new Date(); - const currentTime = time > finish ? duration : (time - start), - timePerc = currentTime / duration, - current = isMany ? - startValue.map( - (_value, i) => easing(currentTime, _value, (byValue as number[])[i], duration) - ) : easing(currentTime, startValue, byValue as number, duration), - valuePerc = isMany ? Math.abs(((current as number[])[0] - startValue[0]) / (byValue as number[])[0]) - : Math.abs(((current as number) - startValue) / (byValue as number)); + const currentTime = time > finish ? duration : time - start, + timePerc = currentTime / duration, + current = isMany + ? startValue.map((_value, i) => + easing(currentTime, _value, (byValue as number[])[i], duration) + ) + : easing(currentTime, startValue, byValue as number, duration), + valuePerc = isMany + ? Math.abs( + ((current as number[])[0] - startValue[0]) / + (byValue as number[])[0] + ) + : Math.abs(((current as number) - startValue) / (byValue as number)); // update context context.currentValue = isMany ? (current as number[]).slice() : current; context.completionRate = valuePerc; @@ -206,7 +218,9 @@ export function animate(options: Partial = {}): CancelFunction } if (time > finish) { // update context - context.currentValue = isMany ? (endValue as number[]).slice() : endValue; + context.currentValue = isMany + ? (endValue as number[]).slice() + : endValue; context.completionRate = 1; context.durationRate = 1; // execute callbacks @@ -214,15 +228,14 @@ export function animate(options: Partial = {}): CancelFunction onComplete(endValue as number, 1, 1); removeFromRegistry(); return; - } - else { + } else { onChange(current, valuePerc, timePerc); requestAnimFrame(tick); } })(start); }; - if (delay > 0 ) { + if (delay > 0) { setTimeout(() => requestAnimFrame(runner), delay); } else { requestAnimFrame(runner); @@ -231,13 +244,13 @@ export function animate(options: Partial = {}): CancelFunction return context.cancel; } -const _requestAnimFrame: AnimationFrameProvider["requestAnimationFrame"] = +const _requestAnimFrame: AnimationFrameProvider['requestAnimationFrame'] = fabric.window.requestAnimationFrame || - function(callback: FrameRequestCallback) { + function (callback: FrameRequestCallback) { return fabric.window.setTimeout(callback, 1000 / 60); }; -const _cancelAnimFrame: AnimationFrameProvider["cancelAnimationFrame"] = +const _cancelAnimFrame: AnimationFrameProvider['cancelAnimationFrame'] = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout; /** diff --git a/src/util/animate_color.ts b/src/util/animate_color.ts index ff66f707a97..971196e41d0 100644 --- a/src/util/animate_color.ts +++ b/src/util/animate_color.ts @@ -1,5 +1,5 @@ //@ts-nocheck -import { Color } from "../color"; +import { Color } from '../color'; import { animate } from './animate'; // Calculate an in-between color. Returns a "rgba()" string. @@ -15,17 +15,22 @@ import { animate } from './animate'; // but begin and end aren't array anymore since we improved animate function // to handler arrays internally. function calculateColor(begin, end, pos) { - let color = 'rgba(' - + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ',' - + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ',' - + parseInt((begin[2] + pos * (end[2] - begin[2])), 10); + let color = + 'rgba(' + + parseInt(begin[0] + pos * (end[0] - begin[0]), 10) + + ',' + + parseInt(begin[1] + pos * (end[1] - begin[1]), 10) + + ',' + + parseInt(begin[2] + pos * (end[2] - begin[2]), 10); - color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); + color += + ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); color += ')'; return color; } -const defaultColorEasing = (currentTime, duration) => 1 - Math.cos(currentTime / duration * (Math.PI / 2)); +const defaultColorEasing = (currentTime, duration) => + 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); /** * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed. @@ -52,7 +57,7 @@ export function animateColor( } = {} ) { const startColor = new Color(fromColor).getSource(), - endColor = new Color(toColor).getSource(); + endColor = new Color(toColor).getSource(); return animate({ ...restOfOptions, duration, @@ -62,11 +67,8 @@ export function animateColor( easing: (currentTime, startValue, byValue, duration) => calculateColor(startValue, byValue, colorEasing(currentTime, duration)), // has to take in account for color restoring; - onComplete: (current, valuePerc, timePerc) => onComplete?.( - calculateColor(endColor, endColor, 0), - valuePerc, - timePerc - ), + onComplete: (current, valuePerc, timePerc) => + onComplete?.(calculateColor(endColor, endColor, 0), valuePerc, timePerc), onChange: (current, valuePerc, timePerc) => { if (onChange) { if (Array.isArray(current)) { @@ -78,7 +80,6 @@ export function animateColor( } onChange(current, valuePerc, timePerc); } - } + }, }); } - diff --git a/src/util/lang_object.ts b/src/util/lang_object.ts index a17533a8264..b52125f7e5b 100644 --- a/src/util/lang_object.ts +++ b/src/util/lang_object.ts @@ -1,6 +1,6 @@ //@ts-nocheck -import { fabric } from "../../HEADER"; +import { fabric } from '../../HEADER'; /** * Copies all enumerable properties of one js object to another @@ -22,37 +22,32 @@ export const extend = (destination, source, deep) => { if (!fabric.isLikelyNode && source instanceof Element) { // avoid cloning deep images, canvases, destination = source; - } - else if (Array.isArray(source)) { + } else if (Array.isArray(source)) { destination = []; for (let i = 0, len = source.length; i < len; i++) { - destination[i] = extend({ }, source[i], deep); + destination[i] = extend({}, source[i], deep); } - } - else if (source && typeof source === 'object') { + } else if (source && typeof source === 'object') { for (const property in source) { if (property === 'canvas' || property === 'group') { // we do not want to clone this props at all. // we want to keep the keys in the copy destination[property] = null; - } - else if (Object.prototype.hasOwnProperty.call(source, property)) { - destination[property] = extend({ }, source[property], deep); + } else if (Object.prototype.hasOwnProperty.call(source, property)) { + destination[property] = extend({}, source[property], deep); } } - } - else { + } else { // this sounds odd for an extend but is ok for recursive use destination = source; } - } - else { + } else { for (const property in source) { destination[property] = source[property]; } } return destination; -} +}; /** * Creates an empty object and copies all enumerable properties of another object to it @@ -64,13 +59,5 @@ export const extend = (destination, source, deep) => { */ //TODO: this function return an empty object if you try to clone null -export const clone = (object: any, deep: boolean) => deep ? extend({ }, object, deep) : { ...object }; - -export function mergeWithDefaults(target: Partial, defaults: T): T { - for(const k in defaults) { - if(target[k] === undefined || target[k] === null) { - target[k] = defaults[k] - } - } - return target; -} +export const clone = (object: any, deep: boolean) => + deep ? extend({}, object, deep) : { ...object }; From 8c39dd5970f602cab6b00d7fb892f75ee51dff25 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sat, 17 Sep 2022 19:44:15 -0500 Subject: [PATCH 005/108] Typing for color animation and fixes for animate --- src/color/color.class.ts | 4 +- src/typedefs.ts | 6 ++ src/util/animate.ts | 7 ++- src/util/animate_color.ts | 114 +++++++++++++++++++++++--------------- 4 files changed, 82 insertions(+), 49 deletions(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index 973cf77a8ce..c41721a6b65 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -3,9 +3,9 @@ import { ColorNameMap } from './color_map'; import { reHSLa, reHex, reRGBa } from './constants'; import { hue2rgb, hexify } from './util'; -type TColorSource = [number, number, number]; +export type TColorSource = [number, number, number]; -type TColorAlphaSource = [number, number, number, number]; +export type TColorAlphaSource = [number, number, number, number]; /** * @class Color common color operations diff --git a/src/typedefs.ts b/src/typedefs.ts index 08f34025f10..4d1b50c6719 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -66,6 +66,12 @@ export type TransformEvent = TEvent & }; }; +/** + * An invalid keyword and an empty string will be handled as the `anonymous` keyword. + * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes + */ +export type TCrossOrigin = '' | 'anonymous' | 'use-credentials' | null; + export type WithReturnType any, TNewReturn> = ( ...a: Parameters ) => TNewReturn; diff --git a/src/util/animate.ts b/src/util/animate.ts index 2c272094a57..e02d388139d 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -31,6 +31,11 @@ export type CancelFunction = VoidFunction; * Animation of a value or list of values */ export interface AnimationOptions { + /** + * The object this animation is being performed on + */ + target?: unknown; + /** * Called when the animation starts */ @@ -154,7 +159,7 @@ export function animate( onChange = noop, abort = noop, onComplete = noop, - endValue = 0, + endValue = 100, delay = 0, } = options; diff --git a/src/util/animate_color.ts b/src/util/animate_color.ts index 971196e41d0..6f4aa2e575d 100644 --- a/src/util/animate_color.ts +++ b/src/util/animate_color.ts @@ -1,37 +1,53 @@ -//@ts-nocheck import { Color } from '../color'; -import { animate } from './animate'; +import { + AbortCallback, + animate, + AnimationOptions, + CancelFunction, +} from './animate'; +import { TColorAlphaSource } from '../color/color.class'; -// Calculate an in-between color. Returns a "rgba()" string. -// Credit: Edwin Martin -// http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js -// const calculateColor = (begin: number[], end: number[], pos) => { -// const [r, g, b, _a] = begin.map((beg, index) => beg + pos * (end[index] - beg)); -// const a = begin && end ? parseFloat(_a) : 1; -// return `rgba(${parseInt(r, 10)},${parseInt(g, 10)},${parseInt(b, 10)},${a})`; -// } - -// color animation is broken. This function pass the tests for some reasons -// but begin and end aren't array anymore since we improved animate function -// to handler arrays internally. -function calculateColor(begin, end, pos) { - let color = - 'rgba(' + - parseInt(begin[0] + pos * (end[0] - begin[0]), 10) + - ',' + - parseInt(begin[1] + pos * (end[1] - begin[1]), 10) + - ',' + - parseInt(begin[2] + pos * (end[2] - begin[2]), 10); - - color += - ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); - color += ')'; - return color; +/** + * Calculate an in-between color. Returns a "rgba()" string. + * Credit: Edwin Martin + * http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js + * @param begin + * @param end + * @param pos + */ +function calculateColor( + begin: TColorAlphaSource, + end: TColorAlphaSource, + pos: number +) { + const [r, g, b, _a] = begin.map( + (beg, index) => beg + pos * (end[index] - beg) + ); + const a = begin && end ? _a : 1; + return `rgba(${Math.round(r)},${Math.round(g)},${Math.round(b)},${a})`; } -const defaultColorEasing = (currentTime, duration) => +/** + * @param t the current time + * @param d duration in ms + */ +export type ColorEasing = (t: number, d: number) => number; + +const defaultColorEasing: ColorEasing = (currentTime, duration) => 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); +/** + * Callback called every frame + * @param current current color + * @param valueRatio ratio of current value to animation max value. [0, 1] + * @param timeRatio ratio of current ms to animation duration. [0, 1] + */ +export type OnColorChangeCallback = ( + current: string, + valueRatio: number, + timeRatio: number +) => void; + /** * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed. * @memberOf fabric.util @@ -46,40 +62,46 @@ const defaultColorEasing = (currentTime, duration) => * @returns {Function} abort function */ export function animateColor( - fromColor, - toColor, + fromColor: string, + toColor: string, duration = 500, { colorEasing = defaultColorEasing, onComplete, onChange, - ...restOfOptions + abort, + target, + }: { + colorEasing?: ColorEasing; + onComplete?: OnColorChangeCallback; + onChange?: OnColorChangeCallback; + abort?: AbortCallback; + target?: unknown; } = {} -) { +): CancelFunction { const startColor = new Color(fromColor).getSource(), endColor = new Color(toColor).getSource(); return animate({ - ...restOfOptions, + target, duration, + abort, startValue: startColor, endValue: endColor, byValue: endColor, easing: (currentTime, startValue, byValue, duration) => - calculateColor(startValue, byValue, colorEasing(currentTime, duration)), + colorEasing(currentTime, duration), // has to take in account for color restoring; onComplete: (current, valuePerc, timePerc) => onComplete?.(calculateColor(endColor, endColor, 0), valuePerc, timePerc), - onChange: (current, valuePerc, timePerc) => { - if (onChange) { - if (Array.isArray(current)) { - return onChange( - calculateColor(current, current, 0), - valuePerc, - timePerc - ); - } - onChange(current, valuePerc, timePerc); - } - }, + onChange: (current, valuePerc, timePerc) => + onChange?.( + calculateColor( + startColor, + endColor, + valuePerc + ), + valuePerc, + timePerc + ), }); } From 2903682f5e76131cc8a40ba93ae0514f328133ac Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sun, 18 Sep 2022 18:28:11 -0500 Subject: [PATCH 006/108] resolve some comments on the PR --- package-lock.json | 48 +++++++++++------------ src/util/anim_ease.ts | 82 +++++++++++++++++++-------------------- src/util/animate.ts | 23 ++++++----- src/util/animate_color.ts | 6 +-- 4 files changed, 77 insertions(+), 82 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1168e372953..ca6f1714418 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3983,9 +3983,9 @@ } }, "node_modules/inquirer-checkbox-plus-prompt/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true, "engines": { "node": ">=4" @@ -5165,9 +5165,9 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "node_modules/minipass": { @@ -5208,9 +5208,9 @@ } }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", "dev": true, "engines": { "node": "*" @@ -6816,9 +6816,9 @@ } }, "node_modules/terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz", + "integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.2", @@ -10525,9 +10525,9 @@ "dev": true }, "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "chardet": { @@ -11384,9 +11384,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "minipass": { @@ -11415,9 +11415,9 @@ "devOptional": true }, "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", "dev": true }, "ms": { @@ -12591,9 +12591,9 @@ } }, "terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz", + "integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.2", diff --git a/src/util/anim_ease.ts b/src/util/anim_ease.ts index 5f732b2b0be..ad31a45aedf 100644 --- a/src/util/anim_ease.ts +++ b/src/util/anim_ease.ts @@ -2,17 +2,17 @@ import { twoMathPi, halfPI } from '../constants'; /** * An easing function - * @param t current "time"/ms elapsed - * @param b start/"base" value - * @param c increment/"change"/"completion rate"/magnitude - * @param d "duration" + * @param currentTime ms elapsed + * @param startValue + * @param byValue increment/change/"completion rate"/magnitude + * @param duration in ms * @returns next value */ -export type EasingFunction = ( - t: number, - b: number, - c: number, - d: number +export type TEasingFunction = ( + currentTime: number, + startValue: number, + byValue: number, + duration: number ) => number; /** @@ -55,21 +55,21 @@ const elastic = ( /** * Default sinusoidal easing */ -export const defaultEasing: EasingFunction = (t, b, c, d) => - -c * Math.cos((t / d) * (Math.PI / 2)) + c + b; +export const defaultEasing: TEasingFunction = (t, b, c, d) => + -c * Math.cos((t / d) * halfPI) + c + b; /** * Cubic easing out * @memberOf fabric.util.ease */ -export const easeOutCubic: EasingFunction = (t, b, c, d) => +export const easeOutCubic: TEasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 2 + 1) + b; /** * Cubic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutCubic: EasingFunction = (t, b, c, d) => { +export const easeInOutCubic: TEasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { return (c / 2) * t ** 3 + b; @@ -81,21 +81,21 @@ export const easeInOutCubic: EasingFunction = (t, b, c, d) => { * Quartic easing in * @memberOf fabric.util.ease */ -export const easeInQuart: EasingFunction = (t, b, c, d) => +export const easeInQuart: TEasingFunction = (t, b, c, d) => c * (t /= d) * t ** 3 + b; /** * Quartic easing out * @memberOf fabric.util.ease */ -export const easeOutQuart: EasingFunction = (t, b, c, d) => +export const easeOutQuart: TEasingFunction = (t, b, c, d) => -c * ((t = t / d - 1) * t ** 3 - 1) + b; /** * Quartic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutQuart: EasingFunction = (t, b, c, d) => { +export const easeInOutQuart: TEasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { return (c / 2) * t ** 4 + b; @@ -107,21 +107,21 @@ export const easeInOutQuart: EasingFunction = (t, b, c, d) => { * Quintic easing in * @memberOf fabric.util.ease */ -export const easeInQuint: EasingFunction = (t, b, c, d) => +export const easeInQuint: TEasingFunction = (t, b, c, d) => c * (t /= d) * t ** 4 + b; /** * Quintic easing out * @memberOf fabric.util.ease */ -export const easeOutQuint: EasingFunction = (t, b, c, d) => +export const easeOutQuint: TEasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 4 + 1) + b; /** * Quintic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutQuint: EasingFunction = (t, b, c, d) => { +export const easeInOutQuint: TEasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { return (c / 2) * t ** 5 + b; @@ -133,42 +133,42 @@ export const easeInOutQuint: EasingFunction = (t, b, c, d) => { * Sinusoidal easing in * @memberOf fabric.util.ease */ -export const easeInSine: EasingFunction = (t, b, c, d) => +export const easeInSine: TEasingFunction = (t, b, c, d) => -c * Math.cos((t / d) * halfPI) + c + b; /** * Sinusoidal easing out * @memberOf fabric.util.ease */ -export const easeOutSine: EasingFunction = (t, b, c, d) => +export const easeOutSine: TEasingFunction = (t, b, c, d) => c * Math.sin((t / d) * halfPI) + b; /** * Sinusoidal easing in and out * @memberOf fabric.util.ease */ -export const easeInOutSine: EasingFunction = (t, b, c, d) => +export const easeInOutSine: TEasingFunction = (t, b, c, d) => (-c / 2) * (Math.cos((Math.PI * t) / d) - 1) + b; /** * Exponential easing in * @memberOf fabric.util.ease */ -export const easeInExpo: EasingFunction = (t, b, c, d) => +export const easeInExpo: TEasingFunction = (t, b, c, d) => t === 0 ? b : c * 2 ** (10 * (t / d - 1)) + b; /** * Exponential easing out * @memberOf fabric.util.ease */ -export const easeOutExpo: EasingFunction = (t, b, c, d) => +export const easeOutExpo: TEasingFunction = (t, b, c, d) => t === d ? b + c : c * -(2 ** ((-10 * t) / d) + 1) + b; /** * Exponential easing in and out * @memberOf fabric.util.ease */ -export const easeInOutExpo: EasingFunction = (t, b, c, d) => { +export const easeInOutExpo: TEasingFunction = (t, b, c, d) => { if (t === 0) { return b; } @@ -186,21 +186,21 @@ export const easeInOutExpo: EasingFunction = (t, b, c, d) => { * Circular easing in * @memberOf fabric.util.ease */ -export const easeInCirc: EasingFunction = (t, b, c, d) => +export const easeInCirc: TEasingFunction = (t, b, c, d) => -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; /** * Circular easing out * @memberOf fabric.util.ease */ -export const easeOutCirc: EasingFunction = (t, b, c, d) => +export const easeOutCirc: TEasingFunction = (t, b, c, d) => c * Math.sqrt(1 - (t = t / d - 1) * t) + b; /** * Circular easing in and out * @memberOf fabric.util.ease */ -export const easeInOutCirc: EasingFunction = (t, b, c, d) => { +export const easeInOutCirc: TEasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { return (-c / 2) * (Math.sqrt(1 - t ** 2) - 1) + b; @@ -212,7 +212,7 @@ export const easeInOutCirc: EasingFunction = (t, b, c, d) => { * Elastic easing in * @memberOf fabric.util.ease */ -export const easeInElastic: EasingFunction = (t, b, c, d) => { +export const easeInElastic: TEasingFunction = (t, b, c, d) => { const s = 1.70158, a = c; let p = 0; @@ -234,7 +234,7 @@ export const easeInElastic: EasingFunction = (t, b, c, d) => { * Elastic easing out * @memberOf fabric.util.ease */ -export const easeOutElastic: EasingFunction = (t, b, c, d) => { +export const easeOutElastic: TEasingFunction = (t, b, c, d) => { const s = 1.70158, a = c; let p = 0; @@ -260,7 +260,7 @@ export const easeOutElastic: EasingFunction = (t, b, c, d) => { * Elastic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutElastic: EasingFunction = (t, b, c, d) => { +export const easeInOutElastic: TEasingFunction = (t, b, c, d) => { const s = 1.70158, a = c; let p = 0; @@ -292,21 +292,21 @@ export const easeInOutElastic: EasingFunction = (t, b, c, d) => { * Backwards easing in * @memberOf fabric.util.ease */ -export const easeInBack: EasingFunction = (t, b, c, d, s = 1.70158) => +export const easeInBack: TEasingFunction = (t, b, c, d, s = 1.70158) => c * (t /= d) * t * ((s + 1) * t - s) + b; /** * Backwards easing out * @memberOf fabric.util.ease */ -export const easeOutBack: EasingFunction = (t, b, c, d, s = 1.70158) => +export const easeOutBack: TEasingFunction = (t, b, c, d, s = 1.70158) => c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; /** * Backwards easing in and out * @memberOf fabric.util.ease */ -export const easeInOutBack: EasingFunction = (t, b, c, d, s = 1.70158) => { +export const easeInOutBack: TEasingFunction = (t, b, c, d, s = 1.70158) => { t /= d / 2; if (t < 1) { return (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b; @@ -318,7 +318,7 @@ export const easeInOutBack: EasingFunction = (t, b, c, d, s = 1.70158) => { * Bouncing easing out * @memberOf fabric.util.ease */ -export const easeOutBounce: EasingFunction = (t, b, c, d) => { +export const easeOutBounce: TEasingFunction = (t, b, c, d) => { if ((t /= d) < 1 / 2.75) { return c * (7.5625 * t * t) + b; } else if (t < 2 / 2.75) { @@ -334,14 +334,14 @@ export const easeOutBounce: EasingFunction = (t, b, c, d) => { * Bouncing easing in * @memberOf fabric.util.ease */ -export const easeInBounce: EasingFunction = (t, b, c, d) => +export const easeInBounce: TEasingFunction = (t, b, c, d) => c - easeOutBounce(d - t, 0, c, d) + b; /** * Bouncing easing in and out * @memberOf fabric.util.ease */ -export const easeInOutBounce: EasingFunction = (t, b, c, d) => +export const easeInOutBounce: TEasingFunction = (t, b, c, d) => t < d / 2 ? easeInBounce(t * 2, 0, c, d) * 0.5 + b : easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; @@ -350,20 +350,20 @@ export const easeInOutBounce: EasingFunction = (t, b, c, d) => * Quadratic easing in * @memberOf fabric.util.ease */ -export const easeInQuad: EasingFunction = (t, b, c, d) => c * (t /= d) * t + b; +export const easeInQuad: TEasingFunction = (t, b, c, d) => c * (t /= d) * t + b; /** * Quadratic easing out * @memberOf fabric.util.ease */ -export const easeOutQuad: EasingFunction = (t, b, c, d) => +export const easeOutQuad: TEasingFunction = (t, b, c, d) => -c * (t /= d) * (t - 2) + b; /** * Quadratic easing in and out * @memberOf fabric.util.ease */ -export const easeInOutQuad: EasingFunction = (t, b, c, d) => { +export const easeInOutQuad: TEasingFunction = (t, b, c, d) => { t /= d / 2; if (t < 1) { return (c / 2) * t ** 2 + b; @@ -375,5 +375,5 @@ export const easeInOutQuad: EasingFunction = (t, b, c, d) => { * Cubic easing in * @memberOf fabric.util.ease */ -export const easeInCubic: EasingFunction = (t, b, c, d) => +export const easeInCubic: TEasingFunction = (t, b, c, d) => c * (t /= d) * t * t + b; diff --git a/src/util/animate.ts b/src/util/animate.ts index e02d388139d..cbcc2f3a2a5 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -2,7 +2,7 @@ import { fabric } from '../../HEADER'; import { runningAnimations } from './animation_registry'; import { noop } from '../constants'; import { WithReturnType } from '../typedefs'; -import { defaultEasing, EasingFunction } from './anim_ease'; +import { defaultEasing, TEasingFunction } from './anim_ease'; /** * Callback called every frame @@ -55,7 +55,7 @@ export interface AnimationOptions { * Easing function * @default [defaultEasing] */ - easing: EasingFunction; + easing: TEasingFunction; /** * Function called at each frame. @@ -112,7 +112,7 @@ export interface AnimationCurrentState { * Animation context */ export interface AnimationContext - extends AnimationOptions, + extends Partial, AnimationCurrentState { /** * Current function used to cancel the animation @@ -163,9 +163,12 @@ export function animate( delay = 0, } = options; - const context: Partial = { + const context: AnimationContext = { ...options, - cancel: noop, // placeholder + cancel: function () { + cancel = true; + return removeFromRegistry(); + }, currentValue: startValue, completionRate: 0, durationRate: 0, @@ -176,10 +179,6 @@ export function animate( return index > -1 && runningAnimations.splice(index, 1)[0]; }; - context.cancel = function () { - cancel = true; - return removeFromRegistry(); - }; runningAnimations.push(context); const runner = function (timestamp: number) { @@ -194,7 +193,7 @@ export function animate( options.onStart && options.onStart(); - (function tick(ticktime) { + (function tick(ticktime: number) { const time = ticktime || +new Date(); const currentTime = time > finish ? duration : time - start, timePerc = currentTime / duration, @@ -265,9 +264,9 @@ const _cancelAnimFrame: AnimationFrameProvider['cancelAnimationFrame'] = * @param {Function} callback Callback to invoke */ export function requestAnimFrame(callback: FrameRequestCallback): number { - return _requestAnimFrame.bind(fabric.window)(callback); + return _requestAnimFrame.call(fabric.window, callback); } export function cancelAnimFrame(handle: number): void { - return _cancelAnimFrame.bind(fabric.window)(handle); + return _cancelAnimFrame.call(fabric.window, handle); } diff --git a/src/util/animate_color.ts b/src/util/animate_color.ts index 6f4aa2e575d..558d7edef28 100644 --- a/src/util/animate_color.ts +++ b/src/util/animate_color.ts @@ -95,11 +95,7 @@ export function animateColor( onComplete?.(calculateColor(endColor, endColor, 0), valuePerc, timePerc), onChange: (current, valuePerc, timePerc) => onChange?.( - calculateColor( - startColor, - endColor, - valuePerc - ), + calculateColor(startColor, endColor, valuePerc), valuePerc, timePerc ), From a0e52f241f3abf00ad3f6d68fb1bdab3959d2fc0 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sun, 18 Sep 2022 18:31:19 -0500 Subject: [PATCH 007/108] remove WithReturnType --- src/typedefs.ts | 4 ---- src/util/animate.ts | 7 +++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/typedefs.ts b/src/typedefs.ts index 4d1b50c6719..6654f01ae60 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -71,7 +71,3 @@ export type TransformEvent = TEvent & * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes */ export type TCrossOrigin = '' | 'anonymous' | 'use-credentials' | null; - -export type WithReturnType any, TNewReturn> = ( - ...a: Parameters -) => TNewReturn; diff --git a/src/util/animate.ts b/src/util/animate.ts index cbcc2f3a2a5..9c93cdbf11d 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -1,7 +1,6 @@ import { fabric } from '../../HEADER'; import { runningAnimations } from './animation_registry'; import { noop } from '../constants'; -import { WithReturnType } from '../typedefs'; import { defaultEasing, TEasingFunction } from './anim_ease'; /** @@ -10,17 +9,17 @@ import { defaultEasing, TEasingFunction } from './anim_ease'; * @param valueRatio ratio of current value to animation max value. [0, 1] * @param timeRatio ratio of current ms to animation duration. [0, 1] */ -export type OnChangeCallback = ( +export type OnChangeCallback = ( t: number | number[], valueRatio: number, timeRatio: number -) => void; +) => T; /** * Called to determine if animation should abort * @returns truthy if animation should abort */ -export type AbortCallback = WithReturnType; +export type AbortCallback = OnChangeCallback; /** * Function used for canceling an animation From d2075e69d949c4c2dc544c5c5704a852976adf96 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 19 Sep 2022 09:12:28 +0300 Subject: [PATCH 008/108] animation registry typings --- src/util/animate.ts | 5 +++-- src/util/animation_registry.ts | 21 +++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/util/animate.ts b/src/util/animate.ts index 9c93cdbf11d..7e410b7934a 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -1,6 +1,7 @@ import { fabric } from '../../HEADER'; -import { runningAnimations } from './animation_registry'; import { noop } from '../constants'; +import { TObject } from '../__types__'; +import { runningAnimations } from './animation_registry'; import { defaultEasing, TEasingFunction } from './anim_ease'; /** @@ -33,7 +34,7 @@ export interface AnimationOptions { /** * The object this animation is being performed on */ - target?: unknown; + target?: TObject | unknown; /** * Called when the animation starts diff --git a/src/util/animation_registry.ts b/src/util/animation_registry.ts index 7e4acbd5e37..1f0fbfe7f4d 100644 --- a/src/util/animation_registry.ts +++ b/src/util/animation_registry.ts @@ -1,17 +1,18 @@ -//@ts-nocheck import { fabric } from '../../HEADER'; +import { Canvas, TObject } from '../__types__'; +import { AnimationContext, CancelFunction } from './animate'; /** * Array holding all running animations * @memberof fabric * @type {AnimationContext[]} */ -class RunningAnimations extends Array { +class RunningAnimations extends Array { /** * cancel all running animations at the next requestAnimFrame * @returns {AnimationContext[]} */ - cancelAll(): any[] { + cancelAll() { const animations = this.splice(0); animations.forEach((animation) => animation.cancel()); return animations; @@ -22,14 +23,14 @@ class RunningAnimations extends Array { * @param {fabric.Canvas} canvas * @returns {AnimationContext[]} */ - cancelByCanvas(canvas: any) { + cancelByCanvas(canvas: Canvas) { if (!canvas) { return []; } const cancelled = this.filter( (animation) => typeof animation.target === 'object' && - animation.target.canvas === canvas + (animation.target as TObject)?.canvas === canvas ); cancelled.forEach((animation) => animation.cancel()); return cancelled; @@ -40,7 +41,7 @@ class RunningAnimations extends Array { * @param {*} target * @returns {AnimationContext[]} */ - cancelByTarget(target) { + cancelByTarget(target: AnimationContext['target']) { const cancelled = this.findAnimationsByTarget(target); cancelled.forEach((animation) => animation.cancel()); return cancelled; @@ -51,8 +52,8 @@ class RunningAnimations extends Array { * @param {CancelFunction} cancelFunc the function returned by animate * @returns {number} */ - findAnimationIndex(cancelFunc) { - return this.indexOf(this.findAnimation(cancelFunc)); + findAnimationIndex(cancelFunc: CancelFunction) { + return this.findIndex((animation) => animation.cancel === cancelFunc); } /** @@ -60,7 +61,7 @@ class RunningAnimations extends Array { * @param {CancelFunction} cancelFunc the function returned by animate * @returns {AnimationContext | undefined} animation's options object */ - findAnimation(cancelFunc) { + findAnimation(cancelFunc: CancelFunction) { return this.find((animation) => animation.cancel === cancelFunc); } @@ -69,7 +70,7 @@ class RunningAnimations extends Array { * @param {*} target the object that is assigned to the target property of the animation context * @returns {AnimationContext[]} array of animation options object associated with target */ - findAnimationsByTarget(target) { + findAnimationsByTarget(target: AnimationContext['target']) { if (!target) { return []; } From 8895a5f011d468090e48c25a665a2c402de06212 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Fri, 23 Sep 2022 16:59:54 -0500 Subject: [PATCH 009/108] revert animate_color.ts TODO: will modify this in another PR --- src/util/animate_color.ts | 110 ++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 64 deletions(-) diff --git a/src/util/animate_color.ts b/src/util/animate_color.ts index 558d7edef28..971196e41d0 100644 --- a/src/util/animate_color.ts +++ b/src/util/animate_color.ts @@ -1,52 +1,36 @@ +//@ts-nocheck import { Color } from '../color'; -import { - AbortCallback, - animate, - AnimationOptions, - CancelFunction, -} from './animate'; -import { TColorAlphaSource } from '../color/color.class'; +import { animate } from './animate'; -/** - * Calculate an in-between color. Returns a "rgba()" string. - * Credit: Edwin Martin - * http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js - * @param begin - * @param end - * @param pos - */ -function calculateColor( - begin: TColorAlphaSource, - end: TColorAlphaSource, - pos: number -) { - const [r, g, b, _a] = begin.map( - (beg, index) => beg + pos * (end[index] - beg) - ); - const a = begin && end ? _a : 1; - return `rgba(${Math.round(r)},${Math.round(g)},${Math.round(b)},${a})`; -} +// Calculate an in-between color. Returns a "rgba()" string. +// Credit: Edwin Martin +// http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js +// const calculateColor = (begin: number[], end: number[], pos) => { +// const [r, g, b, _a] = begin.map((beg, index) => beg + pos * (end[index] - beg)); +// const a = begin && end ? parseFloat(_a) : 1; +// return `rgba(${parseInt(r, 10)},${parseInt(g, 10)},${parseInt(b, 10)},${a})`; +// } -/** - * @param t the current time - * @param d duration in ms - */ -export type ColorEasing = (t: number, d: number) => number; +// color animation is broken. This function pass the tests for some reasons +// but begin and end aren't array anymore since we improved animate function +// to handler arrays internally. +function calculateColor(begin, end, pos) { + let color = + 'rgba(' + + parseInt(begin[0] + pos * (end[0] - begin[0]), 10) + + ',' + + parseInt(begin[1] + pos * (end[1] - begin[1]), 10) + + ',' + + parseInt(begin[2] + pos * (end[2] - begin[2]), 10); -const defaultColorEasing: ColorEasing = (currentTime, duration) => - 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); + color += + ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); + color += ')'; + return color; +} -/** - * Callback called every frame - * @param current current color - * @param valueRatio ratio of current value to animation max value. [0, 1] - * @param timeRatio ratio of current ms to animation duration. [0, 1] - */ -export type OnColorChangeCallback = ( - current: string, - valueRatio: number, - timeRatio: number -) => void; +const defaultColorEasing = (currentTime, duration) => + 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); /** * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed. @@ -62,42 +46,40 @@ export type OnColorChangeCallback = ( * @returns {Function} abort function */ export function animateColor( - fromColor: string, - toColor: string, + fromColor, + toColor, duration = 500, { colorEasing = defaultColorEasing, onComplete, onChange, - abort, - target, - }: { - colorEasing?: ColorEasing; - onComplete?: OnColorChangeCallback; - onChange?: OnColorChangeCallback; - abort?: AbortCallback; - target?: unknown; + ...restOfOptions } = {} -): CancelFunction { +) { const startColor = new Color(fromColor).getSource(), endColor = new Color(toColor).getSource(); return animate({ - target, + ...restOfOptions, duration, - abort, startValue: startColor, endValue: endColor, byValue: endColor, easing: (currentTime, startValue, byValue, duration) => - colorEasing(currentTime, duration), + calculateColor(startValue, byValue, colorEasing(currentTime, duration)), // has to take in account for color restoring; onComplete: (current, valuePerc, timePerc) => onComplete?.(calculateColor(endColor, endColor, 0), valuePerc, timePerc), - onChange: (current, valuePerc, timePerc) => - onChange?.( - calculateColor(startColor, endColor, valuePerc), - valuePerc, - timePerc - ), + onChange: (current, valuePerc, timePerc) => { + if (onChange) { + if (Array.isArray(current)) { + return onChange( + calculateColor(current, current, 0), + valuePerc, + timePerc + ); + } + onChange(current, valuePerc, timePerc); + } + }, }); } From 4cc9713478d205a5597fb58864f774b7a55cd8f9 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Fri, 23 Sep 2022 17:01:54 -0500 Subject: [PATCH 010/108] more explicit typing in animation_registry.ts --- src/util/animation_registry.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/util/animation_registry.ts b/src/util/animation_registry.ts index 1f0fbfe7f4d..020cd968d45 100644 --- a/src/util/animation_registry.ts +++ b/src/util/animation_registry.ts @@ -12,7 +12,7 @@ class RunningAnimations extends Array { * cancel all running animations at the next requestAnimFrame * @returns {AnimationContext[]} */ - cancelAll() { + cancelAll(): AnimationContext[] { const animations = this.splice(0); animations.forEach((animation) => animation.cancel()); return animations; @@ -23,7 +23,7 @@ class RunningAnimations extends Array { * @param {fabric.Canvas} canvas * @returns {AnimationContext[]} */ - cancelByCanvas(canvas: Canvas) { + cancelByCanvas(canvas: Canvas): AnimationContext[] { if (!canvas) { return []; } @@ -41,7 +41,7 @@ class RunningAnimations extends Array { * @param {*} target * @returns {AnimationContext[]} */ - cancelByTarget(target: AnimationContext['target']) { + cancelByTarget(target: AnimationContext['target']): AnimationContext[] { const cancelled = this.findAnimationsByTarget(target); cancelled.forEach((animation) => animation.cancel()); return cancelled; @@ -52,7 +52,7 @@ class RunningAnimations extends Array { * @param {CancelFunction} cancelFunc the function returned by animate * @returns {number} */ - findAnimationIndex(cancelFunc: CancelFunction) { + findAnimationIndex(cancelFunc: CancelFunction): number { return this.findIndex((animation) => animation.cancel === cancelFunc); } @@ -61,7 +61,7 @@ class RunningAnimations extends Array { * @param {CancelFunction} cancelFunc the function returned by animate * @returns {AnimationContext | undefined} animation's options object */ - findAnimation(cancelFunc: CancelFunction) { + findAnimation(cancelFunc: CancelFunction): AnimationContext | undefined { return this.find((animation) => animation.cancel === cancelFunc); } @@ -70,7 +70,9 @@ class RunningAnimations extends Array { * @param {*} target the object that is assigned to the target property of the animation context * @returns {AnimationContext[]} array of animation options object associated with target */ - findAnimationsByTarget(target: AnimationContext['target']) { + findAnimationsByTarget( + target: AnimationContext['target'] + ): AnimationContext[] { if (!target) { return []; } From 1ddd44995816276c7cef606a2d30d4789a2d00a0 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sat, 24 Sep 2022 14:12:48 -0500 Subject: [PATCH 011/108] more explicit typing in animation_registry.ts --- src/util/animate.ts | 22 +++++++++++----------- src/util/animation_registry.ts | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/util/animate.ts b/src/util/animate.ts index 7e410b7934a..27d59654494 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -10,22 +10,22 @@ import { defaultEasing, TEasingFunction } from './anim_ease'; * @param valueRatio ratio of current value to animation max value. [0, 1] * @param timeRatio ratio of current ms to animation duration. [0, 1] */ -export type OnChangeCallback = ( - t: number | number[], +export type TOnAnimationChangeCallback = ( + t: State, valueRatio: number, timeRatio: number -) => T; +) => Return; /** * Called to determine if animation should abort * @returns truthy if animation should abort */ -export type AbortCallback = OnChangeCallback; +export type TAbortCallback = TOnAnimationChangeCallback; /** * Function used for canceling an animation */ -export type CancelFunction = VoidFunction; +export type TCancelFunction = VoidFunction; /** * Animation of a value or list of values @@ -44,12 +44,12 @@ export interface AnimationOptions { /** * Called at each frame of the animation */ - onChange: OnChangeCallback; + onChange: TOnAnimationChangeCallback; /** * Called after the last frame of the animation */ - onComplete: OnChangeCallback; + onComplete: TOnAnimationChangeCallback; /** * Easing function @@ -61,7 +61,7 @@ export interface AnimationOptions { * Function called at each frame. * If it returns true, abort */ - abort: AbortCallback; + abort: TAbortCallback; /** * Starting value(s) @@ -117,7 +117,7 @@ export interface AnimationContext /** * Current function used to cancel the animation */ - cancel: CancelFunction; + cancel: TCancelFunction; } /** @@ -145,11 +145,11 @@ export interface AnimationContext * } * }); * - * @returns {CancelFunction} cancel function + * @returns {TCancelFunction} cancel function */ export function animate( options: Partial = {} -): CancelFunction { +): TCancelFunction { let cancel = false; const { diff --git a/src/util/animation_registry.ts b/src/util/animation_registry.ts index 020cd968d45..791083e8751 100644 --- a/src/util/animation_registry.ts +++ b/src/util/animation_registry.ts @@ -1,6 +1,6 @@ import { fabric } from '../../HEADER'; import { Canvas, TObject } from '../__types__'; -import { AnimationContext, CancelFunction } from './animate'; +import { AnimationContext, TCancelFunction } from './animate'; /** * Array holding all running animations @@ -49,19 +49,19 @@ class RunningAnimations extends Array { /** * - * @param {CancelFunction} cancelFunc the function returned by animate + * @param {TCancelFunction} cancelFunc the function returned by animate * @returns {number} */ - findAnimationIndex(cancelFunc: CancelFunction): number { + findAnimationIndex(cancelFunc: TCancelFunction): number { return this.findIndex((animation) => animation.cancel === cancelFunc); } /** * - * @param {CancelFunction} cancelFunc the function returned by animate + * @param {TCancelFunction} cancelFunc the function returned by animate * @returns {AnimationContext | undefined} animation's options object */ - findAnimation(cancelFunc: CancelFunction): AnimationContext | undefined { + findAnimation(cancelFunc: TCancelFunction): AnimationContext | undefined { return this.find((animation) => animation.cancel === cancelFunc); } From 757fd7a8119a5c3ce22563abf7776b725e9f5356 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sat, 24 Sep 2022 14:20:26 -0500 Subject: [PATCH 012/108] fix comments --- src/util/animate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/animate.ts b/src/util/animate.ts index 27d59654494..e284f1ec9a4 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -99,11 +99,11 @@ export interface AnimationCurrentState { */ currentValue: number | number[]; /** - * Same as valueRatio from @see OnChangeCallback + * Same as valueRatio from @see TOnAnimationChangeCallback */ completionRate: number; /** - * Same as completionRatio from @see OnChangeCallback + * Same as completionRatio from @see TOnAnimationChangeCallback */ durationRate: number; } From 6ebfbdae0ed7ce0971d51a1d45009ec3701a97f0 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sat, 1 Oct 2022 13:06:49 -0500 Subject: [PATCH 013/108] changelog update --- CHANGELOG.md | 1 + src/util/animate.ts | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8476b7f2eab..1a681f3db2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- chore(TS): `animate` and `AnimationRegistry` typing [#8297](https://github.com/fabricjs/fabric.js/pull/8297) - ci(build): safeguard concurrent unlocking [#8309](https://github.com/fabricjs/fabric.js/pull/8309) - ci(): update stale bot [#8307](https://github.com/fabricjs/fabric.js/pull/8307) - ci(test): await golden generation in visual tests [#8284](https://github.com/fabricjs/fabric.js/pull/8284) diff --git a/src/util/animate.ts b/src/util/animate.ts index e284f1ec9a4..d3fe7c354ca 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -10,11 +10,10 @@ import { defaultEasing, TEasingFunction } from './anim_ease'; * @param valueRatio ratio of current value to animation max value. [0, 1] * @param timeRatio ratio of current ms to animation duration. [0, 1] */ -export type TOnAnimationChangeCallback = ( - t: State, - valueRatio: number, - timeRatio: number -) => Return; +export type TOnAnimationChangeCallback< + Return = void, + State = number | number[] +> = (t: State, valueRatio: number, timeRatio: number) => Return; /** * Called to determine if animation should abort From f2b0f8938663a4a8cfe9217f8a1922a156bc8935 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sat, 8 Oct 2022 12:23:14 -0500 Subject: [PATCH 014/108] made the animate function more typesafe made the animation state functions more generic --- src/util/animate.ts | 267 +++++++++++++++++++++------------ src/util/animation_registry.ts | 16 +- 2 files changed, 182 insertions(+), 101 deletions(-) diff --git a/src/util/animate.ts b/src/util/animate.ts index d3fe7c354ca..63d1841ba89 100644 --- a/src/util/animate.ts +++ b/src/util/animate.ts @@ -1,6 +1,5 @@ import { fabric } from '../../HEADER'; import { noop } from '../constants'; -import { TObject } from '../__types__'; import { runningAnimations } from './animation_registry'; import { defaultEasing, TEasingFunction } from './anim_ease'; @@ -19,21 +18,41 @@ export type TOnAnimationChangeCallback< * Called to determine if animation should abort * @returns truthy if animation should abort */ -export type TAbortCallback = TOnAnimationChangeCallback; +export type TAbortCallback = + TOnAnimationChangeCallback; /** * Function used for canceling an animation */ export type TCancelFunction = VoidFunction; +export interface AnimationBounds { + /** + * Starting value(s) + */ + startValue: State; + + /** + * Ending value(s) + * @default 100 + */ + endValue: State; + + /** + * Value(s) to increment/decrement the value(s) by + * @default [endValue - startValue] + */ + byValue: State; +} + /** * Animation of a value or list of values */ -export interface AnimationOptions { +export interface AnimationOptions extends AnimationBounds { /** * The object this animation is being performed on */ - target?: TObject | unknown; + target?: unknown; /** * Called when the animation starts @@ -43,12 +62,12 @@ export interface AnimationOptions { /** * Called at each frame of the animation */ - onChange: TOnAnimationChangeCallback; + onChange: TOnAnimationChangeCallback; /** * Called after the last frame of the animation */ - onComplete: TOnAnimationChangeCallback; + onComplete: TOnAnimationChangeCallback; /** * Easing function @@ -60,24 +79,7 @@ export interface AnimationOptions { * Function called at each frame. * If it returns true, abort */ - abort: TAbortCallback; - - /** - * Starting value(s) - */ - startValue: number | number[]; - - /** - * Ending value(s) - * @default 100 - */ - endValue: number | number[]; - - /** - * Value(s) to increment/decrement the value(s) by - * @default [endValue - startValue] - */ - byValue: number | number[]; + abort: TAbortCallback; /** * Duration of the animation in ms @@ -92,11 +94,11 @@ export interface AnimationOptions { delay: number; } -export interface AnimationCurrentState { +export interface AnimationCurrentState { /** * Current values */ - currentValue: number | number[]; + currentValue: State; /** * Same as valueRatio from @see TOnAnimationChangeCallback */ @@ -110,9 +112,9 @@ export interface AnimationCurrentState { /** * Animation context */ -export interface AnimationContext - extends Partial, - AnimationCurrentState { +export interface AnimationContext + extends Partial>, + AnimationCurrentState { /** * Current function used to cancel the animation */ @@ -147,32 +149,87 @@ export interface AnimationContext * @returns {TCancelFunction} cancel function */ export function animate( - options: Partial = {} + options: Partial | AnimationOptions> = {} ): TCancelFunction { let cancel = false; - const { - startValue = 0, - duration = 500, - easing = defaultEasing, - onChange = noop, - abort = noop, - onComplete = noop, - endValue = 100, - delay = 0, - } = options; - - const context: AnimationContext = { - ...options, - cancel: function () { - cancel = true; - return removeFromRegistry(); - }, - currentValue: startValue, - completionRate: 0, - durationRate: 0, + const isMulti = function isMulti< + Single extends Partial>, + Multi extends Partial> + >(x: Single | Multi): x is Multi { + return Array.isArray(x.startValue); }; + const isMany = isMulti(options); + + const { duration = 500, easing = defaultEasing, delay = 0 } = options; + + // let startValue: number | number[], endValue: number | number[]; + + let bounds: + | (AnimationBounds & + Pick, 'abort' | 'onChange' | 'onComplete'>) + | (AnimationBounds & + Pick, 'abort' | 'onChange' | 'onComplete'>); + + let context: AnimationContext | AnimationContext; + if (isMany) { + let byValue = options.byValue; + const startValue = options.startValue ?? [0], + endValue = options.endValue ?? [100]; + + if (!byValue) { + byValue = new Array(startValue.length); + // using map here means that we need to use a closure, but TS isn't smart enough to realize + // that bounds is still a AnimationBounds inside the closure + for (let i = 0; i < startValue.length; i++) { + byValue[i] = endValue[i] - startValue[i]; + } + } + + bounds = { + startValue, + endValue, + byValue, + onChange: options.onChange ?? noop, + onComplete: options.onComplete ?? noop, + abort: options.abort ?? noop, + }; + + context = { + ...options, + cancel: function () { + cancel = true; + return removeFromRegistry(); + }, + completionRate: 0, + durationRate: 0, + currentValue: bounds.startValue, + }; + } else { + const startValue = options.startValue ?? 0, + endValue = options.endValue ?? 100, + byValue = options.byValue || endValue - startValue; + bounds = { + startValue, + endValue, + byValue, + onChange: options.onChange ?? noop, + onComplete: options.onComplete ?? noop, + abort: options.abort ?? noop, + }; + context = { + ...options, + cancel: function () { + cancel = true; + return removeFromRegistry(); + }, + completionRate: 0, + durationRate: 0, + currentValue: bounds.startValue, + }; + } + const removeFromRegistry = () => { const index = runningAnimations.indexOf(context); return index > -1 && runningAnimations.splice(index, 1)[0]; @@ -182,58 +239,82 @@ export function animate( const runner = function (timestamp: number) { const start = timestamp || +new Date(), - finish = start + duration, - isMany = Array.isArray(startValue), - byValue = - options.byValue || - (isMany - ? startValue.map((value, i) => (endValue as number[])[i] - value) - : (endValue as number) - startValue); + finish = start + duration; options.onStart && options.onStart(); (function tick(ticktime: number) { const time = ticktime || +new Date(); const currentTime = time > finish ? duration : time - start, - timePerc = currentTime / duration, - current = isMany - ? startValue.map((_value, i) => - easing(currentTime, _value, (byValue as number[])[i], duration) - ) - : easing(currentTime, startValue, byValue as number, duration), - valuePerc = isMany - ? Math.abs( - ((current as number[])[0] - startValue[0]) / - (byValue as number[])[0] - ) - : Math.abs(((current as number) - startValue) / (byValue as number)); + timePerc = currentTime / duration; + let current: number | number[], valuePerc: number; + // update context - context.currentValue = isMany ? (current as number[]).slice() : current; - context.completionRate = valuePerc; context.durationRate = timePerc; - - if (cancel) { - return; - } - if (abort(current, valuePerc, timePerc)) { - removeFromRegistry(); - return; - } - if (time > finish) { - // update context - context.currentValue = isMany - ? (endValue as number[]).slice() - : endValue; - context.completionRate = 1; - context.durationRate = 1; - // execute callbacks - onChange(isMany ? (endValue as number[]).slice() : endValue, 1, 1); - onComplete(endValue as number, 1, 1); - removeFromRegistry(); - return; + if (isMulti(bounds)) { + current = new Array(bounds.startValue.length); + for (let i = 0; i < current.length; i++) { + current[i] = easing( + currentTime, + bounds.startValue[i], + bounds.byValue[i], + duration + ); + } + context.currentValue = current.slice(); + valuePerc = Math.abs( + (current[0] - bounds.startValue[0]) / bounds.byValue[0] + ); + if (cancel) { + return; + } + if (bounds.abort(current, valuePerc, timePerc)) { + removeFromRegistry(); + return; + } + if (time > finish) { + context.currentValue = bounds.endValue.slice(); + bounds.onChange(bounds.endValue.slice(), 1, 1); + bounds.onComplete(bounds.endValue, 1, 1); + context.completionRate = 1; + context.durationRate = 1; + // execute callbacks + removeFromRegistry(); + } else { + bounds.onChange(current, valuePerc, timePerc); + requestAnimFrame(tick); + } } else { - onChange(current, valuePerc, timePerc); - requestAnimFrame(tick); + current = easing( + currentTime, + bounds.startValue, + bounds.byValue, + duration + ); + context.currentValue = current; + valuePerc = Math.abs( + ((current as number) - bounds.startValue) / bounds.byValue + ); + context.completionRate = valuePerc; + if (cancel) { + return; + } + if (bounds.abort(current, valuePerc, timePerc)) { + removeFromRegistry(); + return; + } + if (time > finish) { + context.currentValue = bounds.endValue; + bounds.onChange(bounds.endValue, 1, 1); + bounds.onComplete(bounds.endValue, 1, 1); + context.completionRate = 1; + context.durationRate = 1; + // execute callbacks + removeFromRegistry(); + } else { + bounds.onChange(current, valuePerc, timePerc); + requestAnimFrame(tick); + } } })(start); }; diff --git a/src/util/animation_registry.ts b/src/util/animation_registry.ts index 791083e8751..94b4f5237fa 100644 --- a/src/util/animation_registry.ts +++ b/src/util/animation_registry.ts @@ -2,17 +2,19 @@ import { fabric } from '../../HEADER'; import { Canvas, TObject } from '../__types__'; import { AnimationContext, TCancelFunction } from './animate'; +type TAnimation = AnimationContext | AnimationContext; + /** * Array holding all running animations * @memberof fabric * @type {AnimationContext[]} */ -class RunningAnimations extends Array { +class RunningAnimations extends Array { /** * cancel all running animations at the next requestAnimFrame * @returns {AnimationContext[]} */ - cancelAll(): AnimationContext[] { + cancelAll(): TAnimation[] { const animations = this.splice(0); animations.forEach((animation) => animation.cancel()); return animations; @@ -23,7 +25,7 @@ class RunningAnimations extends Array { * @param {fabric.Canvas} canvas * @returns {AnimationContext[]} */ - cancelByCanvas(canvas: Canvas): AnimationContext[] { + cancelByCanvas(canvas: Canvas): TAnimation[] { if (!canvas) { return []; } @@ -41,7 +43,7 @@ class RunningAnimations extends Array { * @param {*} target * @returns {AnimationContext[]} */ - cancelByTarget(target: AnimationContext['target']): AnimationContext[] { + cancelByTarget(target: TAnimation['target']): TAnimation[] { const cancelled = this.findAnimationsByTarget(target); cancelled.forEach((animation) => animation.cancel()); return cancelled; @@ -61,7 +63,7 @@ class RunningAnimations extends Array { * @param {TCancelFunction} cancelFunc the function returned by animate * @returns {AnimationContext | undefined} animation's options object */ - findAnimation(cancelFunc: TCancelFunction): AnimationContext | undefined { + findAnimation(cancelFunc: TCancelFunction): TAnimation | undefined { return this.find((animation) => animation.cancel === cancelFunc); } @@ -70,9 +72,7 @@ class RunningAnimations extends Array { * @param {*} target the object that is assigned to the target property of the animation context * @returns {AnimationContext[]} array of animation options object associated with target */ - findAnimationsByTarget( - target: AnimationContext['target'] - ): AnimationContext[] { + findAnimationsByTarget(target: TAnimation['target']): TAnimation[] { if (!target) { return []; } From 1b49cb3a4a9b228117aa5c7a8fd0c80a0f7ab0ca Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 12 Oct 2022 18:32:29 +0300 Subject: [PATCH 015/108] refactor(): animation directory --- src/util/{ => animation}/animate.ts | 193 +++--------------- src/util/{ => animation}/animate_color.ts | 2 +- .../{ => animation}/animation_registry.ts | 6 +- .../{anim_ease.ts => animation/easing.ts} | 18 +- src/util/animation/index.ts | 5 + src/util/animation/types.ts | 142 +++++++++++++ src/util/misc/misc.ts | 155 +++++++------- 7 files changed, 265 insertions(+), 256 deletions(-) rename src/util/{ => animation}/animate.ts (61%) rename src/util/{ => animation}/animate_color.ts (98%) rename src/util/{ => animation}/animation_registry.ts (94%) rename src/util/{anim_ease.ts => animation/easing.ts} (95%) create mode 100644 src/util/animation/index.ts create mode 100644 src/util/animation/types.ts diff --git a/src/util/animate.ts b/src/util/animation/animate.ts similarity index 61% rename from src/util/animate.ts rename to src/util/animation/animate.ts index 63d1841ba89..1c223172b77 100644 --- a/src/util/animate.ts +++ b/src/util/animation/animate.ts @@ -1,167 +1,48 @@ -import { fabric } from '../../HEADER'; -import { noop } from '../constants'; +import { fabric } from '../../../HEADER'; +import { noop } from '../../constants'; import { runningAnimations } from './animation_registry'; -import { defaultEasing, TEasingFunction } from './anim_ease'; - -/** - * Callback called every frame - * @param t current "time"/ms elapsed. multivalue - * @param valueRatio ratio of current value to animation max value. [0, 1] - * @param timeRatio ratio of current ms to animation duration. [0, 1] - */ -export type TOnAnimationChangeCallback< - Return = void, - State = number | number[] -> = (t: State, valueRatio: number, timeRatio: number) => Return; - -/** - * Called to determine if animation should abort - * @returns truthy if animation should abort - */ -export type TAbortCallback = - TOnAnimationChangeCallback; - -/** - * Function used for canceling an animation - */ -export type TCancelFunction = VoidFunction; - -export interface AnimationBounds { - /** - * Starting value(s) - */ - startValue: State; - - /** - * Ending value(s) - * @default 100 - */ - endValue: State; - - /** - * Value(s) to increment/decrement the value(s) by - * @default [endValue - startValue] - */ - byValue: State; -} - -/** - * Animation of a value or list of values - */ -export interface AnimationOptions extends AnimationBounds { - /** - * The object this animation is being performed on - */ - target?: unknown; - - /** - * Called when the animation starts - */ - onStart?: VoidFunction; - - /** - * Called at each frame of the animation - */ - onChange: TOnAnimationChangeCallback; - - /** - * Called after the last frame of the animation - */ - onComplete: TOnAnimationChangeCallback; - - /** - * Easing function - * @default [defaultEasing] - */ - easing: TEasingFunction; - - /** - * Function called at each frame. - * If it returns true, abort - */ - abort: TAbortCallback; - - /** - * Duration of the animation in ms - * @default 500 - */ - duration: number; - - /** - * Delay to start the animation in ms - * @default 0 - */ - delay: number; -} - -export interface AnimationCurrentState { - /** - * Current values - */ - currentValue: State; - /** - * Same as valueRatio from @see TOnAnimationChangeCallback - */ - completionRate: number; - /** - * Same as completionRatio from @see TOnAnimationChangeCallback - */ - durationRate: number; -} - -/** - * Animation context - */ -export interface AnimationContext - extends Partial>, - AnimationCurrentState { - /** - * Current function used to cancel the animation - */ - cancel: TCancelFunction; -} +import { defaultEasing } from './easing'; +import { + AnimationBounds, + AnimationContext, + AnimationOptions, + isMulti, + TCancelFunction, +} from './types'; /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. - * @memberOf fabric.util + * * @param {AnimationOptions} [options] Animation options - * When using lists, think of something like this: - * @example - * fabric.util.animate({ - * startValue: [1, 2, 3], - * endValue: [2, 4, 6], - * onChange: function([x, y, zoom]) { - * canvas.zoomToPoint(new Point(x, y), zoom); - * canvas.requestRenderAll(); - * } - * }); + * @returns {TCancelFunction} cancel function * * @example - * fabric.util.animate({ + * animate({ * startValue: 1, * endValue: 0, - * onChange: function(v) { + * onChange: (v) => { * obj.set('opacity', v); - * canvas.requestRenderAll(); + * canvas.renderAll(); + * } + * }); + * + * @example When using lists, think of something like this: + * animate({ + * startValue: [1, 2, 3], + * endValue: [2, 4, 6], + * onChange: ([x, y, zoom]) => { + * canvas.zoomToPoint(new Point(x, y), zoom); + * canvas.renderAll(); * } * }); * - * @returns {TCancelFunction} cancel function */ -export function animate( - options: Partial | AnimationOptions> = {} -): TCancelFunction { +export function animate< + T extends number | number[], + S extends AnimationOptions +>(options: Partial = {}): TCancelFunction { let cancel = false; - const isMulti = function isMulti< - Single extends Partial>, - Multi extends Partial> - >(x: Single | Multi): x is Multi { - return Array.isArray(x.startValue); - }; - - const isMany = isMulti(options); - const { duration = 500, easing = defaultEasing, delay = 0 } = options; // let startValue: number | number[], endValue: number | number[]; @@ -173,19 +54,11 @@ export function animate( Pick, 'abort' | 'onChange' | 'onComplete'>); let context: AnimationContext | AnimationContext; - if (isMany) { - let byValue = options.byValue; + if (isMulti(options)) { const startValue = options.startValue ?? [0], - endValue = options.endValue ?? [100]; - - if (!byValue) { - byValue = new Array(startValue.length); - // using map here means that we need to use a closure, but TS isn't smart enough to realize - // that bounds is still a AnimationBounds inside the closure - for (let i = 0; i < startValue.length; i++) { - byValue[i] = endValue[i] - startValue[i]; - } - } + endValue = options.endValue ?? [100], + byValue = + options.byValue ?? startValue.map((value, i) => endValue[i] - value); bounds = { startValue, diff --git a/src/util/animate_color.ts b/src/util/animation/animate_color.ts similarity index 98% rename from src/util/animate_color.ts rename to src/util/animation/animate_color.ts index 971196e41d0..b8ad490a43f 100644 --- a/src/util/animate_color.ts +++ b/src/util/animation/animate_color.ts @@ -1,5 +1,5 @@ //@ts-nocheck -import { Color } from '../color'; +import { Color } from '../../color'; import { animate } from './animate'; // Calculate an in-between color. Returns a "rgba()" string. diff --git a/src/util/animation_registry.ts b/src/util/animation/animation_registry.ts similarity index 94% rename from src/util/animation_registry.ts rename to src/util/animation/animation_registry.ts index 94b4f5237fa..9ae57a74875 100644 --- a/src/util/animation_registry.ts +++ b/src/util/animation/animation_registry.ts @@ -1,6 +1,6 @@ -import { fabric } from '../../HEADER'; -import { Canvas, TObject } from '../__types__'; -import { AnimationContext, TCancelFunction } from './animate'; +import { fabric } from '../../../HEADER'; +import { Canvas, TObject } from '../../__types__'; +import { AnimationContext, TCancelFunction } from './types'; type TAnimation = AnimationContext | AnimationContext; diff --git a/src/util/anim_ease.ts b/src/util/animation/easing.ts similarity index 95% rename from src/util/anim_ease.ts rename to src/util/animation/easing.ts index ad31a45aedf..40eae67912a 100644 --- a/src/util/anim_ease.ts +++ b/src/util/animation/easing.ts @@ -1,19 +1,5 @@ -import { twoMathPi, halfPI } from '../constants'; - -/** - * An easing function - * @param currentTime ms elapsed - * @param startValue - * @param byValue increment/change/"completion rate"/magnitude - * @param duration in ms - * @returns next value - */ -export type TEasingFunction = ( - currentTime: number, - startValue: number, - byValue: number, - duration: number -) => number; +import { twoMathPi, halfPI } from '../../constants'; +import { TEasingFunction } from './types'; /** * Easing functions diff --git a/src/util/animation/index.ts b/src/util/animation/index.ts new file mode 100644 index 00000000000..5ccd7ab90d2 --- /dev/null +++ b/src/util/animation/index.ts @@ -0,0 +1,5 @@ +export * from './animate'; +export * from './animate_color'; +export * from './types'; +export * from './animation_registry'; +export * as ease from './easing'; diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts new file mode 100644 index 00000000000..6a74ce09774 --- /dev/null +++ b/src/util/animation/types.ts @@ -0,0 +1,142 @@ +type TAnimationArgument = number | number[]; + +/** + * Callback called every frame + * @param t current "time"/ms elapsed. multivalue + * @param valueRatio ratio of current value to animation max value. [0, 1] + * @param timeRatio ratio of current ms to animation duration. [0, 1] + */ +export type TOnAnimationChangeCallback< + R = void, + T extends TAnimationArgument = TAnimationArgument +> = (t: T, valueRatio: number, timeRatio: number) => R; + +/** + * Called to determine if animation should abort + * @returns truthy if animation should abort + */ +export type TAbortCallback = + TOnAnimationChangeCallback; + +/** + * Function used for canceling an animation + */ +export type TCancelFunction = VoidFunction; + +/** + * Animation of a value or list of values + */ +export interface AnimationBounds { + /** + * Starting value(s) + */ + startValue: T; + + /** + * Ending value(s) + * @default 100 + */ + endValue: T; + + /** + * Value(s) to increment/decrement the value(s) by + * @default [endValue - startValue] + */ + byValue: T; +} + +/** + * An easing function + * @param currentTime ms elapsed + * @param startValue + * @param byValue increment/change/"completion rate"/magnitude + * @param duration in ms + * @returns next value + */ +export type TEasingFunction = ( + currentTime: number, + startValue: number, + byValue: number, + duration: number +) => number; + +export interface AnimationOptions + extends AnimationBounds { + /** + * The object this animation is being performed on + */ + target?: unknown; + + /** + * Called when the animation starts + */ + onStart?: VoidFunction; + + /** + * Called at each frame of the animation + */ + onChange: TOnAnimationChangeCallback; + + /** + * Called after the last frame of the animation + */ + onComplete: TOnAnimationChangeCallback; + + /** + * Easing function + * @default [defaultEasing] + */ + easing: TEasingFunction; + + /** + * Function called at each frame. + * If it returns true, abort + */ + abort: TAbortCallback; + + /** + * Duration of the animation in ms + * @default 500 + */ + duration: number; + + /** + * Delay to start the animation in ms + * @default 0 + */ + delay: number; +} + +export interface AnimationCurrentState { + /** + * Current values + */ + currentValue: State; + /** + * Same as valueRatio from @see TOnAnimationChangeCallback + */ + completionRate: number; + /** + * Same as completionRatio from @see TOnAnimationChangeCallback + */ + durationRate: number; +} + +/** + * Animation context + */ +export interface AnimationContext + extends Partial>, + AnimationCurrentState { + /** + * Current function used to cancel the animation + */ + cancel: TCancelFunction; +} + +export const isMulti = function isMulti< + Single extends Partial>, + Multi extends Partial> +>(x: Single | Multi): x is Multi { + return Array.isArray(x.startValue); +}; diff --git a/src/util/misc/misc.ts b/src/util/misc/misc.ts index 852ca7a73c3..d5bfbef2aed 100644 --- a/src/util/misc/misc.ts +++ b/src/util/misc/misc.ts @@ -1,102 +1,105 @@ -//@ts-nocheck import { fabric } from '../../../HEADER'; -import { cos } from './cos'; -import { sin } from './sin'; import { - rotateVector, - createVector, - calcAngleBetweenVectors, - getHatVector, - getBisector, -} from './vectors'; -import { degreesToRadians, radiansToDegrees } from './radiansDegreesConversion'; -import { rotatePoint } from './rotatePoint'; -import { getRandomInt, removeFromArray } from '../internals'; -import { projectStrokeOnPoints } from './projectStroke'; + animate, + animateColor, + cancelAnimFrame, + ease, + requestAnimFrame, +} from '../animation'; import { - transformPoint, - invertTransform, - composeMatrix, - qrDecompose, - calcDimensionsMatrix, - calcRotateMatrix, - multiplyTransformMatrices, -} from './matrix'; -import { stylesFromArray, stylesToArray, hasStyleChanged } from './textStyles'; + addListener, + getPointer, + isTouchEvent, + removeListener, +} from '../dom_event'; +import { + cleanUpJsdomNode, + getElementOffset, + getNodeCanvas, + getScrollLeftTop, + makeElementSelectable, + makeElementUnselectable, + wrapElement, +} from '../dom_misc'; +import { request } from '../dom_request'; +import { setStyle } from '../dom_style'; +import { getRandomInt, removeFromArray } from '../internals'; +import { createClass } from '../lang_class'; import { clone, extend } from '../lang_object'; +import { camelize, capitalize, escapeXml, graphemeSplit } from '../lang_string'; +import { + getBoundsOfCurve, + getPathSegmentsInfo, + getPointOnPath, + getRegularPolygonPath, + getSmoothPathFromPoints, + joinPath, + makePathSimpler, + parsePath, + transformPath, +} from '../path'; +import { makeBoundingBoxFromPoints } from './boundingBoxFromPoints'; +import { capValue } from './capValue'; +import { cos } from './cos'; import { + copyCanvasElement, createCanvasElement, createImage, - copyCanvasElement, toDataURL, } from './dom'; -import { toFixed } from './toFixed'; +import { findScaleToCover, findScaleToFit } from './findScaleTo'; +import { isTransparent } from './isTransparent'; import { - matrixToSVG, - parsePreserveAspectRatioAttribute, - groupSVGElements, - parseUnit, - getSvgAttributes, -} from './svgParsing'; -import { findScaleToFit, findScaleToCover } from './findScaleTo'; -import { capValue } from './capValue'; + calcDimensionsMatrix, + calcRotateMatrix, + composeMatrix, + invertTransform, + multiplyTransformMatrices, + qrDecompose, + transformPoint, +} from './matrix'; +import { mergeClipPaths } from './mergeClipPaths'; +import { + enlivenObjectEnlivables, + enlivenObjects, + getKlass, + loadImage, +} from './objectEnlive'; import { - saveObjectTransform, - resetObjectTransform, addTransformToObject, applyTransformToObject, removeTransformFromObject, + resetObjectTransform, + saveObjectTransform, sizeAfterTransform, } from './objectTransforms'; -import { makeBoundingBoxFromPoints } from './boundingBoxFromPoints'; +import { pick } from './pick'; import { calcPlaneChangeMatrix, + sendObjectToPlane, sendPointToPlane, transformPointRelativeToCanvas, - sendObjectToPlane, } from './planeChange'; -import { camelize, capitalize, escapeXml, graphemeSplit } from '../lang_string'; -import { - getKlass, - loadImage, - enlivenObjects, - enlivenObjectEnlivables, -} from './objectEnlive'; -import { pick } from './pick'; -import { - joinPath, - parsePath, - makePathSimpler, - getSmoothPathFromPoints, - getPathSegmentsInfo, - getBoundsOfCurve, - getPointOnPath, - transformPath, - getRegularPolygonPath, -} from '../path'; -import { setStyle } from '../dom_style'; -import { request } from '../dom_request'; +import { projectStrokeOnPoints } from './projectStroke'; +import { degreesToRadians, radiansToDegrees } from './radiansDegreesConversion'; +import { rotatePoint } from './rotatePoint'; +import { sin } from './sin'; import { - isTouchEvent, - getPointer, - removeListener, - addListener, -} from '../dom_event'; + getSvgAttributes, + groupSVGElements, + matrixToSVG, + parsePreserveAspectRatioAttribute, + parseUnit, +} from './svgParsing'; +import { hasStyleChanged, stylesFromArray, stylesToArray } from './textStyles'; +import { toFixed } from './toFixed'; import { - wrapElement, - getScrollLeftTop, - getElementOffset, - getNodeCanvas, - cleanUpJsdomNode, - makeElementUnselectable, - makeElementSelectable, -} from '../dom_misc'; -import { isTransparent } from './isTransparent'; -import { mergeClipPaths } from './mergeClipPaths'; -import * as ease from '../anim_ease'; -import { animateColor } from '../animate_color'; -import { animate, requestAnimFrame, cancelAnimFrame } from '../animate'; -import { createClass } from '../lang_class'; + calcAngleBetweenVectors, + createVector, + getBisector, + getHatVector, + rotateVector, +} from './vectors'; /** * @namespace fabric.util */ From fd134b9c6909e6fafe0366b387f0237412b8ba40 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 12 Oct 2022 18:39:29 +0300 Subject: [PATCH 016/108] extract request/cancel AnimFrame --- src/util/animation/AnimationFrame.ts | 24 ++++++++++++++++++++++++ src/util/animation/animate.ts | 25 +------------------------ src/util/animation/index.ts | 3 ++- 3 files changed, 27 insertions(+), 25 deletions(-) create mode 100644 src/util/animation/AnimationFrame.ts diff --git a/src/util/animation/AnimationFrame.ts b/src/util/animation/AnimationFrame.ts new file mode 100644 index 00000000000..47724fb8eb2 --- /dev/null +++ b/src/util/animation/AnimationFrame.ts @@ -0,0 +1,24 @@ +import { fabric } from '../../../HEADER'; + +const _requestAnimFrame: AnimationFrameProvider['requestAnimationFrame'] = + fabric.window.requestAnimationFrame || + function (callback: FrameRequestCallback) { + return fabric.window.setTimeout(callback, 1000 / 60); + }; + +const _cancelAnimFrame: AnimationFrameProvider['cancelAnimationFrame'] = + fabric.window.cancelAnimationFrame || fabric.window.clearTimeout; + +/** + * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method + * @memberOf fabric.util + * @param {Function} callback Callback to invoke + */ +export function requestAnimFrame(callback: FrameRequestCallback): number { + return _requestAnimFrame.call(fabric.window, callback); +} + +export function cancelAnimFrame(handle: number): void { + return _cancelAnimFrame.call(fabric.window, handle); +} diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 1c223172b77..8739b464716 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -1,7 +1,7 @@ -import { fabric } from '../../../HEADER'; import { noop } from '../../constants'; import { runningAnimations } from './animation_registry'; import { defaultEasing } from './easing'; +import { requestAnimFrame } from './AnimationFrame'; import { AnimationBounds, AnimationContext, @@ -200,26 +200,3 @@ export function animate< return context.cancel; } - -const _requestAnimFrame: AnimationFrameProvider['requestAnimationFrame'] = - fabric.window.requestAnimationFrame || - function (callback: FrameRequestCallback) { - return fabric.window.setTimeout(callback, 1000 / 60); - }; - -const _cancelAnimFrame: AnimationFrameProvider['cancelAnimationFrame'] = - fabric.window.cancelAnimationFrame || fabric.window.clearTimeout; - -/** - * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method - * @memberOf fabric.util - * @param {Function} callback Callback to invoke - */ -export function requestAnimFrame(callback: FrameRequestCallback): number { - return _requestAnimFrame.call(fabric.window, callback); -} - -export function cancelAnimFrame(handle: number): void { - return _cancelAnimFrame.call(fabric.window, handle); -} diff --git a/src/util/animation/index.ts b/src/util/animation/index.ts index 5ccd7ab90d2..97fb52c65d2 100644 --- a/src/util/animation/index.ts +++ b/src/util/animation/index.ts @@ -1,5 +1,6 @@ export * from './animate'; export * from './animate_color'; -export * from './types'; export * from './animation_registry'; export * as ease from './easing'; +export * from './AnimationFrame'; +export * from './types'; From 1f5f12e918906f1778bc71b04469ce509d9dbaa1 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 12 Oct 2022 18:40:41 +0300 Subject: [PATCH 017/108] rename --- src/util/animation/animate.ts | 2 +- src/util/animation/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 8739b464716..0d39640bb55 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -1,6 +1,6 @@ import { noop } from '../../constants'; import { runningAnimations } from './animation_registry'; -import { defaultEasing } from './easing'; +import { defaultEasing } from './Easing'; import { requestAnimFrame } from './AnimationFrame'; import { AnimationBounds, diff --git a/src/util/animation/index.ts b/src/util/animation/index.ts index 97fb52c65d2..eb01d89caba 100644 --- a/src/util/animation/index.ts +++ b/src/util/animation/index.ts @@ -1,6 +1,6 @@ export * from './animate'; export * from './animate_color'; export * from './animation_registry'; -export * as ease from './easing'; +export * as ease from './Easing'; export * from './AnimationFrame'; export * from './types'; From ae4d15b6146cdc45a07d21cabfbc60fb1254cbfc Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 12 Oct 2022 20:06:47 +0300 Subject: [PATCH 018/108] revert(): animate --- src/util/animation/animate.ts | 192 +++++++++++----------------------- 1 file changed, 61 insertions(+), 131 deletions(-) diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 0d39640bb55..bacac23e2fd 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -37,157 +37,87 @@ import { * }); * */ -export function animate< - T extends number | number[], - S extends AnimationOptions ->(options: Partial = {}): TCancelFunction { +export function animate(options: Partial> = {}) { let cancel = false; - const { duration = 500, easing = defaultEasing, delay = 0 } = options; + const { + startValue = 0, + duration = 500, + easing = defaultEasing, + onChange = noop, + abort = noop, + onComplete = noop, + endValue = 100, + delay = 0, + } = options; - // let startValue: number | number[], endValue: number | number[]; - - let bounds: - | (AnimationBounds & - Pick, 'abort' | 'onChange' | 'onComplete'>) - | (AnimationBounds & - Pick, 'abort' | 'onChange' | 'onComplete'>); - - let context: AnimationContext | AnimationContext; - if (isMulti(options)) { - const startValue = options.startValue ?? [0], - endValue = options.endValue ?? [100], - byValue = - options.byValue ?? startValue.map((value, i) => endValue[i] - value); - - bounds = { - startValue, - endValue, - byValue, - onChange: options.onChange ?? noop, - onComplete: options.onComplete ?? noop, - abort: options.abort ?? noop, - }; - - context = { - ...options, - cancel: function () { - cancel = true; - return removeFromRegistry(); - }, - completionRate: 0, - durationRate: 0, - currentValue: bounds.startValue, - }; - } else { - const startValue = options.startValue ?? 0, - endValue = options.endValue ?? 100, - byValue = options.byValue || endValue - startValue; - bounds = { - startValue, - endValue, - byValue, - onChange: options.onChange ?? noop, - onComplete: options.onComplete ?? noop, - abort: options.abort ?? noop, - }; - context = { - ...options, - cancel: function () { - cancel = true; - return removeFromRegistry(); - }, - completionRate: 0, - durationRate: 0, - currentValue: bounds.startValue, - }; - } + const context = { + ...options, + currentValue: startValue, + completionRate: 0, + durationRate: 0, + }; const removeFromRegistry = () => { const index = runningAnimations.indexOf(context); return index > -1 && runningAnimations.splice(index, 1)[0]; }; + context.cancel = function () { + cancel = true; + return removeFromRegistry(); + }; runningAnimations.push(context); - const runner = function (timestamp: number) { + const runner = function (timestamp) { const start = timestamp || +new Date(), - finish = start + duration; + finish = start + duration, + isMany = Array.isArray(startValue), + byValue = + options.byValue || + (isMany + ? startValue.map((value, i) => endValue[i] - value) + : endValue - startValue); options.onStart && options.onStart(); - (function tick(ticktime: number) { + (function tick(ticktime) { const time = ticktime || +new Date(); const currentTime = time > finish ? duration : time - start, - timePerc = currentTime / duration; - let current: number | number[], valuePerc: number; - + timePerc = currentTime / duration, + current = isMany + ? startValue.map((_value, i) => + easing(currentTime, _value, byValue[i], duration) + ) + : easing(currentTime, startValue, byValue, duration), + valuePerc = isMany + ? Math.abs((current[0] - startValue[0]) / byValue[0]) + : Math.abs((current - startValue) / byValue); // update context + context.currentValue = isMany ? current.slice() : current; + context.completionRate = valuePerc; context.durationRate = timePerc; - if (isMulti(bounds)) { - current = new Array(bounds.startValue.length); - for (let i = 0; i < current.length; i++) { - current[i] = easing( - currentTime, - bounds.startValue[i], - bounds.byValue[i], - duration - ); - } - context.currentValue = current.slice(); - valuePerc = Math.abs( - (current[0] - bounds.startValue[0]) / bounds.byValue[0] - ); - if (cancel) { - return; - } - if (bounds.abort(current, valuePerc, timePerc)) { - removeFromRegistry(); - return; - } - if (time > finish) { - context.currentValue = bounds.endValue.slice(); - bounds.onChange(bounds.endValue.slice(), 1, 1); - bounds.onComplete(bounds.endValue, 1, 1); - context.completionRate = 1; - context.durationRate = 1; - // execute callbacks - removeFromRegistry(); - } else { - bounds.onChange(current, valuePerc, timePerc); - requestAnimFrame(tick); - } + + if (cancel) { + return; + } + if (abort(current, valuePerc, timePerc)) { + removeFromRegistry(); + return; + } + if (time > finish) { + // update context + context.currentValue = isMany ? endValue.slice() : endValue; + context.completionRate = 1; + context.durationRate = 1; + // execute callbacks + onChange(isMany ? endValue.slice() : endValue, 1, 1); + onComplete(endValue, 1, 1); + removeFromRegistry(); + return; } else { - current = easing( - currentTime, - bounds.startValue, - bounds.byValue, - duration - ); - context.currentValue = current; - valuePerc = Math.abs( - ((current as number) - bounds.startValue) / bounds.byValue - ); - context.completionRate = valuePerc; - if (cancel) { - return; - } - if (bounds.abort(current, valuePerc, timePerc)) { - removeFromRegistry(); - return; - } - if (time > finish) { - context.currentValue = bounds.endValue; - bounds.onChange(bounds.endValue, 1, 1); - bounds.onComplete(bounds.endValue, 1, 1); - context.completionRate = 1; - context.durationRate = 1; - // execute callbacks - removeFromRegistry(); - } else { - bounds.onChange(current, valuePerc, timePerc); - requestAnimFrame(tick); - } + onChange(current, valuePerc, timePerc); + requestAnimFrame(tick); } })(start); }; From 91af64a91b3ae4c0d0e87eb71428c9469dbf09a9 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 12 Oct 2022 20:13:05 +0300 Subject: [PATCH 019/108] Update types.ts --- src/util/animation/types.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 6a74ce09774..7c0de514fef 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -1,4 +1,4 @@ -type TAnimationArgument = number | number[]; +export type TAnimationArgument = number | number[]; /** * Callback called every frame @@ -7,8 +7,8 @@ type TAnimationArgument = number | number[]; * @param timeRatio ratio of current ms to animation duration. [0, 1] */ export type TOnAnimationChangeCallback< - R = void, - T extends TAnimationArgument = TAnimationArgument + T extends TAnimationArgument, + R = void > = (t: T, valueRatio: number, timeRatio: number) => R; /** @@ -16,7 +16,7 @@ export type TOnAnimationChangeCallback< * @returns truthy if animation should abort */ export type TAbortCallback = - TOnAnimationChangeCallback; + TOnAnimationChangeCallback; /** * Function used for canceling an animation @@ -60,8 +60,9 @@ export type TEasingFunction = ( duration: number ) => number; -export interface AnimationOptions - extends AnimationBounds { +export interface AnimationOptions< + T extends TAnimationArgument = TAnimationArgument +> extends AnimationBounds { /** * The object this animation is being performed on */ @@ -75,12 +76,12 @@ export interface AnimationOptions /** * Called at each frame of the animation */ - onChange: TOnAnimationChangeCallback; + onChange: TOnAnimationChangeCallback; /** * Called after the last frame of the animation */ - onComplete: TOnAnimationChangeCallback; + onComplete: TOnAnimationChangeCallback; /** * Easing function From c9e9a14b18007df8fc99bb92398b6079651d9a5d Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 12 Oct 2022 20:13:20 +0300 Subject: [PATCH 020/108] refactor(): remove from registry --- src/util/animation/animate.ts | 39 +++++++----------------- src/util/animation/animation_registry.ts | 37 ++++++++++++++++++++-- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index bacac23e2fd..a67f57e7cd7 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -1,14 +1,8 @@ import { noop } from '../../constants'; +import { requestAnimFrame } from './AnimationFrame'; import { runningAnimations } from './animation_registry'; import { defaultEasing } from './Easing'; -import { requestAnimFrame } from './AnimationFrame'; -import { - AnimationBounds, - AnimationContext, - AnimationOptions, - isMulti, - TCancelFunction, -} from './types'; +import { AnimationOptions, TCancelFunction } from './types'; /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. @@ -38,8 +32,6 @@ import { * */ export function animate(options: Partial> = {}) { - let cancel = false; - const { startValue = 0, duration = 500, @@ -51,23 +43,14 @@ export function animate(options: Partial> = {}) { delay = 0, } = options; - const context = { - ...options, - currentValue: startValue, - completionRate: 0, - durationRate: 0, - }; - - const removeFromRegistry = () => { - const index = runningAnimations.indexOf(context); - return index > -1 && runningAnimations.splice(index, 1)[0]; - }; - - context.cancel = function () { - cancel = true; - return removeFromRegistry(); - }; - runningAnimations.push(context); + let cancelled = false; + const { context, remove: removeFromRegistry } = runningAnimations.register( + options, + startValue, + () => { + cancelled = true; + } + ); const runner = function (timestamp) { const start = timestamp || +new Date(), @@ -98,7 +81,7 @@ export function animate(options: Partial> = {}) { context.completionRate = valuePerc; context.durationRate = timePerc; - if (cancel) { + if (cancelled) { return; } if (abort(current, valuePerc, timePerc)) { diff --git a/src/util/animation/animation_registry.ts b/src/util/animation/animation_registry.ts index 9ae57a74875..98b442e494c 100644 --- a/src/util/animation/animation_registry.ts +++ b/src/util/animation/animation_registry.ts @@ -1,8 +1,14 @@ import { fabric } from '../../../HEADER'; import { Canvas, TObject } from '../../__types__'; -import { AnimationContext, TCancelFunction } from './types'; +import { + AnimationContext, + AnimationOptions, + TAnimationArgument, + TCancelFunction, +} from './types'; -type TAnimation = AnimationContext | AnimationContext; +type TAnimation = + AnimationContext; /** * Array holding all running animations @@ -10,6 +16,33 @@ type TAnimation = AnimationContext | AnimationContext; * @type {AnimationContext[]} */ class RunningAnimations extends Array { + register( + options: Partial, + startValue: TAnimationArgument, + cancel: VoidFunction + ) { + const context = { + ...options, + currentValue: startValue, + completionRate: 0, + durationRate: 0, + cancel: () => { + cancel(); + this.remove(context); + }, + }; + this.push(context); + return { + context, + remove: () => this.remove(context), + }; + } + + private remove(context: TAnimation) { + const index = this.indexOf(context); + index > -1 && this.splice(index, 1); + } + /** * cancel all running animations at the next requestAnimFrame * @returns {AnimationContext[]} From 052bd3fedd9fcb2b7f3541b36177554116f9ff65 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 13 Oct 2022 15:01:45 +0300 Subject: [PATCH 021/108] fix(): TDegree/TRadian & number --- src/typedefs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typedefs.ts b/src/typedefs.ts index 6654f01ae60..d56b2e80531 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -1,7 +1,7 @@ // https://www.typescriptlang.org/docs/handbook/utility-types.html interface NominalTag { - nominalTag: T; + nominalTag?: T; } type Nominal = NominalTag & Type; From e9b8f2a8e7f2b2711c6a08f8f6efe6549fd333ee Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 13 Oct 2022 15:04:10 +0300 Subject: [PATCH 022/108] Revert "fix(): TDegree/TRadian & number" This reverts commit 052bd3fedd9fcb2b7f3541b36177554116f9ff65. --- src/typedefs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typedefs.ts b/src/typedefs.ts index d56b2e80531..6654f01ae60 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -1,7 +1,7 @@ // https://www.typescriptlang.org/docs/handbook/utility-types.html interface NominalTag { - nominalTag?: T; + nominalTag: T; } type Nominal = NominalTag & Type; From a728316041901f41af561c3defe9863075bca829 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sun, 16 Oct 2022 13:33:58 -0500 Subject: [PATCH 023/108] move stuff in from the ts-animation branch first stab at making a more typesafe animate --- src/util/animation/animate.ts | 140 +++++++++++++++----- src/util/animation/animation_registry.ts | 38 ++++-- src/util/animation/types.ts | 40 +++--- src/util/misc/misc.ts | 157 +++++++++++------------ 4 files changed, 227 insertions(+), 148 deletions(-) diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index a67f57e7cd7..484d3dd718a 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -1,8 +1,13 @@ import { noop } from '../../constants'; import { requestAnimFrame } from './AnimationFrame'; import { runningAnimations } from './animation_registry'; -import { defaultEasing } from './Easing'; -import { AnimationOptions, TCancelFunction } from './types'; +import { defaultEasing } from './easing'; +import { + AnimationContext, + AnimationOptions, + isMulti, + TCancelFunction, +} from './types'; /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. @@ -31,7 +36,28 @@ import { AnimationOptions, TCancelFunction } from './types'; * }); * */ -export function animate(options: Partial> = {}) { +export function animate( + options: Partial | AnimationOptions> = {} +): TCancelFunction { + const cancelled = { status: false }; + const { context, remove: removeFromRegistry } = runningAnimations.register( + options, + () => { + cancelled.status = true; + } + ); + if (isMulti(context)) { + return animateArray(context, cancelled, removeFromRegistry); + } else { + return animateSingle(context, cancelled, removeFromRegistry); + } +} + +function animateSingle( + context: AnimationContext, + cancelled: { status: boolean }, + removeFromRegistry: VoidFunction +) { const { startValue = 0, duration = 500, @@ -41,47 +67,97 @@ export function animate(options: Partial> = {}) { onComplete = noop, endValue = 100, delay = 0, - } = options; + } = context; - let cancelled = false; - const { context, remove: removeFromRegistry } = runningAnimations.register( - options, - startValue, - () => { - cancelled = true; - } - ); + const runner = function (timestamp: number) { + const start = timestamp || +new Date(), + finish = start + duration, + byValue = context.byValue || endValue - startValue; + + context.onStart && context.onStart(); + + (function tick(ticktime: number) { + const time = ticktime || +new Date(); + const currentTime = time > finish ? duration : time - start, + timePerc = currentTime / duration, + current = easing(currentTime, startValue, byValue, duration), + valuePerc = Math.abs((current - startValue) / byValue); + // update context + context.currentValue = current; + context.completionRate = valuePerc; + context.durationRate = timePerc; + + if (cancelled.status) { + return; + } + if (abort(current, valuePerc, timePerc)) { + removeFromRegistry(); + return; + } + if (time > finish) { + // update context + context.currentValue = endValue; + context.completionRate = 1; + context.durationRate = 1; + // execute callbacks + onChange(endValue, 1, 1); + onComplete(endValue, 1, 1); + removeFromRegistry(); + return; + } else { + onChange(current, valuePerc, timePerc); + requestAnimFrame(tick); + } + })(start); + }; + + if (delay > 0) { + setTimeout(() => requestAnimFrame(runner), delay); + } else { + requestAnimFrame(runner); + } + + return context.cancel; +} + +function animateArray( + context: AnimationContext, + cancelled: { status: boolean }, + removeFromRegistry: VoidFunction +) { + const { + startValue = [0], + duration = 500, + easing = defaultEasing, + onChange = noop, + abort = noop, + onComplete = noop, + endValue = [100], + delay = 0, + } = context; - const runner = function (timestamp) { + const runner = function (timestamp: number) { const start = timestamp || +new Date(), finish = start + duration, - isMany = Array.isArray(startValue), byValue = - options.byValue || - (isMany - ? startValue.map((value, i) => endValue[i] - value) - : endValue - startValue); + context.byValue || startValue.map((value, i) => endValue[i] - value); - options.onStart && options.onStart(); + context.onStart && context.onStart(); - (function tick(ticktime) { + (function tick(ticktime: number) { const time = ticktime || +new Date(); const currentTime = time > finish ? duration : time - start, timePerc = currentTime / duration, - current = isMany - ? startValue.map((_value, i) => - easing(currentTime, _value, byValue[i], duration) - ) - : easing(currentTime, startValue, byValue, duration), - valuePerc = isMany - ? Math.abs((current[0] - startValue[0]) / byValue[0]) - : Math.abs((current - startValue) / byValue); + current = startValue.map((_value, i) => + easing(currentTime, _value, byValue[i], duration) + ), + valuePerc = Math.abs((current[0] - startValue[0]) / byValue[0]); // update context - context.currentValue = isMany ? current.slice() : current; + context.currentValue = current.slice(); context.completionRate = valuePerc; context.durationRate = timePerc; - if (cancelled) { + if (cancelled.status) { return; } if (abort(current, valuePerc, timePerc)) { @@ -90,11 +166,11 @@ export function animate(options: Partial> = {}) { } if (time > finish) { // update context - context.currentValue = isMany ? endValue.slice() : endValue; + context.currentValue = endValue.slice(); context.completionRate = 1; context.durationRate = 1; // execute callbacks - onChange(isMany ? endValue.slice() : endValue, 1, 1); + onChange(endValue.slice(), 1, 1); onComplete(endValue, 1, 1); removeFromRegistry(); return; diff --git a/src/util/animation/animation_registry.ts b/src/util/animation/animation_registry.ts index 98b442e494c..7e07811d79e 100644 --- a/src/util/animation/animation_registry.ts +++ b/src/util/animation/animation_registry.ts @@ -3,12 +3,11 @@ import { Canvas, TObject } from '../../__types__'; import { AnimationContext, AnimationOptions, - TAnimationArgument, + isMulti, TCancelFunction, } from './types'; -type TAnimation = - AnimationContext; +type TAnimation = AnimationContext | AnimationContext; /** * Array holding all running animations @@ -17,20 +16,31 @@ type TAnimation = */ class RunningAnimations extends Array { register( - options: Partial, - startValue: TAnimationArgument, + options: Partial | AnimationOptions>, cancel: VoidFunction ) { - const context = { - ...options, - currentValue: startValue, - completionRate: 0, - durationRate: 0, - cancel: () => { - cancel(); - this.remove(context); - }, + cancel = () => { + cancel(); + this.remove(context); }; + let context: AnimationContext | AnimationContext; + if (isMulti(options)) { + context = { + ...options, + currentValue: options.startValue ?? [0], + completionRate: 0, + durationRate: 0, + cancel, + }; + } else { + context = { + ...options, + currentValue: options.startValue ?? 0, + completionRate: 0, + durationRate: 0, + cancel, + }; + } this.push(context); return { context, diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 7c0de514fef..9d72429e89a 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -1,22 +1,20 @@ -export type TAnimationArgument = number | number[]; - /** * Callback called every frame - * @param t current "time"/ms elapsed. multivalue + * @param current current value of the state. potentially multivalue * @param valueRatio ratio of current value to animation max value. [0, 1] * @param timeRatio ratio of current ms to animation duration. [0, 1] */ -export type TOnAnimationChangeCallback< - T extends TAnimationArgument, - R = void -> = (t: T, valueRatio: number, timeRatio: number) => R; +export type TOnAnimationChangeCallback = ( + current: State, + valueRatio: number, + timeRatio: number +) => R; /** * Called to determine if animation should abort * @returns truthy if animation should abort */ -export type TAbortCallback = - TOnAnimationChangeCallback; +export type TAbortCallback = TOnAnimationChangeCallback; /** * Function used for canceling an animation @@ -26,23 +24,23 @@ export type TCancelFunction = VoidFunction; /** * Animation of a value or list of values */ -export interface AnimationBounds { +export interface AnimationBounds { /** * Starting value(s) */ - startValue: T; + startValue: State; /** * Ending value(s) * @default 100 */ - endValue: T; + endValue: State; /** * Value(s) to increment/decrement the value(s) by * @default [endValue - startValue] */ - byValue: T; + byValue: State; } /** @@ -60,9 +58,7 @@ export type TEasingFunction = ( duration: number ) => number; -export interface AnimationOptions< - T extends TAnimationArgument = TAnimationArgument -> extends AnimationBounds { +export interface AnimationOptions extends AnimationBounds { /** * The object this animation is being performed on */ @@ -76,12 +72,12 @@ export interface AnimationOptions< /** * Called at each frame of the animation */ - onChange: TOnAnimationChangeCallback; + onChange: TOnAnimationChangeCallback; /** * Called after the last frame of the animation */ - onComplete: TOnAnimationChangeCallback; + onComplete: TOnAnimationChangeCallback; /** * Easing function @@ -93,7 +89,7 @@ export interface AnimationOptions< * Function called at each frame. * If it returns true, abort */ - abort: TAbortCallback; + abort: TAbortCallback; /** * Duration of the animation in ms @@ -126,9 +122,9 @@ export interface AnimationCurrentState { /** * Animation context */ -export interface AnimationContext - extends Partial>, - AnimationCurrentState { +export interface AnimationContext + extends Partial>, + AnimationCurrentState { /** * Current function used to cancel the animation */ diff --git a/src/util/misc/misc.ts b/src/util/misc/misc.ts index d5bfbef2aed..c3fa3bf617f 100644 --- a/src/util/misc/misc.ts +++ b/src/util/misc/misc.ts @@ -1,105 +1,102 @@ import { fabric } from '../../../HEADER'; +import { cos } from './cos'; +import { sin } from './sin'; import { - animate, - animateColor, - cancelAnimFrame, - ease, - requestAnimFrame, -} from '../animation'; -import { - addListener, - getPointer, - isTouchEvent, - removeListener, -} from '../dom_event'; -import { - cleanUpJsdomNode, - getElementOffset, - getNodeCanvas, - getScrollLeftTop, - makeElementSelectable, - makeElementUnselectable, - wrapElement, -} from '../dom_misc'; -import { request } from '../dom_request'; -import { setStyle } from '../dom_style'; + rotateVector, + createVector, + calcAngleBetweenVectors, + getUnitVector, + getBisector, +} from './vectors'; +import { degreesToRadians, radiansToDegrees } from './radiansDegreesConversion'; +import { rotatePoint } from './rotatePoint'; import { getRandomInt, removeFromArray } from '../internals'; -import { createClass } from '../lang_class'; -import { clone, extend } from '../lang_object'; -import { camelize, capitalize, escapeXml, graphemeSplit } from '../lang_string'; +import { projectStrokeOnPoints } from './projectStroke'; import { - getBoundsOfCurve, - getPathSegmentsInfo, - getPointOnPath, - getRegularPolygonPath, - getSmoothPathFromPoints, - joinPath, - makePathSimpler, - parsePath, - transformPath, -} from '../path'; -import { makeBoundingBoxFromPoints } from './boundingBoxFromPoints'; -import { capValue } from './capValue'; -import { cos } from './cos'; + transformPoint, + invertTransform, + composeMatrix, + qrDecompose, + calcDimensionsMatrix, + calcRotateMatrix, + multiplyTransformMatrices, +} from './matrix'; +import { stylesFromArray, stylesToArray, hasStyleChanged } from './textStyles'; +import { clone, extend } from '../lang_object'; import { - copyCanvasElement, createCanvasElement, createImage, + copyCanvasElement, toDataURL, } from './dom'; -import { findScaleToCover, findScaleToFit } from './findScaleTo'; -import { isTransparent } from './isTransparent'; -import { - calcDimensionsMatrix, - calcRotateMatrix, - composeMatrix, - invertTransform, - multiplyTransformMatrices, - qrDecompose, - transformPoint, -} from './matrix'; -import { mergeClipPaths } from './mergeClipPaths'; +import { toFixed } from './toFixed'; import { - enlivenObjectEnlivables, - enlivenObjects, - getKlass, - loadImage, -} from './objectEnlive'; + matrixToSVG, + parsePreserveAspectRatioAttribute, + groupSVGElements, + parseUnit, + getSvgAttributes, +} from './svgParsing'; +import { findScaleToFit, findScaleToCover } from './findScaleTo'; +import { capValue } from './capValue'; import { + saveObjectTransform, + resetObjectTransform, addTransformToObject, applyTransformToObject, removeTransformFromObject, - resetObjectTransform, - saveObjectTransform, sizeAfterTransform, } from './objectTransforms'; -import { pick } from './pick'; +import { makeBoundingBoxFromPoints } from './boundingBoxFromPoints'; import { calcPlaneChangeMatrix, - sendObjectToPlane, sendPointToPlane, transformPointRelativeToCanvas, + sendObjectToPlane, } from './planeChange'; -import { projectStrokeOnPoints } from './projectStroke'; -import { degreesToRadians, radiansToDegrees } from './radiansDegreesConversion'; -import { rotatePoint } from './rotatePoint'; -import { sin } from './sin'; +import { camelize, capitalize, escapeXml, graphemeSplit } from '../lang_string'; import { - getSvgAttributes, - groupSVGElements, - matrixToSVG, - parsePreserveAspectRatioAttribute, - parseUnit, -} from './svgParsing'; -import { hasStyleChanged, stylesFromArray, stylesToArray } from './textStyles'; -import { toFixed } from './toFixed'; + getKlass, + loadImage, + enlivenObjects, + enlivenObjectEnlivables, +} from './objectEnlive'; +import { pick } from './pick'; import { - calcAngleBetweenVectors, - createVector, - getBisector, - getHatVector, - rotateVector, -} from './vectors'; + joinPath, + parsePath, + makePathSimpler, + getSmoothPathFromPoints, + getPathSegmentsInfo, + getBoundsOfCurve, + getPointOnPath, + transformPath, + getRegularPolygonPath, +} from '../path'; +import { setStyle } from '../dom_style'; +import { request } from '../dom_request'; +import { + isTouchEvent, + getPointer, + removeListener, + addListener, +} from '../dom_event'; +import { + wrapElement, + getScrollLeftTop, + getElementOffset, + getNodeCanvas, + cleanUpJsdomNode, + makeElementUnselectable, + makeElementSelectable, +} from '../dom_misc'; +import { isTransparent } from './isTransparent'; +import { mergeClipPaths } from './mergeClipPaths'; +import * as ease from '../animation/easing'; +import { animateColor } from '../animation'; +import { animate } from '../animation'; +import { requestAnimFrame, cancelAnimFrame } from "../animation"; +import { createClass } from '../lang_class'; /** * @namespace fabric.util */ @@ -109,7 +106,7 @@ fabric.util = { rotateVector, createVector, calcAngleBetweenVectors, - getHatVector, + getUnitVector, getBisector, degreesToRadians, radiansToDegrees, From 966f3415b7152e467314c10fae5d2ae6a26cb6dc Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sun, 16 Oct 2022 13:51:31 -0500 Subject: [PATCH 024/108] fix building --- src/shapes/object.class.ts | 2 +- src/static_canvas.class.ts | 2 +- src/util/animation/index.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shapes/object.class.ts b/src/shapes/object.class.ts index 00dcb1b58f3..93c663cb50a 100644 --- a/src/shapes/object.class.ts +++ b/src/shapes/object.class.ts @@ -5,7 +5,7 @@ import { VERSION } from '../constants'; import { Point } from '../point.class'; import { capValue } from '../util/misc/capValue'; import { pick } from '../util/misc/pick'; -import { runningAnimations } from '../util/animation_registry'; +import { runningAnimations } from '../util/animation'; import { enlivenObjectEnlivables } from '../util/misc/objectEnlive'; (function (global) { diff --git a/src/static_canvas.class.ts b/src/static_canvas.class.ts index f9754f7c05f..b83d2924e8d 100644 --- a/src/static_canvas.class.ts +++ b/src/static_canvas.class.ts @@ -2,7 +2,7 @@ import { config } from './config'; import { VERSION } from './constants'; import { Point } from './point.class'; -import { requestAnimFrame } from './util/animate'; +import { requestAnimFrame } from './util/animation'; import { removeFromArray } from './util/internals'; import { pick } from './util/misc/pick'; diff --git a/src/util/animation/index.ts b/src/util/animation/index.ts index eb01d89caba..97fb52c65d2 100644 --- a/src/util/animation/index.ts +++ b/src/util/animation/index.ts @@ -1,6 +1,6 @@ export * from './animate'; export * from './animate_color'; export * from './animation_registry'; -export * as ease from './Easing'; +export * as ease from './easing'; export * from './AnimationFrame'; export * from './types'; From 9b8397ee87913d9fb77a1d40a4242de682d7bd0a Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sun, 16 Oct 2022 14:14:31 -0500 Subject: [PATCH 025/108] WIP. fix bug --- src/util/animation/animation_registry.ts | 40 +++++++++++++++--------- src/util/animation/types.ts | 3 +- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/util/animation/animation_registry.ts b/src/util/animation/animation_registry.ts index 7e07811d79e..ddfe535b96a 100644 --- a/src/util/animation/animation_registry.ts +++ b/src/util/animation/animation_registry.ts @@ -6,6 +6,7 @@ import { isMulti, TCancelFunction, } from './types'; +import { noop } from '../../constants'; type TAnimation = AnimationContext | AnimationContext; @@ -17,35 +18,44 @@ type TAnimation = AnimationContext | AnimationContext; class RunningAnimations extends Array { register( options: Partial | AnimationOptions>, - cancel: VoidFunction + canceler: VoidFunction ) { - cancel = () => { - cancel(); - this.remove(context); - }; - let context: AnimationContext | AnimationContext; + //TODO: remove this extreme code duplication. IDK how without doing stuff like `as number` or `as number[]` if (isMulti(options)) { - context = { + const context: AnimationContext = { ...options, currentValue: options.startValue ?? [0], completionRate: 0, durationRate: 0, - cancel, + cancel: () => { + canceler(); + this.remove(context); + return context; + }, + }; + this.push(context); + return { + context, + remove: () => this.remove(context), }; } else { - context = { + const context: AnimationContext = { ...options, currentValue: options.startValue ?? 0, completionRate: 0, durationRate: 0, - cancel, + cancel: () => { + canceler(); + this.remove(context); + return context; + }, + }; + this.push(context); + return { + context, + remove: () => this.remove(context), }; } - this.push(context); - return { - context, - remove: () => this.remove(context), - }; } private remove(context: TAnimation) { diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 9d72429e89a..04d5f5bfcb8 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -16,10 +16,11 @@ export type TOnAnimationChangeCallback = ( */ export type TAbortCallback = TOnAnimationChangeCallback; +// TODO: this isn't that bad but still a little iffy. animation registry gets angry if I give this a type param, because then stuff like findAnimationIndex requires a type param too. Not sure which is better /** * Function used for canceling an animation */ -export type TCancelFunction = VoidFunction; +export type TCancelFunction = () => AnimationContext | AnimationContext; /** * Animation of a value or list of values From a596aabb194338610bff6a33f97101107b99fb7e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 21:03:44 +0300 Subject: [PATCH 026/108] refactor(): classes --- src/util/animation/Animation.ts | 199 +++++++++++++++++++++++ src/util/animation/animate.ts | 189 +++------------------ src/util/animation/animation_registry.ts | 117 ++----------- src/util/animation/types.ts | 120 +++++--------- 4 files changed, 279 insertions(+), 346 deletions(-) create mode 100644 src/util/animation/Animation.ts diff --git a/src/util/animation/Animation.ts b/src/util/animation/Animation.ts new file mode 100644 index 00000000000..4c679f44431 --- /dev/null +++ b/src/util/animation/Animation.ts @@ -0,0 +1,199 @@ +import { noop } from '../../constants'; +import { requestAnimFrame } from './AnimationFrame'; +import { runningAnimations } from './animation_registry'; +import { defaultEasing } from './easing'; +import { + TAnimationBaseOptions, + AnimationOptions, + TAnimationValues, + ArrayAnimationOptions, + TAbortCallback, + TEasingFunction, + TOnAnimationChangeCallback, +} from './types'; + +export abstract class AnimationBase { + readonly startValue: T; + readonly endValue: T; + readonly byValue: T; + readonly duration: number; + readonly delay: number; + protected readonly easing: TEasingFunction; + private readonly _onStart: VoidFunction; + private readonly _onChange: TOnAnimationChangeCallback; + private readonly _onComplete: TOnAnimationChangeCallback; + private readonly _abort: TAbortCallback; + /** + * used to register the animation to a target object so it can be cancelled within hte object context + */ + readonly target?: unknown; + + state: 'pending' | 'running' | 'completed' | 'aborted' = 'pending'; + /** + * time % + */ + xRate = 0; + /** + * value % + */ + yRate = 0; + /** + * current value + */ + value: T; + /** + * animation start time ms + */ + private startTime!: number; + + constructor({ + startValue, + endValue, + byValue, + duration = 500, + delay = 0, + easing = defaultEasing, + onStart = noop, + onChange = noop, + onComplete = noop, + abort = noop, + target, + }: Partial> & TAnimationValues) { + this.startValue = startValue; + this.endValue = endValue; + this.byValue = byValue; + this.duration = duration; + this.delay = delay; + this.easing = easing; + this._onStart = onStart; + this._onChange = onChange; + this._onComplete = onComplete; + this._abort = abort; + this.value = this.startValue; + this.target = target; + } + + protected abstract calculate(currentTime: number): { + value: T; + changeRate: number; + }; + + start() { + const runner: FrameRequestCallback = (timestamp) => { + this.startTime = timestamp; + this.state = 'running'; + this._onStart(); + this.tick(timestamp); + }; + + this.register(); + + // setTimeout(cb, 0) will run cb on the next frame, causing a delay + // we don't want that + if (this.delay > 0) { + setTimeout(() => requestAnimFrame(runner), this.delay); + } else { + requestAnimFrame(runner); + } + } + + private tick: FrameRequestCallback = (t: number) => { + const durationMs = t - this.startTime; + const boundDurationMs = Math.min(durationMs, this.duration); + this.xRate = boundDurationMs / this.duration; + const { value, changeRate } = this.calculate(boundDurationMs); + this.value = Array.isArray(value) ? (value.slice() as T) : value; + this.yRate = changeRate; + + if (this.state === 'aborted') { + return; + } + if (this._abort(value, this.yRate, this.xRate)) { + this.state = 'aborted'; + this.unregister(); + } + if (durationMs > boundDurationMs) { + // TODO this line seems redundant + this.xRate = this.yRate = 1; + this._onChange( + (Array.isArray(this.endValue) + ? this.endValue.slice() + : this.endValue) as T, + this.yRate, + this.xRate + ); + this.state = 'completed'; + this._onComplete(this.endValue, this.yRate, this.xRate); + this.unregister(); + } else { + this._onChange(value, this.yRate, this.xRate); + requestAnimFrame(this.tick); + } + }; + + private register() { + runningAnimations.push(this as any); + } + + private unregister() { + runningAnimations.remove(this as any); + } + + abort() { + this.state = 'aborted'; + this.unregister(); + } +} + +export class ValueAnimation extends AnimationBase { + constructor({ + startValue = 0, + endValue = 100, + byValue = endValue - startValue, + ...options + }: AnimationOptions) { + super({ + ...options, + startValue, + endValue, + byValue, + }); + } + protected calculate(currentTime: number) { + const value = this.easing( + currentTime, + this.startValue, + this.byValue, + this.duration + ); + return { + value, + changeRate: Math.abs((value - this.startValue) / this.byValue), + }; + } +} + +export class ArrayAnimation extends AnimationBase { + constructor({ + startValue = [0], + endValue = [100], + byValue = endValue.map((value, i) => value - startValue[i]), + ...options + }: ArrayAnimationOptions) { + super({ + ...options, + startValue, + endValue, + byValue, + }); + } + protected calculate(currentTime: number) { + const values = this.startValue.map((value, i) => + this.easing(currentTime, value, this.byValue[i], this.duration) + ); + return { + value: values, + changeRate: Math.abs((values[0] - this.startValue[0]) / this.byValue[0]), + }; + } +} diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 484d3dd718a..1d13c433cf9 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -1,19 +1,19 @@ -import { noop } from '../../constants'; -import { requestAnimFrame } from './AnimationFrame'; -import { runningAnimations } from './animation_registry'; -import { defaultEasing } from './easing'; -import { - AnimationContext, - AnimationOptions, - isMulti, - TCancelFunction, -} from './types'; +import { ArrayAnimation, ValueAnimation } from './Animation'; +import { AnimationOptions, ArrayAnimationOptions } from './types'; + +const isArrayAnimation = ( + options: ArrayAnimationOptions | AnimationOptions +): options is ArrayAnimationOptions => { + return ( + Array.isArray(options.startValue) || + Array.isArray(options.endValue) || + Array.isArray(options.byValue) + ); +}; /** - * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. - * - * @param {AnimationOptions} [options] Animation options - * @returns {TCancelFunction} cancel function + * Changes value(s) from startValue to endValue within a certain period of time, + * invoking callbacks as the value(s) change. * * @example * animate({ @@ -21,11 +21,12 @@ import { * endValue: 0, * onChange: (v) => { * obj.set('opacity', v); + * // since we are running in a requested frame we should call `renderAll` and not `requestRenderAll` * canvas.renderAll(); * } * }); * - * @example When using lists, think of something like this: + * @example Using lists: * animate({ * startValue: [1, 2, 3], * endValue: [2, 4, 6], @@ -36,156 +37,8 @@ import { * }); * */ -export function animate( - options: Partial | AnimationOptions> = {} -): TCancelFunction { - const cancelled = { status: false }; - const { context, remove: removeFromRegistry } = runningAnimations.register( - options, - () => { - cancelled.status = true; - } - ); - if (isMulti(context)) { - return animateArray(context, cancelled, removeFromRegistry); - } else { - return animateSingle(context, cancelled, removeFromRegistry); - } -} - -function animateSingle( - context: AnimationContext, - cancelled: { status: boolean }, - removeFromRegistry: VoidFunction -) { - const { - startValue = 0, - duration = 500, - easing = defaultEasing, - onChange = noop, - abort = noop, - onComplete = noop, - endValue = 100, - delay = 0, - } = context; - - const runner = function (timestamp: number) { - const start = timestamp || +new Date(), - finish = start + duration, - byValue = context.byValue || endValue - startValue; - - context.onStart && context.onStart(); - - (function tick(ticktime: number) { - const time = ticktime || +new Date(); - const currentTime = time > finish ? duration : time - start, - timePerc = currentTime / duration, - current = easing(currentTime, startValue, byValue, duration), - valuePerc = Math.abs((current - startValue) / byValue); - // update context - context.currentValue = current; - context.completionRate = valuePerc; - context.durationRate = timePerc; - - if (cancelled.status) { - return; - } - if (abort(current, valuePerc, timePerc)) { - removeFromRegistry(); - return; - } - if (time > finish) { - // update context - context.currentValue = endValue; - context.completionRate = 1; - context.durationRate = 1; - // execute callbacks - onChange(endValue, 1, 1); - onComplete(endValue, 1, 1); - removeFromRegistry(); - return; - } else { - onChange(current, valuePerc, timePerc); - requestAnimFrame(tick); - } - })(start); - }; - - if (delay > 0) { - setTimeout(() => requestAnimFrame(runner), delay); - } else { - requestAnimFrame(runner); - } - - return context.cancel; -} - -function animateArray( - context: AnimationContext, - cancelled: { status: boolean }, - removeFromRegistry: VoidFunction -) { - const { - startValue = [0], - duration = 500, - easing = defaultEasing, - onChange = noop, - abort = noop, - onComplete = noop, - endValue = [100], - delay = 0, - } = context; - - const runner = function (timestamp: number) { - const start = timestamp || +new Date(), - finish = start + duration, - byValue = - context.byValue || startValue.map((value, i) => endValue[i] - value); - - context.onStart && context.onStart(); - - (function tick(ticktime: number) { - const time = ticktime || +new Date(); - const currentTime = time > finish ? duration : time - start, - timePerc = currentTime / duration, - current = startValue.map((_value, i) => - easing(currentTime, _value, byValue[i], duration) - ), - valuePerc = Math.abs((current[0] - startValue[0]) / byValue[0]); - // update context - context.currentValue = current.slice(); - context.completionRate = valuePerc; - context.durationRate = timePerc; - - if (cancelled.status) { - return; - } - if (abort(current, valuePerc, timePerc)) { - removeFromRegistry(); - return; - } - if (time > finish) { - // update context - context.currentValue = endValue.slice(); - context.completionRate = 1; - context.durationRate = 1; - // execute callbacks - onChange(endValue.slice(), 1, 1); - onComplete(endValue, 1, 1); - removeFromRegistry(); - return; - } else { - onChange(current, valuePerc, timePerc); - requestAnimFrame(tick); - } - })(start); - }; - - if (delay > 0) { - setTimeout(() => requestAnimFrame(runner), delay); - } else { - requestAnimFrame(runner); - } - - return context.cancel; -} +export const animate = (options: AnimationOptions | ArrayAnimationOptions) => + (isArrayAnimation(options) + ? new ArrayAnimation(options) + : new ValueAnimation(options) + ).start(); diff --git a/src/util/animation/animation_registry.ts b/src/util/animation/animation_registry.ts index ddfe535b96a..c4d78f39470 100644 --- a/src/util/animation/animation_registry.ts +++ b/src/util/animation/animation_registry.ts @@ -1,138 +1,53 @@ import { fabric } from '../../../HEADER'; import { Canvas, TObject } from '../../__types__'; -import { - AnimationContext, - AnimationOptions, - isMulti, - TCancelFunction, -} from './types'; -import { noop } from '../../constants'; - -type TAnimation = AnimationContext | AnimationContext; +import { AnimationBase } from './Animation'; /** * Array holding all running animations - * @memberof fabric - * @type {AnimationContext[]} */ -class RunningAnimations extends Array { - register( - options: Partial | AnimationOptions>, - canceler: VoidFunction - ) { - //TODO: remove this extreme code duplication. IDK how without doing stuff like `as number` or `as number[]` - if (isMulti(options)) { - const context: AnimationContext = { - ...options, - currentValue: options.startValue ?? [0], - completionRate: 0, - durationRate: 0, - cancel: () => { - canceler(); - this.remove(context); - return context; - }, - }; - this.push(context); - return { - context, - remove: () => this.remove(context), - }; - } else { - const context: AnimationContext = { - ...options, - currentValue: options.startValue ?? 0, - completionRate: 0, - durationRate: 0, - cancel: () => { - canceler(); - this.remove(context); - return context; - }, - }; - this.push(context); - return { - context, - remove: () => this.remove(context), - }; - } - } - - private remove(context: TAnimation) { +class AnimationRegistry< + T extends AnimationBase | AnimationBase +> extends Array { + remove(context: T) { const index = this.indexOf(context); index > -1 && this.splice(index, 1); } /** * cancel all running animations at the next requestAnimFrame - * @returns {AnimationContext[]} */ - cancelAll(): TAnimation[] { + cancelAll() { const animations = this.splice(0); - animations.forEach((animation) => animation.cancel()); + animations.forEach((animation) => animation.abort()); return animations; } /** * cancel all running animations attached to canvas at the next requestAnimFrame - * @param {fabric.Canvas} canvas - * @returns {AnimationContext[]} */ - cancelByCanvas(canvas: Canvas): TAnimation[] { + cancelByCanvas(canvas: Canvas) { if (!canvas) { return []; } - const cancelled = this.filter( + const animations = this.filter( (animation) => typeof animation.target === 'object' && (animation.target as TObject)?.canvas === canvas ); - cancelled.forEach((animation) => animation.cancel()); - return cancelled; + animations.forEach((animation) => animation.abort()); + return animations; } /** * cancel all running animations for target at the next requestAnimFrame - * @param {*} target - * @returns {AnimationContext[]} - */ - cancelByTarget(target: TAnimation['target']): TAnimation[] { - const cancelled = this.findAnimationsByTarget(target); - cancelled.forEach((animation) => animation.cancel()); - return cancelled; - } - - /** - * - * @param {TCancelFunction} cancelFunc the function returned by animate - * @returns {number} - */ - findAnimationIndex(cancelFunc: TCancelFunction): number { - return this.findIndex((animation) => animation.cancel === cancelFunc); - } - - /** - * - * @param {TCancelFunction} cancelFunc the function returned by animate - * @returns {AnimationContext | undefined} animation's options object - */ - findAnimation(cancelFunc: TCancelFunction): TAnimation | undefined { - return this.find((animation) => animation.cancel === cancelFunc); - } - - /** - * - * @param {*} target the object that is assigned to the target property of the animation context - * @returns {AnimationContext[]} array of animation options object associated with target */ - findAnimationsByTarget(target: TAnimation['target']): TAnimation[] { - if (!target) { - return []; - } - return this.filter((animation) => animation.target === target); + cancelByTarget(target: T['target']) { + const animations = this.filter((animation) => animation.target === target); + animations.forEach((animation) => animation.abort()); + return animations; } } -export const runningAnimations = new RunningAnimations(); +export const runningAnimations = new AnimationRegistry(); fabric.runningAnimations = runningAnimations; diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 04d5f5bfcb8..f12720042f3 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -1,49 +1,21 @@ /** * Callback called every frame - * @param current current value of the state. potentially multivalue + * @param {number | number[]} value current value of the animation. * @param valueRatio ratio of current value to animation max value. [0, 1] * @param timeRatio ratio of current ms to animation duration. [0, 1] */ -export type TOnAnimationChangeCallback = ( - current: State, +export type TOnAnimationChangeCallback = ( + value: T, valueRatio: number, timeRatio: number ) => R; /** - * Called to determine if animation should abort + * Called on each step to determine if animation should abort * @returns truthy if animation should abort */ export type TAbortCallback = TOnAnimationChangeCallback; -// TODO: this isn't that bad but still a little iffy. animation registry gets angry if I give this a type param, because then stuff like findAnimationIndex requires a type param too. Not sure which is better -/** - * Function used for canceling an animation - */ -export type TCancelFunction = () => AnimationContext | AnimationContext; - -/** - * Animation of a value or list of values - */ -export interface AnimationBounds { - /** - * Starting value(s) - */ - startValue: State; - - /** - * Ending value(s) - * @default 100 - */ - endValue: State; - - /** - * Value(s) to increment/decrement the value(s) by - * @default [endValue - startValue] - */ - byValue: State; -} - /** * An easing function * @param currentTime ms elapsed @@ -59,82 +31,76 @@ export type TEasingFunction = ( duration: number ) => number; -export interface AnimationOptions extends AnimationBounds { +export type TAnimationBaseOptions = { /** - * The object this animation is being performed on + * Duration of the animation in ms + * @default 500 */ - target?: unknown; + duration: number; + + /** + * Delay to start the animation in ms + * @default 0 + */ + delay: number; /** * Called when the animation starts */ - onStart?: VoidFunction; + onStart: VoidFunction; /** - * Called at each frame of the animation + * Easing function + * @default {defaultEasing} */ - onChange: TOnAnimationChangeCallback; + easing: TEasingFunction; /** - * Called after the last frame of the animation + * Called at each frame of the animation */ - onComplete: TOnAnimationChangeCallback; + onChange: TOnAnimationChangeCallback; /** - * Easing function - * @default [defaultEasing] + * Called after the last frame of the animation */ - easing: TEasingFunction; + onComplete: TOnAnimationChangeCallback; /** * Function called at each frame. * If it returns true, abort */ - abort: TAbortCallback; + abort: TAbortCallback; /** - * Duration of the animation in ms - * @default 500 + * The object this animation is being performed on */ - duration: number; + target: unknown; +}; +export type TAnimationValues = { /** - * Delay to start the animation in ms + * Starting value(s) * @default 0 */ - delay: number; -} + startValue: T; -export interface AnimationCurrentState { - /** - * Current values - */ - currentValue: State; /** - * Same as valueRatio from @see TOnAnimationChangeCallback - */ - completionRate: number; - /** - * Same as completionRatio from @see TOnAnimationChangeCallback + * Ending value(s) + * @default 100 */ - durationRate: number; -} + endValue: T; -/** - * Animation context - */ -export interface AnimationContext - extends Partial>, - AnimationCurrentState { /** - * Current function used to cancel the animation + * Value(s) to increment/decrement the value(s) by + * @default [endValue - startValue] */ - cancel: TCancelFunction; -} - -export const isMulti = function isMulti< - Single extends Partial>, - Multi extends Partial> ->(x: Single | Multi): x is Multi { - return Array.isArray(x.startValue); + byValue: T; }; + +export type TAnimationOptions = Partial< + TAnimationValues & TAnimationBaseOptions +>; + +export type AnimationOptions = TAnimationOptions; + +export type ArrayAnimationOptions = TAnimationOptions; From d13069ce348d4ac85a15fa43191063e687233042 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 22:40:06 +0300 Subject: [PATCH 027/108] prepare for color --- src/util/animation/Animation.ts | 8 ++++-- src/util/animation/animation_registry.ts | 4 +-- src/util/animation/types.ts | 35 ++++++++++++++++++------ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/util/animation/Animation.ts b/src/util/animation/Animation.ts index 4c679f44431..9600e01e142 100644 --- a/src/util/animation/Animation.ts +++ b/src/util/animation/Animation.ts @@ -10,6 +10,7 @@ import { TAbortCallback, TEasingFunction, TOnAnimationChangeCallback, + TAnimationCallbacks, } from './types'; export abstract class AnimationBase { @@ -58,7 +59,8 @@ export abstract class AnimationBase { onComplete = noop, abort = noop, target, - }: Partial> & TAnimationValues) { + }: Partial> & + TAnimationValues) { this.startValue = startValue; this.endValue = endValue; this.byValue = byValue; @@ -132,11 +134,11 @@ export abstract class AnimationBase { }; private register() { - runningAnimations.push(this as any); + runningAnimations.push(this); } private unregister() { - runningAnimations.remove(this as any); + runningAnimations.remove(this); } abort() { diff --git a/src/util/animation/animation_registry.ts b/src/util/animation/animation_registry.ts index c4d78f39470..ea2d22b0fcc 100644 --- a/src/util/animation/animation_registry.ts +++ b/src/util/animation/animation_registry.ts @@ -5,9 +5,7 @@ import { AnimationBase } from './Animation'; /** * Array holding all running animations */ -class AnimationRegistry< - T extends AnimationBase | AnimationBase -> extends Array { +class AnimationRegistry> extends Array { remove(context: T) { const index = this.indexOf(context); index > -1 && this.splice(index, 1); diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index f12720042f3..d02aee4743c 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -1,3 +1,5 @@ +import { TColorAlphaSource, TColorArg } from '../../color/color.class'; + /** * Callback called every frame * @param {number | number[]} value current value of the animation. @@ -31,7 +33,18 @@ export type TEasingFunction = ( duration: number ) => number; -export type TAnimationBaseOptions = { +/** + * A color easing function + * @param currentTime ms elapsed + * @param duration in ms + * @returns next value + */ +export type TColorEasingRateFunction = ( + currentTime: number, + duration: number +) => number; + +export type TAnimationBaseOptions = { /** * Duration of the animation in ms * @default 500 @@ -55,6 +68,13 @@ export type TAnimationBaseOptions = { */ easing: TEasingFunction; + /** + * The object this animation is being performed on + */ + target: unknown; +}; + +export type TAnimationCallbacks = { /** * Called at each frame of the animation */ @@ -70,11 +90,6 @@ export type TAnimationBaseOptions = { * If it returns true, abort */ abort: TAbortCallback; - - /** - * The object this animation is being performed on - */ - target: unknown; }; export type TAnimationValues = { @@ -97,10 +112,14 @@ export type TAnimationValues = { byValue: T; }; -export type TAnimationOptions = Partial< - TAnimationValues & TAnimationBaseOptions +export type TAnimationOptions = Partial< + TAnimationBaseOptions & TAnimationValues & TAnimationCallbacks >; export type AnimationOptions = TAnimationOptions; export type ArrayAnimationOptions = TAnimationOptions; + +export type ColorAnimationOptions = TAnimationOptions & { + colorEasing: TColorEasingRateFunction; +}; From f5d24be9cc12f916631805057030384f35a4762c Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:11:44 +0300 Subject: [PATCH 028/108] generics --- src/util/animation/Animation.ts | 6 ++-- src/util/animation/types.ts | 50 ++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/util/animation/Animation.ts b/src/util/animation/Animation.ts index 9600e01e142..f6545a68083 100644 --- a/src/util/animation/Animation.ts +++ b/src/util/animation/Animation.ts @@ -19,7 +19,7 @@ export abstract class AnimationBase { readonly byValue: T; readonly duration: number; readonly delay: number; - protected readonly easing: TEasingFunction; + protected readonly easing: TEasingFunction; private readonly _onStart: VoidFunction; private readonly _onChange: TOnAnimationChangeCallback; private readonly _onComplete: TOnAnimationChangeCallback; @@ -59,7 +59,7 @@ export abstract class AnimationBase { onComplete = noop, abort = noop, target, - }: Partial> & + }: Partial & TAnimationCallbacks> & TAnimationValues) { this.startValue = startValue; this.endValue = endValue; @@ -191,7 +191,7 @@ export class ArrayAnimation extends AnimationBase { } protected calculate(currentTime: number) { const values = this.startValue.map((value, i) => - this.easing(currentTime, value, this.byValue[i], this.duration) + this.easing(currentTime, value, this.byValue[i], this.duration, i) ); return { value: values, diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index d02aee4743c..cfd350a68c8 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -22,29 +22,37 @@ export type TAbortCallback = TOnAnimationChangeCallback; * An easing function * @param currentTime ms elapsed * @param startValue - * @param byValue increment/change/"completion rate"/magnitude + * @param byValue * @param duration in ms * @returns next value */ -export type TEasingFunction = ( - currentTime: number, - startValue: number, - byValue: number, - duration: number -) => number; +export type TEasingFunction = T extends any[] + ? ( + currentTime: number, + startValue: number, + byValue: number, + duration: number, + index: number + ) => number + : ( + currentTime: number, + startValue: number, + byValue: number, + duration: number + ) => number; /** * A color easing function * @param currentTime ms elapsed * @param duration in ms - * @returns next value + * @returns timeRate ∈ [0, 1] */ export type TColorEasingRateFunction = ( currentTime: number, duration: number ) => number; -export type TAnimationBaseOptions = { +export type TAnimationBaseOptions = { /** * Duration of the animation in ms * @default 500 @@ -57,16 +65,11 @@ export type TAnimationBaseOptions = { */ delay: number; - /** - * Called when the animation starts - */ - onStart: VoidFunction; - /** * Easing function * @default {defaultEasing} */ - easing: TEasingFunction; + easing: TEasingFunction; /** * The object this animation is being performed on @@ -75,6 +78,11 @@ export type TAnimationBaseOptions = { }; export type TAnimationCallbacks = { + /** + * Called when the animation starts + */ + onStart: VoidFunction; + /** * Called at each frame of the animation */ @@ -112,14 +120,18 @@ export type TAnimationValues = { byValue: T; }; -export type TAnimationOptions = Partial< - TAnimationBaseOptions & TAnimationValues & TAnimationCallbacks +export type TAnimationOptions = Partial< + TAnimationBaseOptions & TAnimationValues & TAnimationCallbacks >; export type AnimationOptions = TAnimationOptions; export type ArrayAnimationOptions = TAnimationOptions; -export type ColorAnimationOptions = TAnimationOptions & { - colorEasing: TColorEasingRateFunction; +export type ColorAnimationOptions = TAnimationOptions< + TColorArg, + string, + number[] +> & { + colorEasing?: TColorEasingRateFunction; }; From ab4eb7d7724a4cd24d4623eba4aa4aa7cc664007 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:11:52 +0300 Subject: [PATCH 029/108] Update color.class.ts --- src/color/color.class.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index c41721a6b65..014478e0dbf 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -7,6 +7,8 @@ export type TColorSource = [number, number, number]; export type TColorAlphaSource = [number, number, number, number]; +export type TColorArg = string | TColorSource | TColorAlphaSource; + /** * @class Color common color operations * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors colors} @@ -18,9 +20,12 @@ export class Color { * * @param {string} [color] optional in hex or rgb(a) or hsl format or from known color list */ - constructor(color?: string) { + constructor(color?: TColorArg) { if (!color) { this.setSource([0, 0, 0, 1]); + } else if (Array.isArray(color)) { + const [r, g, b, a = 1] = color; + this.setSource([r, g, b, a]); } else { this._tryParsingColor(color); } From d3c998b09bfe516bfe97c7ea585dfb0d514050b0 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:13:31 +0300 Subject: [PATCH 030/108] refactor(): color animation --- src/util/animation/ColorAnimation.ts | 65 +++++++++++++++++++++ src/util/animation/animate.ts | 21 ++++++- src/util/animation/animate_color.ts | 85 ---------------------------- 3 files changed, 85 insertions(+), 86 deletions(-) create mode 100644 src/util/animation/ColorAnimation.ts delete mode 100644 src/util/animation/animate_color.ts diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts new file mode 100644 index 00000000000..d000886d6f3 --- /dev/null +++ b/src/util/animation/ColorAnimation.ts @@ -0,0 +1,65 @@ +import { Color } from '../../color'; +import { TColorAlphaSource } from '../../color/color.class'; +import { noop } from '../../constants'; +import { AnimationBase } from './Animation'; +import { + ColorAnimationOptions, + TColorEasingRateFunction, + TOnAnimationChangeCallback, +} from './types'; + +const wrapColorCallback = + (callback: TOnAnimationChangeCallback) => + (rgba: TColorAlphaSource, valueRatio: number, timeRatio: number) => + callback(new Color(rgba).toRgba(), valueRatio, timeRatio); + +const defaultColorEasingRate: TColorEasingRateFunction = ( + currentTime, + duration +) => 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); + +export class ColorAnimation extends AnimationBase { + readonly colorEasing: TColorEasingRateFunction; + constructor({ + startValue, + endValue, + easing = (currentTime, startValue, byValue) => startValue + byValue, + colorEasing = defaultColorEasingRate, + onChange = noop, + onComplete = noop, + abort = noop, + ...options + }: ColorAnimationOptions) { + const startColor = new Color(startValue).getSource(); + const endColor = new Color(endValue).getSource(); + super({ + ...options, + startValue: startColor, + endValue: endColor, + byValue: endColor.map( + (value, i) => value - startColor[i] + ) as TColorAlphaSource, + easing, + onChange: wrapColorCallback(onChange), + onComplete: wrapColorCallback(onComplete), + abort: wrapColorCallback(abort), + }); + this.colorEasing = colorEasing; + } + protected calculate(currentTime: number) { + const changeRate = this.colorEasing(currentTime, this.duration); + const rgba = this.startValue.map((value, i) => + this.easing( + currentTime, + value, + changeRate * this.byValue[i], + this.duration, + i + ) + ) as TColorAlphaSource; + return { + value: rgba, + changeRate, + }; + } +} diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 1d13c433cf9..08f59708db4 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -1,5 +1,11 @@ +import { TColorArg } from '../../color/color.class'; import { ArrayAnimation, ValueAnimation } from './Animation'; -import { AnimationOptions, ArrayAnimationOptions } from './types'; +import { ColorAnimation } from './ColorAnimation'; +import { + AnimationOptions, + ArrayAnimationOptions, + ColorAnimationOptions, +} from './types'; const isArrayAnimation = ( options: ArrayAnimationOptions | AnimationOptions @@ -42,3 +48,16 @@ export const animate = (options: AnimationOptions | ArrayAnimationOptions) => ? new ArrayAnimation(options) : new ValueAnimation(options) ).start(); + +export const animateColor = ( + startValue: TColorArg, + endValue: TColorArg, + duration?: number, + options?: ColorAnimationOptions +) => + new ColorAnimation({ + ...options, + startValue, + endValue, + duration, + }).start(); diff --git a/src/util/animation/animate_color.ts b/src/util/animation/animate_color.ts deleted file mode 100644 index b8ad490a43f..00000000000 --- a/src/util/animation/animate_color.ts +++ /dev/null @@ -1,85 +0,0 @@ -//@ts-nocheck -import { Color } from '../../color'; -import { animate } from './animate'; - -// Calculate an in-between color. Returns a "rgba()" string. -// Credit: Edwin Martin -// http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js -// const calculateColor = (begin: number[], end: number[], pos) => { -// const [r, g, b, _a] = begin.map((beg, index) => beg + pos * (end[index] - beg)); -// const a = begin && end ? parseFloat(_a) : 1; -// return `rgba(${parseInt(r, 10)},${parseInt(g, 10)},${parseInt(b, 10)},${a})`; -// } - -// color animation is broken. This function pass the tests for some reasons -// but begin and end aren't array anymore since we improved animate function -// to handler arrays internally. -function calculateColor(begin, end, pos) { - let color = - 'rgba(' + - parseInt(begin[0] + pos * (end[0] - begin[0]), 10) + - ',' + - parseInt(begin[1] + pos * (end[1] - begin[1]), 10) + - ',' + - parseInt(begin[2] + pos * (end[2] - begin[2]), 10); - - color += - ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); - color += ')'; - return color; -} - -const defaultColorEasing = (currentTime, duration) => - 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); - -/** - * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed. - * @memberOf fabric.util - * @param {String} fromColor The starting color in hex or rgb(a) format. - * @param {String} toColor The starting color in hex or rgb(a) format. - * @param {Number} [duration] Duration of change (in ms). - * @param {Object} [options] Animation options - * @param {Function} [options.onChange] Callback; invoked on every value change - * @param {Function} [options.onComplete] Callback; invoked when value change is completed - * @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used. - * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called. - * @returns {Function} abort function - */ -export function animateColor( - fromColor, - toColor, - duration = 500, - { - colorEasing = defaultColorEasing, - onComplete, - onChange, - ...restOfOptions - } = {} -) { - const startColor = new Color(fromColor).getSource(), - endColor = new Color(toColor).getSource(); - return animate({ - ...restOfOptions, - duration, - startValue: startColor, - endValue: endColor, - byValue: endColor, - easing: (currentTime, startValue, byValue, duration) => - calculateColor(startValue, byValue, colorEasing(currentTime, duration)), - // has to take in account for color restoring; - onComplete: (current, valuePerc, timePerc) => - onComplete?.(calculateColor(endColor, endColor, 0), valuePerc, timePerc), - onChange: (current, valuePerc, timePerc) => { - if (onChange) { - if (Array.isArray(current)) { - return onChange( - calculateColor(current, current, 0), - valuePerc, - timePerc - ); - } - onChange(current, valuePerc, timePerc); - } - }, - }); -} From 00c1adfc49336360c561fbd02c090476f64f194a Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:19:54 +0300 Subject: [PATCH 031/108] refactor(): extract to files --- src/util/animation/Animation.ts | 177 +----------------- src/util/animation/AnimationBase.ts | 146 +++++++++++++++ ...tionFrame.ts => AnimationFrameProvider.ts} | 0 src/util/animation/ArrayAnimation.ts | 27 +++ src/util/animation/ColorAnimation.ts | 2 +- src/util/animation/animate.ts | 5 +- src/util/animation/animation_registry.ts | 2 +- src/util/animation/index.ts | 3 +- src/util/animation/types.ts | 2 +- 9 files changed, 183 insertions(+), 181 deletions(-) create mode 100644 src/util/animation/AnimationBase.ts rename src/util/animation/{AnimationFrame.ts => AnimationFrameProvider.ts} (100%) create mode 100644 src/util/animation/ArrayAnimation.ts diff --git a/src/util/animation/Animation.ts b/src/util/animation/Animation.ts index f6545a68083..511ced97d5b 100644 --- a/src/util/animation/Animation.ts +++ b/src/util/animation/Animation.ts @@ -1,153 +1,7 @@ -import { noop } from '../../constants'; -import { requestAnimFrame } from './AnimationFrame'; -import { runningAnimations } from './animation_registry'; -import { defaultEasing } from './easing'; -import { - TAnimationBaseOptions, - AnimationOptions, - TAnimationValues, - ArrayAnimationOptions, - TAbortCallback, - TEasingFunction, - TOnAnimationChangeCallback, - TAnimationCallbacks, -} from './types'; +import { AnimationBase } from './AnimationBase'; +import { AnimationOptions } from './types'; -export abstract class AnimationBase { - readonly startValue: T; - readonly endValue: T; - readonly byValue: T; - readonly duration: number; - readonly delay: number; - protected readonly easing: TEasingFunction; - private readonly _onStart: VoidFunction; - private readonly _onChange: TOnAnimationChangeCallback; - private readonly _onComplete: TOnAnimationChangeCallback; - private readonly _abort: TAbortCallback; - /** - * used to register the animation to a target object so it can be cancelled within hte object context - */ - readonly target?: unknown; - - state: 'pending' | 'running' | 'completed' | 'aborted' = 'pending'; - /** - * time % - */ - xRate = 0; - /** - * value % - */ - yRate = 0; - /** - * current value - */ - value: T; - /** - * animation start time ms - */ - private startTime!: number; - - constructor({ - startValue, - endValue, - byValue, - duration = 500, - delay = 0, - easing = defaultEasing, - onStart = noop, - onChange = noop, - onComplete = noop, - abort = noop, - target, - }: Partial & TAnimationCallbacks> & - TAnimationValues) { - this.startValue = startValue; - this.endValue = endValue; - this.byValue = byValue; - this.duration = duration; - this.delay = delay; - this.easing = easing; - this._onStart = onStart; - this._onChange = onChange; - this._onComplete = onComplete; - this._abort = abort; - this.value = this.startValue; - this.target = target; - } - - protected abstract calculate(currentTime: number): { - value: T; - changeRate: number; - }; - - start() { - const runner: FrameRequestCallback = (timestamp) => { - this.startTime = timestamp; - this.state = 'running'; - this._onStart(); - this.tick(timestamp); - }; - - this.register(); - - // setTimeout(cb, 0) will run cb on the next frame, causing a delay - // we don't want that - if (this.delay > 0) { - setTimeout(() => requestAnimFrame(runner), this.delay); - } else { - requestAnimFrame(runner); - } - } - - private tick: FrameRequestCallback = (t: number) => { - const durationMs = t - this.startTime; - const boundDurationMs = Math.min(durationMs, this.duration); - this.xRate = boundDurationMs / this.duration; - const { value, changeRate } = this.calculate(boundDurationMs); - this.value = Array.isArray(value) ? (value.slice() as T) : value; - this.yRate = changeRate; - - if (this.state === 'aborted') { - return; - } - if (this._abort(value, this.yRate, this.xRate)) { - this.state = 'aborted'; - this.unregister(); - } - if (durationMs > boundDurationMs) { - // TODO this line seems redundant - this.xRate = this.yRate = 1; - this._onChange( - (Array.isArray(this.endValue) - ? this.endValue.slice() - : this.endValue) as T, - this.yRate, - this.xRate - ); - this.state = 'completed'; - this._onComplete(this.endValue, this.yRate, this.xRate); - this.unregister(); - } else { - this._onChange(value, this.yRate, this.xRate); - requestAnimFrame(this.tick); - } - }; - - private register() { - runningAnimations.push(this); - } - - private unregister() { - runningAnimations.remove(this); - } - - abort() { - this.state = 'aborted'; - this.unregister(); - } -} - -export class ValueAnimation extends AnimationBase { +export class Animation extends AnimationBase { constructor({ startValue = 0, endValue = 100, @@ -174,28 +28,3 @@ export class ValueAnimation extends AnimationBase { }; } } - -export class ArrayAnimation extends AnimationBase { - constructor({ - startValue = [0], - endValue = [100], - byValue = endValue.map((value, i) => value - startValue[i]), - ...options - }: ArrayAnimationOptions) { - super({ - ...options, - startValue, - endValue, - byValue, - }); - } - protected calculate(currentTime: number) { - const values = this.startValue.map((value, i) => - this.easing(currentTime, value, this.byValue[i], this.duration, i) - ); - return { - value: values, - changeRate: Math.abs((values[0] - this.startValue[0]) / this.byValue[0]), - }; - } -} diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts new file mode 100644 index 00000000000..a464ee55e4a --- /dev/null +++ b/src/util/animation/AnimationBase.ts @@ -0,0 +1,146 @@ +import { noop } from '../../constants'; +import { requestAnimFrame } from './AnimationFrameProvider'; +import { runningAnimations } from './animation_registry'; +import { defaultEasing } from './easing'; +import { + TAnimationBaseOptions, + TAnimationValues, + TAbortCallback, + TEasingFunction, + TOnAnimationChangeCallback, + TAnimationCallbacks, +} from './types'; + +export abstract class AnimationBase { + readonly startValue: T; + readonly endValue: T; + readonly byValue: T; + readonly duration: number; + readonly delay: number; + protected readonly easing: TEasingFunction; + private readonly _onStart: VoidFunction; + private readonly _onChange: TOnAnimationChangeCallback; + private readonly _onComplete: TOnAnimationChangeCallback; + private readonly _abort: TAbortCallback; + /** + * used to register the animation to a target object so it can be cancelled within hte object context + */ + readonly target?: unknown; + + state: 'pending' | 'running' | 'completed' | 'aborted' = 'pending'; + /** + * time % + */ + xRate = 0; + /** + * value % + */ + yRate = 0; + /** + * current value + */ + value: T; + /** + * animation start time ms + */ + private startTime!: number; + + constructor({ + startValue, + endValue, + byValue, + duration = 500, + delay = 0, + easing = defaultEasing, + onStart = noop, + onChange = noop, + onComplete = noop, + abort = noop, + target, + }: Partial & TAnimationCallbacks> & + TAnimationValues) { + this.startValue = startValue; + this.endValue = endValue; + this.byValue = byValue; + this.duration = duration; + this.delay = delay; + this.easing = easing; + this._onStart = onStart; + this._onChange = onChange; + this._onComplete = onComplete; + this._abort = abort; + this.value = this.startValue; + this.target = target; + } + + protected abstract calculate(currentTime: number): { + value: T; + changeRate: number; + }; + + start() { + const runner: FrameRequestCallback = (timestamp) => { + this.startTime = timestamp; + this.state = 'running'; + this._onStart(); + this.tick(timestamp); + }; + + this.register(); + + // setTimeout(cb, 0) will run cb on the next frame, causing a delay + // we don't want that + if (this.delay > 0) { + setTimeout(() => requestAnimFrame(runner), this.delay); + } else { + requestAnimFrame(runner); + } + } + + private tick: FrameRequestCallback = (t: number) => { + const durationMs = t - this.startTime; + const boundDurationMs = Math.min(durationMs, this.duration); + this.xRate = boundDurationMs / this.duration; + const { value, changeRate } = this.calculate(boundDurationMs); + this.value = Array.isArray(value) ? (value.slice() as T) : value; + this.yRate = changeRate; + + if (this.state === 'aborted') { + return; + } + if (this._abort(value, this.yRate, this.xRate)) { + this.state = 'aborted'; + this.unregister(); + } + if (durationMs > boundDurationMs) { + // TODO this line seems redundant + this.xRate = this.yRate = 1; + this._onChange( + (Array.isArray(this.endValue) + ? this.endValue.slice() + : this.endValue) as T, + this.yRate, + this.xRate + ); + this.state = 'completed'; + this._onComplete(this.endValue, this.yRate, this.xRate); + this.unregister(); + } else { + this._onChange(value, this.yRate, this.xRate); + requestAnimFrame(this.tick); + } + }; + + private register() { + runningAnimations.push(this); + } + + private unregister() { + runningAnimations.remove(this); + } + + abort() { + this.state = 'aborted'; + this.unregister(); + } +} diff --git a/src/util/animation/AnimationFrame.ts b/src/util/animation/AnimationFrameProvider.ts similarity index 100% rename from src/util/animation/AnimationFrame.ts rename to src/util/animation/AnimationFrameProvider.ts diff --git a/src/util/animation/ArrayAnimation.ts b/src/util/animation/ArrayAnimation.ts new file mode 100644 index 00000000000..76bc5125c81 --- /dev/null +++ b/src/util/animation/ArrayAnimation.ts @@ -0,0 +1,27 @@ +import { AnimationBase } from './AnimationBase'; +import { ArrayAnimationOptions } from './types'; + +export class ArrayAnimation extends AnimationBase { + constructor({ + startValue = [0], + endValue = [100], + byValue = endValue.map((value, i) => value - startValue[i]), + ...options + }: ArrayAnimationOptions) { + super({ + ...options, + startValue, + endValue, + byValue, + }); + } + protected calculate(currentTime: number) { + const values = this.startValue.map((value, i) => + this.easing(currentTime, value, this.byValue[i], this.duration, i) + ); + return { + value: values, + changeRate: Math.abs((values[0] - this.startValue[0]) / this.byValue[0]), + }; + } +} diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index d000886d6f3..4c86987becc 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -1,7 +1,7 @@ import { Color } from '../../color'; import { TColorAlphaSource } from '../../color/color.class'; import { noop } from '../../constants'; -import { AnimationBase } from './Animation'; +import { AnimationBase } from './AnimationBase'; import { ColorAnimationOptions, TColorEasingRateFunction, diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 08f59708db4..34be072e733 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -1,5 +1,6 @@ import { TColorArg } from '../../color/color.class'; -import { ArrayAnimation, ValueAnimation } from './Animation'; +import { Animation } from './Animation'; +import { ArrayAnimation } from './ArrayAnimation'; import { ColorAnimation } from './ColorAnimation'; import { AnimationOptions, @@ -46,7 +47,7 @@ const isArrayAnimation = ( export const animate = (options: AnimationOptions | ArrayAnimationOptions) => (isArrayAnimation(options) ? new ArrayAnimation(options) - : new ValueAnimation(options) + : new Animation(options) ).start(); export const animateColor = ( diff --git a/src/util/animation/animation_registry.ts b/src/util/animation/animation_registry.ts index ea2d22b0fcc..36f1f564e75 100644 --- a/src/util/animation/animation_registry.ts +++ b/src/util/animation/animation_registry.ts @@ -1,6 +1,6 @@ import { fabric } from '../../../HEADER'; import { Canvas, TObject } from '../../__types__'; -import { AnimationBase } from './Animation'; +import { AnimationBase } from './AnimationBase'; /** * Array holding all running animations diff --git a/src/util/animation/index.ts b/src/util/animation/index.ts index 97fb52c65d2..22957bde588 100644 --- a/src/util/animation/index.ts +++ b/src/util/animation/index.ts @@ -1,6 +1,5 @@ export * from './animate'; -export * from './animate_color'; +export * from './AnimationFrameProvider'; export * from './animation_registry'; export * as ease from './easing'; -export * from './AnimationFrame'; export * from './types'; diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index cfd350a68c8..bd5e37dab70 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -26,7 +26,7 @@ export type TAbortCallback = TOnAnimationChangeCallback; * @param duration in ms * @returns next value */ -export type TEasingFunction = T extends any[] +export type TEasingFunction = T extends any[] ? ( currentTime: number, startValue: number, From a1466cdd0f9adf71a8abe348fa53f07f6d25798f Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:21:27 +0300 Subject: [PATCH 032/108] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2abd73ba20..9337eb9fb8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## [next] -- chore(TS): `animate` and `AnimationRegistry` typing [#8297](https://github.com/fabricjs/fabric.js/pull/8297) +- refactor(TS): `animate` and `AnimationRegistry` to classes [#8297](https://github.com/fabricjs/fabric.js/pull/8297) - BREAKING fix(polyline/polygon): stroke bounding box for all line join/cap cases [#8344](https://github.com/fabricjs/fabric.js/pull/8344) BREAKING: `_setPositionDimensions` was removed in favor of `setDimensions` - test(): Added 2 tests for polygon shapes and transforms with translations [#8370](https://github.com/fabricjs/fabric.js/pull/8370) From 79628f433a0abf17ea0c908263c4de255893e5fd Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:24:05 +0300 Subject: [PATCH 033/108] rename --- src/util/animation/AnimationBase.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index a464ee55e4a..d41e1f6e9c8 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -79,7 +79,7 @@ export abstract class AnimationBase { }; start() { - const runner: FrameRequestCallback = (timestamp) => { + const firstTick: FrameRequestCallback = (timestamp) => { this.startTime = timestamp; this.state = 'running'; this._onStart(); @@ -91,9 +91,9 @@ export abstract class AnimationBase { // setTimeout(cb, 0) will run cb on the next frame, causing a delay // we don't want that if (this.delay > 0) { - setTimeout(() => requestAnimFrame(runner), this.delay); + setTimeout(() => requestAnimFrame(firstTick), this.delay); } else { - requestAnimFrame(runner); + requestAnimFrame(firstTick); } } From f5842c759af978f9d6e2e20cceee19e4dbe8792b Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:24:29 +0300 Subject: [PATCH 034/108] rename --- src/util/animation/AnimationBase.ts | 2 +- .../animation/{animation_registry.ts => AnimationRegistry.ts} | 0 src/util/animation/index.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/util/animation/{animation_registry.ts => AnimationRegistry.ts} (100%) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index d41e1f6e9c8..80b6ad0b452 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -1,6 +1,6 @@ import { noop } from '../../constants'; import { requestAnimFrame } from './AnimationFrameProvider'; -import { runningAnimations } from './animation_registry'; +import { runningAnimations } from './AnimationRegistry'; import { defaultEasing } from './easing'; import { TAnimationBaseOptions, diff --git a/src/util/animation/animation_registry.ts b/src/util/animation/AnimationRegistry.ts similarity index 100% rename from src/util/animation/animation_registry.ts rename to src/util/animation/AnimationRegistry.ts diff --git a/src/util/animation/index.ts b/src/util/animation/index.ts index 22957bde588..f1fea7f06de 100644 --- a/src/util/animation/index.ts +++ b/src/util/animation/index.ts @@ -1,5 +1,5 @@ export * from './animate'; export * from './AnimationFrameProvider'; -export * from './animation_registry'; +export * from './AnimationRegistry'; export * as ease from './easing'; export * from './types'; From 7134f759247468d7b21f67faadd306bbec93999e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:32:38 +0300 Subject: [PATCH 035/108] naming --- src/util/animation/AnimationBase.ts | 20 ++++++++++---------- src/util/animation/ColorAnimation.ts | 4 ++-- src/util/animation/types.ts | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index 80b6ad0b452..c5a8140cda2 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -31,11 +31,11 @@ export abstract class AnimationBase { /** * time % */ - xRate = 0; + durationRate = 0; /** * value % */ - yRate = 0; + valueRate = 0; /** * current value */ @@ -100,33 +100,33 @@ export abstract class AnimationBase { private tick: FrameRequestCallback = (t: number) => { const durationMs = t - this.startTime; const boundDurationMs = Math.min(durationMs, this.duration); - this.xRate = boundDurationMs / this.duration; + this.durationRate = boundDurationMs / this.duration; const { value, changeRate } = this.calculate(boundDurationMs); this.value = Array.isArray(value) ? (value.slice() as T) : value; - this.yRate = changeRate; + this.valueRate = changeRate; if (this.state === 'aborted') { return; } - if (this._abort(value, this.yRate, this.xRate)) { + if (this._abort(value, this.valueRate, this.durationRate)) { this.state = 'aborted'; this.unregister(); } if (durationMs > boundDurationMs) { // TODO this line seems redundant - this.xRate = this.yRate = 1; + this.durationRate = this.valueRate = 1; this._onChange( (Array.isArray(this.endValue) ? this.endValue.slice() : this.endValue) as T, - this.yRate, - this.xRate + this.valueRate, + this.durationRate ); this.state = 'completed'; - this._onComplete(this.endValue, this.yRate, this.xRate); + this._onComplete(this.endValue, this.valueRate, this.durationRate); this.unregister(); } else { - this._onChange(value, this.yRate, this.xRate); + this._onChange(value, this.valueRate, this.durationRate); requestAnimFrame(this.tick); } }; diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index 4c86987becc..49be55ada18 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -10,8 +10,8 @@ import { const wrapColorCallback = (callback: TOnAnimationChangeCallback) => - (rgba: TColorAlphaSource, valueRatio: number, timeRatio: number) => - callback(new Color(rgba).toRgba(), valueRatio, timeRatio); + (rgba: TColorAlphaSource, valueRate: number, durationRate: number) => + callback(new Color(rgba).toRgba(), valueRate, durationRate); const defaultColorEasingRate: TColorEasingRateFunction = ( currentTime, diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index bd5e37dab70..83bffad3217 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -1,15 +1,15 @@ -import { TColorAlphaSource, TColorArg } from '../../color/color.class'; +import { TColorArg } from '../../color/color.class'; /** * Callback called every frame * @param {number | number[]} value current value of the animation. - * @param valueRatio ratio of current value to animation max value. [0, 1] - * @param timeRatio ratio of current ms to animation duration. [0, 1] + * @param valueRate ∈ [0, 1], current value / end value. + * @param durationRate ∈ [0, 1], time passed / duration. */ export type TOnAnimationChangeCallback = ( value: T, - valueRatio: number, - timeRatio: number + valueRate: number, + durationRate: number ) => R; /** @@ -45,7 +45,7 @@ export type TEasingFunction = T extends any[] * A color easing function * @param currentTime ms elapsed * @param duration in ms - * @returns timeRate ∈ [0, 1] + * @returns durationRate ∈ [0, 1] */ export type TColorEasingRateFunction = ( currentTime: number, From d5edccb27ecf9d7a30f4deb73f485f14209f33eb Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:37:31 +0300 Subject: [PATCH 036/108] cleanup --- src/util/animation/easing.ts | 39 ++++-------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/src/util/animation/easing.ts b/src/util/animation/easing.ts index 40eae67912a..8f314b9500c 100644 --- a/src/util/animation/easing.ts +++ b/src/util/animation/easing.ts @@ -1,12 +1,11 @@ -import { twoMathPi, halfPI } from '../../constants'; -import { TEasingFunction } from './types'; - /** * Easing functions - * See Easing Equations by Robert Penner - * @namespace fabric.util.ease + * @see {@link http://gizma.com/easing/ Easing Equations by Robert Penner} */ +import { twoMathPi, halfPI } from '../../constants'; +import { TEasingFunction } from './types'; + /** * TODO: ask about docs for this, I don't understand it * @param a @@ -46,14 +45,12 @@ export const defaultEasing: TEasingFunction = (t, b, c, d) => /** * Cubic easing out - * @memberOf fabric.util.ease */ export const easeOutCubic: TEasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 2 + 1) + b; /** * Cubic easing in and out - * @memberOf fabric.util.ease */ export const easeInOutCubic: TEasingFunction = (t, b, c, d) => { t /= d / 2; @@ -65,21 +62,18 @@ export const easeInOutCubic: TEasingFunction = (t, b, c, d) => { /** * Quartic easing in - * @memberOf fabric.util.ease */ export const easeInQuart: TEasingFunction = (t, b, c, d) => c * (t /= d) * t ** 3 + b; /** * Quartic easing out - * @memberOf fabric.util.ease */ export const easeOutQuart: TEasingFunction = (t, b, c, d) => -c * ((t = t / d - 1) * t ** 3 - 1) + b; /** * Quartic easing in and out - * @memberOf fabric.util.ease */ export const easeInOutQuart: TEasingFunction = (t, b, c, d) => { t /= d / 2; @@ -91,21 +85,18 @@ export const easeInOutQuart: TEasingFunction = (t, b, c, d) => { /** * Quintic easing in - * @memberOf fabric.util.ease */ export const easeInQuint: TEasingFunction = (t, b, c, d) => c * (t /= d) * t ** 4 + b; /** * Quintic easing out - * @memberOf fabric.util.ease */ export const easeOutQuint: TEasingFunction = (t, b, c, d) => c * ((t /= d - 1) * t ** 4 + 1) + b; /** * Quintic easing in and out - * @memberOf fabric.util.ease */ export const easeInOutQuint: TEasingFunction = (t, b, c, d) => { t /= d / 2; @@ -117,42 +108,36 @@ export const easeInOutQuint: TEasingFunction = (t, b, c, d) => { /** * Sinusoidal easing in - * @memberOf fabric.util.ease */ export const easeInSine: TEasingFunction = (t, b, c, d) => -c * Math.cos((t / d) * halfPI) + c + b; /** * Sinusoidal easing out - * @memberOf fabric.util.ease */ export const easeOutSine: TEasingFunction = (t, b, c, d) => c * Math.sin((t / d) * halfPI) + b; /** * Sinusoidal easing in and out - * @memberOf fabric.util.ease */ export const easeInOutSine: TEasingFunction = (t, b, c, d) => (-c / 2) * (Math.cos((Math.PI * t) / d) - 1) + b; /** * Exponential easing in - * @memberOf fabric.util.ease */ export const easeInExpo: TEasingFunction = (t, b, c, d) => t === 0 ? b : c * 2 ** (10 * (t / d - 1)) + b; /** * Exponential easing out - * @memberOf fabric.util.ease */ export const easeOutExpo: TEasingFunction = (t, b, c, d) => t === d ? b + c : c * -(2 ** ((-10 * t) / d) + 1) + b; /** * Exponential easing in and out - * @memberOf fabric.util.ease */ export const easeInOutExpo: TEasingFunction = (t, b, c, d) => { if (t === 0) { @@ -170,21 +155,18 @@ export const easeInOutExpo: TEasingFunction = (t, b, c, d) => { /** * Circular easing in - * @memberOf fabric.util.ease */ export const easeInCirc: TEasingFunction = (t, b, c, d) => -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; /** * Circular easing out - * @memberOf fabric.util.ease */ export const easeOutCirc: TEasingFunction = (t, b, c, d) => c * Math.sqrt(1 - (t = t / d - 1) * t) + b; /** * Circular easing in and out - * @memberOf fabric.util.ease */ export const easeInOutCirc: TEasingFunction = (t, b, c, d) => { t /= d / 2; @@ -196,7 +178,6 @@ export const easeInOutCirc: TEasingFunction = (t, b, c, d) => { /** * Elastic easing in - * @memberOf fabric.util.ease */ export const easeInElastic: TEasingFunction = (t, b, c, d) => { const s = 1.70158, @@ -218,7 +199,6 @@ export const easeInElastic: TEasingFunction = (t, b, c, d) => { /** * Elastic easing out - * @memberOf fabric.util.ease */ export const easeOutElastic: TEasingFunction = (t, b, c, d) => { const s = 1.70158, @@ -244,7 +224,6 @@ export const easeOutElastic: TEasingFunction = (t, b, c, d) => { /** * Elastic easing in and out - * @memberOf fabric.util.ease */ export const easeInOutElastic: TEasingFunction = (t, b, c, d) => { const s = 1.70158, @@ -276,21 +255,18 @@ export const easeInOutElastic: TEasingFunction = (t, b, c, d) => { /** * Backwards easing in - * @memberOf fabric.util.ease */ export const easeInBack: TEasingFunction = (t, b, c, d, s = 1.70158) => c * (t /= d) * t * ((s + 1) * t - s) + b; /** * Backwards easing out - * @memberOf fabric.util.ease */ export const easeOutBack: TEasingFunction = (t, b, c, d, s = 1.70158) => c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; /** * Backwards easing in and out - * @memberOf fabric.util.ease */ export const easeInOutBack: TEasingFunction = (t, b, c, d, s = 1.70158) => { t /= d / 2; @@ -302,7 +278,6 @@ export const easeInOutBack: TEasingFunction = (t, b, c, d, s = 1.70158) => { /** * Bouncing easing out - * @memberOf fabric.util.ease */ export const easeOutBounce: TEasingFunction = (t, b, c, d) => { if ((t /= d) < 1 / 2.75) { @@ -318,14 +293,12 @@ export const easeOutBounce: TEasingFunction = (t, b, c, d) => { /** * Bouncing easing in - * @memberOf fabric.util.ease */ export const easeInBounce: TEasingFunction = (t, b, c, d) => c - easeOutBounce(d - t, 0, c, d) + b; /** * Bouncing easing in and out - * @memberOf fabric.util.ease */ export const easeInOutBounce: TEasingFunction = (t, b, c, d) => t < d / 2 @@ -334,20 +307,17 @@ export const easeInOutBounce: TEasingFunction = (t, b, c, d) => /** * Quadratic easing in - * @memberOf fabric.util.ease */ export const easeInQuad: TEasingFunction = (t, b, c, d) => c * (t /= d) * t + b; /** * Quadratic easing out - * @memberOf fabric.util.ease */ export const easeOutQuad: TEasingFunction = (t, b, c, d) => -c * (t /= d) * (t - 2) + b; /** * Quadratic easing in and out - * @memberOf fabric.util.ease */ export const easeInOutQuad: TEasingFunction = (t, b, c, d) => { t /= d / 2; @@ -359,7 +329,6 @@ export const easeInOutQuad: TEasingFunction = (t, b, c, d) => { /** * Cubic easing in - * @memberOf fabric.util.ease */ export const easeInCubic: TEasingFunction = (t, b, c, d) => c * (t /= d) * t * t + b; From 55f62b7c1ce55abcffcd6669be3d9659025db97e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:38:11 +0300 Subject: [PATCH 037/108] cleanup --- src/util/animation/AnimationFrameProvider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util/animation/AnimationFrameProvider.ts b/src/util/animation/AnimationFrameProvider.ts index 47724fb8eb2..c410676cd40 100644 --- a/src/util/animation/AnimationFrameProvider.ts +++ b/src/util/animation/AnimationFrameProvider.ts @@ -12,7 +12,6 @@ const _cancelAnimFrame: AnimationFrameProvider['cancelAnimationFrame'] = /** * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method - * @memberOf fabric.util * @param {Function} callback Callback to invoke */ export function requestAnimFrame(callback: FrameRequestCallback): number { From ce996cf164e10212068b4f96809a570b4dfa657f Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:51:43 +0300 Subject: [PATCH 038/108] Update types.ts --- src/util/animation/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 83bffad3217..9bf92cd0d0d 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -114,7 +114,7 @@ export type TAnimationValues = { endValue: T; /** - * Value(s) to increment/decrement the value(s) by + * Difference between the start value(s) to the end value(s) * @default [endValue - startValue] */ byValue: T; From 52211c8719d2125cbb49a241f8c9e01d049a15ac Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 22 Oct 2022 23:54:30 +0300 Subject: [PATCH 039/108] Update misc.ts --- src/util/misc/misc.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/util/misc/misc.ts b/src/util/misc/misc.ts index c3fa3bf617f..2e0d711b6e9 100644 --- a/src/util/misc/misc.ts +++ b/src/util/misc/misc.ts @@ -92,10 +92,13 @@ import { } from '../dom_misc'; import { isTransparent } from './isTransparent'; import { mergeClipPaths } from './mergeClipPaths'; -import * as ease from '../animation/easing'; -import { animateColor } from '../animation'; -import { animate } from '../animation'; -import { requestAnimFrame, cancelAnimFrame } from "../animation"; +import { + animate, + animateColor, + ease, + requestAnimFrame, + cancelAnimFrame, +} from '../animation'; import { createClass } from '../lang_class'; /** * @namespace fabric.util From fceb050e88214684d2c528ea73ea90d84f8d0ada Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 00:06:19 +0300 Subject: [PATCH 040/108] cleanup --- src/util/animation/AnimationRegistry.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/util/animation/AnimationRegistry.ts b/src/util/animation/AnimationRegistry.ts index 36f1f564e75..25d5730ab75 100644 --- a/src/util/animation/AnimationRegistry.ts +++ b/src/util/animation/AnimationRegistry.ts @@ -24,9 +24,6 @@ class AnimationRegistry> extends Array { * cancel all running animations attached to canvas at the next requestAnimFrame */ cancelByCanvas(canvas: Canvas) { - if (!canvas) { - return []; - } const animations = this.filter( (animation) => typeof animation.target === 'object' && From f53e2f5a3b8b3085c655b9f690f7a0ec479d4e9b Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 00:09:35 +0300 Subject: [PATCH 041/108] BREAKING: return context --- src/util/animation/animate.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 34be072e733..d52501e200e 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -44,21 +44,26 @@ const isArrayAnimation = ( * }); * */ -export const animate = (options: AnimationOptions | ArrayAnimationOptions) => - (isArrayAnimation(options) +export const animate = (options: AnimationOptions | ArrayAnimationOptions) => { + const animation = isArrayAnimation(options) ? new ArrayAnimation(options) - : new Animation(options) - ).start(); + : new Animation(options); + animation.start(); + return animation; +}; export const animateColor = ( startValue: TColorArg, endValue: TColorArg, duration?: number, options?: ColorAnimationOptions -) => - new ColorAnimation({ +) => { + const animation = new ColorAnimation({ ...options, startValue, endValue, duration, - }).start(); + }); + animation.start(); + return animation; +}; From 2142869914ab6f062701454c04dc335224965cfe Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 00:12:30 +0300 Subject: [PATCH 042/108] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca94b1e5dc8..18470b0f90c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [next] - refactor(TS): `animate` and `AnimationRegistry` to classes [#8297](https://github.com/fabricjs/fabric.js/pull/8297) + BREAKING: return animation instance from animate instead of a cancel function and remove `findAnimationByXXX` from `AnimationRegistry` - chore(TS): convert object to es6 class [#8322](https://github.com/fabricjs/fabric.js/pull/8322) - docs(): guides follow up, feature request template [#8379](https://github.com/fabricjs/fabric.js/pull/8379) - docs(): refactor guides, bug report template [#8189](https://github.com/fabricjs/fabric.js/pull/8189) From f6929671fefb03ac3cd231681add8e97adf533b6 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 00:52:09 +0300 Subject: [PATCH 043/108] fix timestamps --- src/util/animation/AnimationBase.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index c5a8140cda2..decb131fcf9 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -79,7 +79,7 @@ export abstract class AnimationBase { }; start() { - const firstTick: FrameRequestCallback = (timestamp) => { + const firstTick: FrameRequestCallback = (timestamp = +new Date()) => { this.startTime = timestamp; this.state = 'running'; this._onStart(); @@ -97,7 +97,7 @@ export abstract class AnimationBase { } } - private tick: FrameRequestCallback = (t: number) => { + private tick: FrameRequestCallback = (t: number = +new Date()) => { const durationMs = t - this.startTime; const boundDurationMs = Math.min(durationMs, this.duration); this.durationRate = boundDurationMs / this.duration; @@ -107,12 +107,10 @@ export abstract class AnimationBase { if (this.state === 'aborted') { return; - } - if (this._abort(value, this.valueRate, this.durationRate)) { + } else if (this._abort(value, this.valueRate, this.durationRate)) { this.state = 'aborted'; this.unregister(); - } - if (durationMs > boundDurationMs) { + } else if (durationMs >= this.duration) { // TODO this line seems redundant this.durationRate = this.valueRate = 1; this._onChange( From cb1d54f719a9b85aa25b40141207ecf84e99afcf Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 00:56:48 +0300 Subject: [PATCH 044/108] fix cancelling empty This reverts commit fceb050e88214684d2c528ea73ea90d84f8d0ada. --- src/util/animation/AnimationRegistry.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/util/animation/AnimationRegistry.ts b/src/util/animation/AnimationRegistry.ts index 25d5730ab75..968950e359d 100644 --- a/src/util/animation/AnimationRegistry.ts +++ b/src/util/animation/AnimationRegistry.ts @@ -24,6 +24,9 @@ class AnimationRegistry> extends Array { * cancel all running animations attached to canvas at the next requestAnimFrame */ cancelByCanvas(canvas: Canvas) { + if (!canvas) { + return []; + } const animations = this.filter( (animation) => typeof animation.target === 'object' && @@ -37,6 +40,9 @@ class AnimationRegistry> extends Array { * cancel all running animations for target at the next requestAnimFrame */ cancelByTarget(target: T['target']) { + if (!target) { + return []; + } const animations = this.filter((animation) => animation.target === target); animations.forEach((animation) => animation.abort()); return animations; From fc0217de242d71722ca0d485dbb8e4371d7a9167 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 01:55:03 +0300 Subject: [PATCH 045/108] fix: timestamp , abort before start --- src/util/animation/AnimationBase.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index decb131fcf9..a6bc148d3fa 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -79,11 +79,12 @@ export abstract class AnimationBase { }; start() { - const firstTick: FrameRequestCallback = (timestamp = +new Date()) => { - this.startTime = timestamp; + const firstTick: FrameRequestCallback = (timestamp) => { + if (this.state !== 'pending') return; + this.startTime = timestamp || +new Date(); this.state = 'running'; this._onStart(); - this.tick(timestamp); + this.tick(this.startTime); }; this.register(); @@ -97,8 +98,8 @@ export abstract class AnimationBase { } } - private tick: FrameRequestCallback = (t: number = +new Date()) => { - const durationMs = t - this.startTime; + private tick: FrameRequestCallback = (t) => { + const durationMs = (t || +new Date()) - this.startTime; const boundDurationMs = Math.min(durationMs, this.duration); this.durationRate = boundDurationMs / this.duration; const { value, changeRate } = this.calculate(boundDurationMs); From c0222541abf3aab280e2cded883dccb12572bf66 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 02:04:29 +0300 Subject: [PATCH 046/108] fix tests --- test/unit/animation.js | 104 ++++++++++++++++++------------------- test/unit/canvas.js | 2 +- test/unit/canvas_static.js | 2 +- test/unit/object.js | 9 ++-- 4 files changed, 58 insertions(+), 59 deletions(-) diff --git a/test/unit/animation.js b/test/unit/animation.js index 2e1b957c886..9d18f967140 100644 --- a/test/unit/animation.js +++ b/test/unit/animation.js @@ -1,5 +1,8 @@ -(function() { - QUnit.module('fabric.util.animate', { +(function () { + + const findAnimationsByTarget = target => fabric.runningAnimations.filter(({ target: t }) => target === t); + + QUnit.module('animate', { afterEach: function (assert) { assert.equal(fabric.runningAnimations.length, 0, 'runningAnimations should be empty at the end of a test'); fabric.runningAnimations.cancelAll(); @@ -50,10 +53,14 @@ QUnit.test('animation context', function (assert) { var done = assert.async(); var options = { foo: 'bar' }; - fabric.util.animate(options); + const context = fabric.util.animate(options); + assert.equal(context.state, 'pending', 'state'); + assert.ok(typeof context.abort === 'function', 'context'); + assert.equal(context.duration, 500, 'defaults'); assert.propEqual(options, { foo: 'bar' }, 'options were mutated'); - setTimeout(function() { - assert.equal(fabric.runningAnimations.length, 0, 'animation should exist in registry'); + setTimeout(function () { + assert.equal(context.state, 'completed', 'state'); + assert.equal(fabric.runningAnimations.length, 0, 'animation should not exist in registry'); done(); }, 1000); }); @@ -63,40 +70,31 @@ assert.ok(fabric.runningAnimations instanceof Array); assert.ok(typeof fabric.runningAnimations.cancelAll === 'function'); assert.ok(typeof fabric.runningAnimations.cancelByTarget === 'function'); - assert.ok(typeof fabric.runningAnimations.findAnimationIndex === 'function'); - assert.ok(typeof fabric.runningAnimations.findAnimation === 'function'); - assert.ok(typeof fabric.runningAnimations.findAnimationsByTarget === 'function'); + assert.ok(typeof fabric.runningAnimations.cancelByCanvas === 'function'); assert.equal(fabric.runningAnimations.length, 0, 'should have registered animation'); - var abort, target = { foo: 'bar' }; + var context, target = { foo: 'bar' }; var options = { target, - onChange(currentValue, completionRate, durationRate) { - var context = fabric.runningAnimations.findAnimation(abort); - assert.equal(context.currentValue, currentValue, 'context.currentValue is wrong'); - assert.equal(context.completionRate, completionRate, 'context.completionRate is wrong'); - assert.equal(context.durationRate, durationRate, 'context.durationRate is wrong'); - assert.equal(fabric.runningAnimations.findAnimationIndex(abort), 0, 'animation should exist in registry'); + onChange() { + assert.equal(context.state, 'running', 'state'); + assert.equal(fabric.runningAnimations.indexOf(context), 0, 'animation should exist in registry'); }, onComplete() { setTimeout(() => { + assert.equal(context.state, 'completed', 'state'); assert.equal(fabric.runningAnimations.length, 0, 'should have unregistered animation'); done(); }, 0); } }; - abort = fabric.util.animate(options); - var context = fabric.runningAnimations.findAnimation(abort); + context = fabric.util.animate(options); assert.equal(fabric.runningAnimations.length, 1, 'should have registered animation'); - assert.equal(fabric.runningAnimations.findAnimationIndex(abort), 0, 'animation should exist in registry'); - assert.equal(context.cancel, abort, 'animation should exist in registry'); - assert.equal(context.currentValue, 0, 'context.currentValue is wrong'); - assert.equal(context.completionRate, 0, 'context.completionRate is wrong'); - assert.equal(context.durationRate, 0, 'context.durationRate is wrong'); - var byTarget = fabric.runningAnimations.findAnimationsByTarget(target); + assert.equal(fabric.runningAnimations.indexOf(context), 0, 'animation should exist in registry'); + var byTarget = findAnimationsByTarget(target); assert.equal(byTarget.length, 1, 'should have found registered animation by target'); assert.deepEqual(byTarget[0], context, 'should have found registered animation by target'); delete byTarget[0].target; - assert.equal(fabric.runningAnimations.findAnimationsByTarget(target), 0, 'should not have found registered animation by target'); + assert.equal(findAnimationsByTarget(target), 0, 'should not have found registered animation by target'); }); QUnit.test('fabric.runningAnimations with abort', function (assert) { @@ -115,25 +113,24 @@ done(); }, 0); } - assert.equal(fabric.runningAnimations.findAnimationIndex(abort), 0, 'animation should exist in registry'); + assert.equal(fabric.runningAnimations.indexOf(context), 0, 'animation should exist in registry'); return _abort; } }; - var abort = fabric.util.animate(options); + var context = fabric.util.animate(options); assert.equal(fabric.runningAnimations.length, 1, 'should have registered animation'); - assert.equal(fabric.runningAnimations.findAnimationIndex(abort), 0, 'animation should exist in registry'); - assert.equal(fabric.runningAnimations.findAnimation(abort).cancel, abort, 'animation should exist in registry'); + assert.equal(fabric.runningAnimations.indexOf(context), 0, 'animation should exist in registry'); }); QUnit.test('fabric.runningAnimations with imperative abort', function (assert) { var options = { foo: 'bar' }; - var abort = fabric.util.animate(options); + var context = fabric.util.animate(options); + assert.equal(context.state, 'pending', 'state'); assert.equal(fabric.runningAnimations.length, 1, 'should have registered animation'); - assert.equal(fabric.runningAnimations.findAnimationIndex(abort), 0, 'animation should exist in registry'); - assert.equal(fabric.runningAnimations.findAnimation(abort).cancel, abort, 'animation should exist in registry'); - var context = abort(); + assert.equal(fabric.runningAnimations.indexOf(context), 0, 'animation should exist in registry'); + context.abort(); + assert.equal(context.state, 'aborted', 'state'); assert.equal(fabric.runningAnimations.length, 0, 'should have unregistered animation'); - assert.equal(context.foo, 'bar', 'should return animation context'); }); QUnit.test('fabric.runningAnimations cancelAll', function (assert) { @@ -149,8 +146,6 @@ // make sure splice didn't destroy instance assert.ok(fabric.runningAnimations instanceof Array); assert.ok(typeof fabric.runningAnimations.cancelAll === 'function'); - assert.ok(typeof fabric.runningAnimations.findAnimationIndex === 'function'); - assert.ok(typeof fabric.runningAnimations.findAnimation === 'function'); }); QUnit.test('fabric.runningAnimations cancelByCanvas', function (assert) { @@ -179,7 +174,7 @@ fabric.util.animate(options); fabric.util.animate(options); fabric.util.animate(options); - fabric.util.animate(opt2); + const baz = fabric.util.animate(opt2); assert.equal(fabric.runningAnimations.length, 4, 'should have registered animations'); var cancelledAnimations = fabric.runningAnimations.cancelByTarget(); assert.equal(cancelledAnimations.length, 0, 'should return empty array'); @@ -187,7 +182,7 @@ cancelledAnimations = fabric.runningAnimations.cancelByTarget('pip'); assert.equal(cancelledAnimations.length, 3, 'should return cancelled animations'); assert.equal(fabric.runningAnimations.length, 1, 'should have left 1 registered animation'); - assert.equal(fabric.runningAnimations[0].bar, opt2.bar, 'should have left 1 registered animation'); + assert.strictEqual(fabric.runningAnimations[0], baz, 'should have left 1 registered animation'); setTimeout(() => { done(); }, 1000); @@ -248,7 +243,7 @@ object.animate(prop, 'blue'); assert.ok(true, 'animate without options does not crash'); assert.equal(fabric.runningAnimations.length, index + 1, 'should have 1 registered animation'); - assert.equal(fabric.runningAnimations.findAnimationsByTarget(object).length, index + 1, 'animation.target should be set'); + assert.equal(findAnimationsByTarget(object).length, index + 1, 'animation.target should be set'); setTimeout(function () { assert.equal(object[prop], new fabric.Color('blue').toRgba(), 'property [' + prop + '] has been animated'); @@ -283,7 +278,7 @@ object.animate({ left: 40}); assert.ok(true, 'animate without options does not crash'); assert.equal(fabric.runningAnimations.length, 1, 'should have 1 registered animation'); - assert.equal(fabric.runningAnimations.findAnimationsByTarget(object).length, 1, 'animation.target should be set'); + assert.equal(findAnimationsByTarget(object).length, 1, 'animation.target should be set'); setTimeout(function() { assert.equal(40, Math.round(object.left)); @@ -343,7 +338,9 @@ duration: 96, onChange: function(currentValue) { assert.equal(fabric.runningAnimations.length, 1, 'runningAnimations should not be empty'); - assert.deepEqual(fabric.runningAnimations[0]['currentValue'], currentValue) + assert.ok(Array.isArray(currentValue), 'should be array'); + assert.ok(fabric.runningAnimations[0].value !== currentValue, 'should not share array'); + assert.deepEqual(fabric.runningAnimations[0].value, currentValue); assert.equal(currentValue.length, 3); currentValue.forEach(function(v) { assert.ok(v > 0, 'confirm values are not invalid numbers'); @@ -366,14 +363,14 @@ var done = assert.async(); var object = new fabric.Object({ left: 123, top: 124 }); - var context; + var context; object.animate({ left: 223, top: 224 }, { abort: function() { context = this; return true; } }); - + setTimeout(function() { assert.equal(123, Math.round(object.get('left'))); assert.equal(124, Math.round(object.get('top'))); @@ -386,20 +383,22 @@ var done = assert.async(); var object = new fabric.Object({ left: 123, top: 124 }); - var context; - var abort = object._animate('left', 223, { + let called = 0; + const context = object._animate('left', 223, { abort: function () { - context = this; + called++; return false; } }); - assert.ok(typeof abort === 'function'); - abort(); + assert.ok(typeof context.abort === 'function'); + assert.equal(context.state, 'pending', 'state'); + context.abort(); + assert.equal(context.state, 'aborted', 'state'); setTimeout(function () { - assert.equal(123, Math.round(object.get('left'))); - assert.equal(context, undefined, 'declarative abort should not be called after imperative abort was called'); + assert.equal(Math.round(object.get('left')), 123); + assert.equal(called, 0, 'declarative abort should be called once before imperative abort cancels the run'); done(); }, 100); }); @@ -407,18 +406,17 @@ QUnit.test('animate with delay', function (assert) { var done = assert.async(); var object = new fabric.Object({ left: 123, top: 124 }); - var started = false; var t = new Date(); - object._animate('left', 223, { + const context = object._animate('left', 223, { onStart: function () { - started = true; + assert.equal(context.state, 'running', 'state'); assert.gte(new Date() - t, 500, 'animation delay'); return false; }, onComplete: done, delay: 500 }); - assert.ok(started === false); + assert.equal(context.state, 'pending', 'state'); }); QUnit.test('animate easing easeInQuad', function(assert) { diff --git a/test/unit/canvas.js b/test/unit/canvas.js index 406b0347819..80d72f182ad 100644 --- a/test/unit/canvas.js +++ b/test/unit/canvas.js @@ -2340,7 +2340,7 @@ } assert.equal(canvas.item(0), rect); - assert.ok(typeof canvas.fxRemove(rect, { onComplete: onComplete }) === 'function', 'should return animation abort function'); + assert.ok(typeof canvas.fxRemove(rect, { onComplete: onComplete }).abort === 'function', 'should return animation abort function'); setTimeout(function() { assert.equal(canvas.item(0), undefined); diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js index dfa9143eafd..8bbccf635b1 100644 --- a/test/unit/canvas_static.js +++ b/test/unit/canvas_static.js @@ -1728,7 +1728,7 @@ } assert.ok(canvas.item(0) === rect); - assert.ok(typeof canvas.fxRemove(rect, { onComplete: onComplete }) === 'function', 'should return animation abort function'); + assert.ok(typeof canvas.fxRemove(rect, { onComplete: onComplete }).abort === 'function', 'should return animation abort function'); }); QUnit.test('setViewportTransform', function(assert) { diff --git a/test/unit/object.js b/test/unit/object.js index 1f88774f4af..24edab75639 100644 --- a/test/unit/object.js +++ b/test/unit/object.js @@ -570,13 +570,13 @@ var callbacks = { onComplete: onComplete, onChange: onChange }; assert.ok(typeof object.fxStraighten === 'function'); - assert.ok(typeof object.fxStraighten(callbacks) === 'function', 'should return animation abort function'); + assert.ok(typeof object.fxStraighten(callbacks).abort === 'function', 'should return animation context'); assert.equal(fabric.util.toFixed(object.get('angle'), 0), 43); setTimeout(function(){ assert.ok(onCompleteFired); assert.ok(onChangeFired); assert.equal(object.get('angle'), 0, 'angle should be set to 0 by the end of animation'); - assert.ok(typeof object.fxStraighten() === 'function', 'should work without callbacks'); + assert.ok(typeof object.fxStraighten().abort === 'function', 'should work without callbacks'); done(); }, 1000); }); @@ -1503,9 +1503,10 @@ var object = new fabric.Object({ fill: 'blue', width: 100, height: 100 }); assert.ok(typeof object.dispose === 'function'); object.animate('fill', 'red'); - assert.equal(fabric.runningAnimations.findAnimationsByTarget(object).length, 1, 'runningAnimations should include the animation'); + const findAnimationsByTarget = target => fabric.runningAnimations.filter(({ target: t }) => target === t); + assert.equal(findAnimationsByTarget(object).length, 1, 'runningAnimations should include the animation'); object.dispose(); - assert.equal(fabric.runningAnimations.findAnimationsByTarget(object).length, 0, 'runningAnimations should be empty after dispose'); + assert.equal(findAnimationsByTarget(object).length, 0, 'runningAnimations should be empty after dispose'); }); QUnit.test('prototype changes', function (assert) { var object = new fabric.Object(); From fd763b7a885c4111bf5ecd7b069f166ab031d9d7 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 02:12:53 +0300 Subject: [PATCH 047/108] dep(): `colorEasing` handled by `easing` --- src/util/animation/ColorAnimation.ts | 31 +++++++--------------------- src/util/animation/types.ts | 15 +------------- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index 49be55ada18..de1eeac395c 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -2,29 +2,22 @@ import { Color } from '../../color'; import { TColorAlphaSource } from '../../color/color.class'; import { noop } from '../../constants'; import { AnimationBase } from './AnimationBase'; -import { - ColorAnimationOptions, - TColorEasingRateFunction, - TOnAnimationChangeCallback, -} from './types'; +import { ColorAnimationOptions, TOnAnimationChangeCallback } from './types'; const wrapColorCallback = (callback: TOnAnimationChangeCallback) => (rgba: TColorAlphaSource, valueRate: number, durationRate: number) => callback(new Color(rgba).toRgba(), valueRate, durationRate); -const defaultColorEasingRate: TColorEasingRateFunction = ( - currentTime, - duration -) => 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); - export class ColorAnimation extends AnimationBase { - readonly colorEasing: TColorEasingRateFunction; constructor({ startValue, endValue, - easing = (currentTime, startValue, byValue) => startValue + byValue, - colorEasing = defaultColorEasingRate, + easing = (currentTime, startValue, byValue, duration) => { + const durationRate = + 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); + return startValue + byValue * durationRate; + }, onChange = noop, onComplete = noop, abort = noop, @@ -44,22 +37,14 @@ export class ColorAnimation extends AnimationBase { onComplete: wrapColorCallback(onComplete), abort: wrapColorCallback(abort), }); - this.colorEasing = colorEasing; } protected calculate(currentTime: number) { - const changeRate = this.colorEasing(currentTime, this.duration); const rgba = this.startValue.map((value, i) => - this.easing( - currentTime, - value, - changeRate * this.byValue[i], - this.duration, - i - ) + this.easing(currentTime, value, this.byValue[i], this.duration, i) ) as TColorAlphaSource; return { value: rgba, - changeRate, + changeRate: Math.abs((rgba[0] - this.startValue[0]) / this.byValue[0]), }; } } diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 9bf92cd0d0d..cb32ae7cf48 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -41,17 +41,6 @@ export type TEasingFunction = T extends any[] duration: number ) => number; -/** - * A color easing function - * @param currentTime ms elapsed - * @param duration in ms - * @returns durationRate ∈ [0, 1] - */ -export type TColorEasingRateFunction = ( - currentTime: number, - duration: number -) => number; - export type TAnimationBaseOptions = { /** * Duration of the animation in ms @@ -132,6 +121,4 @@ export type ColorAnimationOptions = TAnimationOptions< TColorArg, string, number[] -> & { - colorEasing?: TColorEasingRateFunction; -}; +>; From 139ee11f8e73a372c1f3a1289e487941aa5af4d2 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 02:19:38 +0300 Subject: [PATCH 048/108] BREAKING change `animateColor` signature --- CHANGELOG.md | 4 +++- src/mixins/animation.mixin.ts | 7 +------ src/util/animation/animate.ts | 15 ++------------- test/unit/animation.js | 7 +++++-- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18470b0f90c..553afbc14b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ ## [next] - refactor(TS): `animate` and `AnimationRegistry` to classes [#8297](https://github.com/fabricjs/fabric.js/pull/8297) - BREAKING: return animation instance from animate instead of a cancel function and remove `findAnimationByXXX` from `AnimationRegistry` + BREAKING: + - return animation instance from animate instead of a cancel function and remove `findAnimationByXXX` from `AnimationRegistry` + - change `animateColor` signature to match `animate`, removed `colorEasing` - chore(TS): convert object to es6 class [#8322](https://github.com/fabricjs/fabric.js/pull/8322) - docs(): guides follow up, feature request template [#8379](https://github.com/fabricjs/fabric.js/pull/8379) - docs(): refactor guides, bug report template [#8189](https://github.com/fabricjs/fabric.js/pull/8189) diff --git a/src/mixins/animation.mixin.ts b/src/mixins/animation.mixin.ts index ad8cec99a82..d6733a201ad 100644 --- a/src/mixins/animation.mixin.ts +++ b/src/mixins/animation.mixin.ts @@ -243,12 +243,7 @@ import { FabricObject } from '../shapes/object.class'; }; if (propIsColor) { - return fabric.util.animateColor( - _options.startValue, - _options.endValue, - _options.duration, - _options - ); + return fabric.util.animateColor(_options); } else { return fabric.util.animate(_options); } diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index d52501e200e..ebed78514e5 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -1,4 +1,3 @@ -import { TColorArg } from '../../color/color.class'; import { Animation } from './Animation'; import { ArrayAnimation } from './ArrayAnimation'; import { ColorAnimation } from './ColorAnimation'; @@ -52,18 +51,8 @@ export const animate = (options: AnimationOptions | ArrayAnimationOptions) => { return animation; }; -export const animateColor = ( - startValue: TColorArg, - endValue: TColorArg, - duration?: number, - options?: ColorAnimationOptions -) => { - const animation = new ColorAnimation({ - ...options, - startValue, - endValue, - duration, - }); +export const animateColor = (options: ColorAnimationOptions) => { + const animation = new ColorAnimation(options); animation.start(); return animation; }; diff --git a/test/unit/animation.js b/test/unit/animation.js index 9d18f967140..1356e466fcf 100644 --- a/test/unit/animation.js +++ b/test/unit/animation.js @@ -21,8 +21,11 @@ assert.ok(typeof val === 'string', 'expected type is String'); } assert.ok(typeof fabric.util.animateColor === 'function', 'animateColor is a function'); - fabric.util.animateColor('red', 'blue', 16, { - onComplete: function(val, changePerc, timePerc) { + fabric.util.animateColor({ + startValue: 'red', + endValue: 'blue', + duration: 16, + onComplete: function (val, changePerc, timePerc) { // animate color need some fixing assert.equal(val, 'rgba(0,0,255,1)', 'color is blue'); assert.equal(changePerc, 1, 'change percentage is 100%'); From d1c103748f0a90f61fb278d1dd7ca2a520e0842a Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 02:24:25 +0300 Subject: [PATCH 049/108] accept color byValue --- src/util/animation/ColorAnimation.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index de1eeac395c..fbc1c007db7 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -13,6 +13,7 @@ export class ColorAnimation extends AnimationBase { constructor({ startValue, endValue, + byValue, easing = (currentTime, startValue, byValue, duration) => { const durationRate = 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); @@ -29,9 +30,11 @@ export class ColorAnimation extends AnimationBase { ...options, startValue: startColor, endValue: endColor, - byValue: endColor.map( - (value, i) => value - startColor[i] - ) as TColorAlphaSource, + byValue: byValue + ? new Color(byValue).getSource() + : (endColor.map( + (value, i) => value - startColor[i] + ) as TColorAlphaSource), easing, onChange: wrapColorCallback(onChange), onComplete: wrapColorCallback(onComplete), From a7a9d281acb254be36084be99b87e80586b42a58 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 02:50:11 +0300 Subject: [PATCH 050/108] Revert "accept color byValue" This reverts commit d1c103748f0a90f61fb278d1dd7ca2a520e0842a. --- src/util/animation/ColorAnimation.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index fbc1c007db7..de1eeac395c 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -13,7 +13,6 @@ export class ColorAnimation extends AnimationBase { constructor({ startValue, endValue, - byValue, easing = (currentTime, startValue, byValue, duration) => { const durationRate = 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); @@ -30,11 +29,9 @@ export class ColorAnimation extends AnimationBase { ...options, startValue: startColor, endValue: endColor, - byValue: byValue - ? new Color(byValue).getSource() - : (endColor.map( - (value, i) => value - startColor[i] - ) as TColorAlphaSource), + byValue: endColor.map( + (value, i) => value - startColor[i] + ) as TColorAlphaSource, easing, onChange: wrapColorCallback(onChange), onComplete: wrapColorCallback(onComplete), From 41adf12480d6e1ee0540c5a79030e47094694156 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 02:59:31 +0300 Subject: [PATCH 051/108] fix(): `byValue` over `endValue` --- src/util/animation/AnimationBase.ts | 8 ++------ test/unit/animation.js | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index a6bc148d3fa..851a32a2fea 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -112,17 +112,13 @@ export abstract class AnimationBase { this.state = 'aborted'; this.unregister(); } else if (durationMs >= this.duration) { - // TODO this line seems redundant - this.durationRate = this.valueRate = 1; this._onChange( - (Array.isArray(this.endValue) - ? this.endValue.slice() - : this.endValue) as T, + Array.isArray(value) ? (value.slice() as T) : value, this.valueRate, this.durationRate ); this.state = 'completed'; - this._onComplete(this.endValue, this.valueRate, this.durationRate); + this._onComplete(value, this.valueRate, this.durationRate); this.unregister(); } else { this._onChange(value, this.valueRate, this.durationRate); diff --git a/test/unit/animation.js b/test/unit/animation.js index 1356e466fcf..f02f7c0af4e 100644 --- a/test/unit/animation.js +++ b/test/unit/animation.js @@ -191,6 +191,23 @@ }, 1000); }); + QUnit.test('byValue takes precedence over endValue', function (assert) { + const done = assert.async(); + const context = fabric.util.animate({ + startValue: 0, + endValue: 5, + byValue: 10, + duration: 96, + onComplete: function (endValue, v, t) { + assert.equal(endValue, 10); + assert.equal(v, 1); + assert.equal(t, 1); + assert.equal(context.value, 10); + done(); + } + }); + }); + QUnit.test('animate', function(assert) { var done = assert.async(); var object = new fabric.Object({ left: 20, top: 30, width: 40, height: 50, angle: 43 }); From aa32d24c3dc9a0615aa05350e0b8377fbe7249e5 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 03:00:45 +0300 Subject: [PATCH 052/108] block `byValue` on color options --- src/util/animation/types.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index cb32ae7cf48..6fb22061066 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -117,8 +117,7 @@ export type AnimationOptions = TAnimationOptions; export type ArrayAnimationOptions = TAnimationOptions; -export type ColorAnimationOptions = TAnimationOptions< - TColorArg, - string, - number[] +export type ColorAnimationOptions = Omit< + TAnimationOptions, + 'byValue' >; From 470854a28836682e849b8995b977880c9f4d8625 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 03:09:35 +0300 Subject: [PATCH 053/108] Revert "fix(): `byValue` over `endValue`" This reverts commit 41adf12480d6e1ee0540c5a79030e47094694156. --- src/util/animation/AnimationBase.ts | 8 ++++++-- test/unit/animation.js | 17 ----------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index 851a32a2fea..a6bc148d3fa 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -112,13 +112,17 @@ export abstract class AnimationBase { this.state = 'aborted'; this.unregister(); } else if (durationMs >= this.duration) { + // TODO this line seems redundant + this.durationRate = this.valueRate = 1; this._onChange( - Array.isArray(value) ? (value.slice() as T) : value, + (Array.isArray(this.endValue) + ? this.endValue.slice() + : this.endValue) as T, this.valueRate, this.durationRate ); this.state = 'completed'; - this._onComplete(value, this.valueRate, this.durationRate); + this._onComplete(this.endValue, this.valueRate, this.durationRate); this.unregister(); } else { this._onChange(value, this.valueRate, this.durationRate); diff --git a/test/unit/animation.js b/test/unit/animation.js index f02f7c0af4e..1356e466fcf 100644 --- a/test/unit/animation.js +++ b/test/unit/animation.js @@ -191,23 +191,6 @@ }, 1000); }); - QUnit.test('byValue takes precedence over endValue', function (assert) { - const done = assert.async(); - const context = fabric.util.animate({ - startValue: 0, - endValue: 5, - byValue: 10, - duration: 96, - onComplete: function (endValue, v, t) { - assert.equal(endValue, 10); - assert.equal(v, 1); - assert.equal(t, 1); - assert.equal(context.value, 10); - done(); - } - }); - }); - QUnit.test('animate', function(assert) { var done = assert.async(); var object = new fabric.Object({ left: 20, top: 30, width: 40, height: 50, angle: 43 }); From dfb5910ed62f58f578ef9eebbc2762d48ba2504f Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 03:10:46 +0300 Subject: [PATCH 054/108] Update AnimationBase.ts --- src/util/animation/AnimationBase.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index a6bc148d3fa..872c91d16fd 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -112,7 +112,6 @@ export abstract class AnimationBase { this.state = 'aborted'; this.unregister(); } else if (durationMs >= this.duration) { - // TODO this line seems redundant this.durationRate = this.valueRate = 1; this._onChange( (Array.isArray(this.endValue) From 11de652cc4e8093815a42586f9bc0b745b97e61a Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 08:49:15 +0300 Subject: [PATCH 055/108] fix(): color chage rate --- src/util/animation/AnimationBase.ts | 1 + src/util/animation/ColorAnimation.ts | 14 +++++++++++--- test/unit/animation.js | 15 ++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index 872c91d16fd..e9fe2df9ac8 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -112,6 +112,7 @@ export abstract class AnimationBase { this.state = 'aborted'; this.unregister(); } else if (durationMs >= this.duration) { + // eliminate rounding issues this.durationRate = this.valueRate = 1; this._onChange( (Array.isArray(this.endValue) diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index de1eeac395c..d207e3f1fb8 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -39,12 +39,20 @@ export class ColorAnimation extends AnimationBase { }); } protected calculate(currentTime: number) { - const rgba = this.startValue.map((value, i) => + const [r, g, b, a] = this.startValue.map((value, i) => this.easing(currentTime, value, this.byValue[i], this.duration, i) ) as TColorAlphaSource; return { - value: rgba, - changeRate: Math.abs((rgba[0] - this.startValue[0]) / this.byValue[0]), + value: [r, g, b, a] as TColorAlphaSource, + changeRate: + // to correctly calculate the change rate we must find a changed value + [r, g, b] + .map((p, i) => + this.byValue[i] !== 0 + ? Math.abs((p - this.startValue[i]) / this.byValue[i]) + : 0 + ) + .find((p) => p !== 0) || 0, }; } } diff --git a/test/unit/animation.js b/test/unit/animation.js index 1356e466fcf..27f66b47bb2 100644 --- a/test/unit/animation.js +++ b/test/unit/animation.js @@ -26,7 +26,6 @@ endValue: 'blue', duration: 16, onComplete: function (val, changePerc, timePerc) { - // animate color need some fixing assert.equal(val, 'rgba(0,0,255,1)', 'color is blue'); assert.equal(changePerc, 1, 'change percentage is 100%'); assert.equal(timePerc, 1, 'time percentage is 100%'); @@ -36,6 +35,20 @@ }); }); + QUnit.test('animateColor change percentage is calculated from a changed value', function (assert) { + const done = assert.async(); + fabric.util.animateColor({ + startValue: 'red', + endValue: 'magenta', + duration: 96, + onChange: function (val, changePerc, timePerc) { + const durationRate = 1 - Math.cos(timePerc * (Math.PI / 2)); + changePerc !== 1 && assert.equal(changePerc, durationRate, 'change percentage should equal time percentage'); + }, + onComplete: done, + }); + }); + // QUnit.test('fabric.util.animate', function(assert) { // var done = assert.async(); // function testing(val) { From 86d31dbbb045d579e7af98dbfaf1ffd2ee421a09 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 09:08:22 +0300 Subject: [PATCH 056/108] color byValue --- src/util/animation/ColorAnimation.ts | 14 +++++-- src/util/animation/types.ts | 7 ++-- test/unit/animation.js | 63 ++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index d207e3f1fb8..401cf58c533 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -1,6 +1,7 @@ import { Color } from '../../color'; import { TColorAlphaSource } from '../../color/color.class'; import { noop } from '../../constants'; +import { capValue } from '../misc/capValue'; import { AnimationBase } from './AnimationBase'; import { ColorAnimationOptions, TOnAnimationChangeCallback } from './types'; @@ -13,6 +14,7 @@ export class ColorAnimation extends AnimationBase { constructor({ startValue, endValue, + byValue, easing = (currentTime, startValue, byValue, duration) => { const durationRate = 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); @@ -29,9 +31,13 @@ export class ColorAnimation extends AnimationBase { ...options, startValue: startColor, endValue: endColor, - byValue: endColor.map( - (value, i) => value - startColor[i] - ) as TColorAlphaSource, + byValue: byValue + ? new Color(byValue) + .setAlpha(Array.isArray(byValue) && byValue[3] ? byValue[3] : 0) + .getSource() + : (endColor.map( + (value, i) => value - startColor[i] + ) as TColorAlphaSource), easing, onChange: wrapColorCallback(onChange), onComplete: wrapColorCallback(onComplete), @@ -43,7 +49,7 @@ export class ColorAnimation extends AnimationBase { this.easing(currentTime, value, this.byValue[i], this.duration, i) ) as TColorAlphaSource; return { - value: [r, g, b, a] as TColorAlphaSource, + value: [r, g, b, capValue(0, a, 1)] as TColorAlphaSource, changeRate: // to correctly calculate the change rate we must find a changed value [r, g, b] diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 6fb22061066..cb32ae7cf48 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -117,7 +117,8 @@ export type AnimationOptions = TAnimationOptions; export type ArrayAnimationOptions = TAnimationOptions; -export type ColorAnimationOptions = Omit< - TAnimationOptions, - 'byValue' +export type ColorAnimationOptions = TAnimationOptions< + TColorArg, + string, + number[] >; diff --git a/test/unit/animation.js b/test/unit/animation.js index 27f66b47bb2..c8ea02deb55 100644 --- a/test/unit/animation.js +++ b/test/unit/animation.js @@ -49,6 +49,69 @@ }); }); + QUnit.test.only('animateColor byValue', function (assert) { + var done = assert.async(); + fabric.util.animateColor({ + startValue: 'red', + byValue: 'blue', + duration: 16, + onComplete: function (val, changePerc, timePerc) { + assert.equal(val, 'rgba(255,0,255,1)', 'color is magenta'); + assert.equal(changePerc, 1, 'change percentage is 100%'); + assert.equal(timePerc, 1, 'time percentage is 100%'); + done(); + } + }); + }); + + QUnit.test.only('animateColor byValue with ignored opacity', function (assert) { + var done = assert.async(); + fabric.util.animateColor({ + startValue: 'rgba(255,0,0,0.5)', + byValue: 'rgba(0,0,255,0.5)', + duration: 16, + onComplete: function (val, changePerc, timePerc) { + assert.equal(val, 'rgba(255,0,255,1)', 'color is magenta'); + assert.equal(changePerc, 1, 'change percentage is 100%'); + assert.equal(timePerc, 1, 'time percentage is 100%'); + done(); + } + }); + }); + + QUnit.test.only('animateColor byValue with opacity', function (assert) { + var done = assert.async(); + fabric.util.animateColor({ + startValue: 'red', + byValue: [0, 0, 255, -0.5], + duration: 16, + onComplete: function (val, changePerc, timePerc) { + assert.equal(val, 'rgba(255,0,255,0.5)', 'color is magenta'); + assert.equal(changePerc, 1, 'change percentage is 100%'); + assert.equal(timePerc, 1, 'time percentage is 100%'); + done(); + } + }); + }); + + QUnit.test.only('animateColor byValue with wrong opacity is ignored', function (assert) { + var done = assert.async(); + fabric.util.animateColor({ + startValue: 'red', + byValue: [0, 0, 255, 0.5], + duration: 16, + onChange: val => { + assert.equal(new fabric.Color(val).getAlpha(), 1, 'alpha diff should be ignored') + }, + onComplete: function (val, changePerc, timePerc) { + assert.equal(val, 'rgba(255,0,255,1)', 'color is magenta'); + assert.equal(changePerc, 1, 'change percentage is 100%'); + assert.equal(timePerc, 1, 'time percentage is 100%'); + done(); + } + }); + }); + // QUnit.test('fabric.util.animate', function(assert) { // var done = assert.async(); // function testing(val) { From 7ea80e9c1b1cac4684947126bf80fa1dbee2abc1 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 09:20:35 +0300 Subject: [PATCH 057/108] fix(): round color pixels --- src/util/animation/ColorAnimation.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index 401cf58c533..08a273f3c1c 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -48,11 +48,12 @@ export class ColorAnimation extends AnimationBase { const [r, g, b, a] = this.startValue.map((value, i) => this.easing(currentTime, value, this.byValue[i], this.duration, i) ) as TColorAlphaSource; + const rgb = [r, g, b].map(Math.round); return { - value: [r, g, b, capValue(0, a, 1)] as TColorAlphaSource, + value: [...rgb, capValue(0, a, 1)] as TColorAlphaSource, changeRate: // to correctly calculate the change rate we must find a changed value - [r, g, b] + rgb .map((p, i) => this.byValue[i] !== 0 ? Math.abs((p - this.startValue[i]) / this.byValue[i]) From 9156442839fc14e718dda45c24279801f56e048d Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 09:49:32 +0300 Subject: [PATCH 058/108] fix tests --- test/unit/animation.js | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/test/unit/animation.js b/test/unit/animation.js index c8ea02deb55..5878b8f6451 100644 --- a/test/unit/animation.js +++ b/test/unit/animation.js @@ -37,19 +37,20 @@ QUnit.test('animateColor change percentage is calculated from a changed value', function (assert) { const done = assert.async(); + let called = false; fabric.util.animateColor({ startValue: 'red', endValue: 'magenta', duration: 96, - onChange: function (val, changePerc, timePerc) { - const durationRate = 1 - Math.cos(timePerc * (Math.PI / 2)); - changePerc !== 1 && assert.equal(changePerc, durationRate, 'change percentage should equal time percentage'); + onChange: function (val, changePerc) { + called && assert.ok(changePerc !== 0, 'change percentage'); + called = true; }, onComplete: done, }); }); - QUnit.test.only('animateColor byValue', function (assert) { + QUnit.test('animateColor byValue', function (assert) { var done = assert.async(); fabric.util.animateColor({ startValue: 'red', @@ -64,14 +65,14 @@ }); }); - QUnit.test.only('animateColor byValue with ignored opacity', function (assert) { + QUnit.test('animateColor byValue with ignored opacity', function (assert) { var done = assert.async(); fabric.util.animateColor({ startValue: 'rgba(255,0,0,0.5)', byValue: 'rgba(0,0,255,0.5)', duration: 16, onComplete: function (val, changePerc, timePerc) { - assert.equal(val, 'rgba(255,0,255,1)', 'color is magenta'); + assert.equal(val, 'rgba(255,0,255,0.5)', 'color is magenta'); assert.equal(changePerc, 1, 'change percentage is 100%'); assert.equal(timePerc, 1, 'time percentage is 100%'); done(); @@ -79,7 +80,7 @@ }); }); - QUnit.test.only('animateColor byValue with opacity', function (assert) { + QUnit.test('animateColor byValue with opacity', function (assert) { var done = assert.async(); fabric.util.animateColor({ startValue: 'red', @@ -94,7 +95,7 @@ }); }); - QUnit.test.only('animateColor byValue with wrong opacity is ignored', function (assert) { + QUnit.test('animateColor byValue with wrong opacity is ignored', function (assert) { var done = assert.async(); fabric.util.animateColor({ startValue: 'red', @@ -112,23 +113,6 @@ }); }); - // QUnit.test('fabric.util.animate', function(assert) { - // var done = assert.async(); - // function testing(val) { - // assert.notEqual(val, 'rgba(0,0,255,1)', 'color is not blue'); - // assert.ok(typeof val === 'String'); - // } - // assert.ok(typeof fabric.util.animate === 'function', 'fabric.util.animate is a function'); - // fabric.util.animate('red', 'blue', 16, { - // onComplete: function() { - // // animate color need some fixing - // // assert.equal(val, 'rgba(0,0,255,1)', 'color is blue') - // done(); - // }, - // onChange: testing, - // }); - // }); - QUnit.test('animation context', function (assert) { var done = assert.async(); var options = { foo: 'bar' }; From cf883e27a74f5f1efead6ce497eeab2daf970124 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 09:53:25 +0300 Subject: [PATCH 059/108] fix(): endValue --- src/util/animation/AnimationBase.ts | 10 +++++----- src/util/animation/easing.ts | 22 +++++++++++----------- test/unit/animation.js | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index e9fe2df9ac8..83f5c42847d 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -112,17 +112,17 @@ export abstract class AnimationBase { this.state = 'aborted'; this.unregister(); } else if (durationMs >= this.duration) { - // eliminate rounding issues + // calculate end value in since both byValue and endValue are populated with defaults if missing + // this means that if both byValue and endValue are passed in options endValue will be ignored + const { value: endValue } = this.calculate(this.duration); this.durationRate = this.valueRate = 1; this._onChange( - (Array.isArray(this.endValue) - ? this.endValue.slice() - : this.endValue) as T, + (Array.isArray(endValue) ? endValue.slice() : endValue) as T, this.valueRate, this.durationRate ); this.state = 'completed'; - this._onComplete(this.endValue, this.valueRate, this.durationRate); + this._onComplete(endValue, this.valueRate, this.durationRate); this.unregister(); } else { this._onChange(value, this.valueRate, this.durationRate); diff --git a/src/util/animation/easing.ts b/src/util/animation/easing.ts index 8f314b9500c..ea2b13521c3 100644 --- a/src/util/animation/easing.ts +++ b/src/util/animation/easing.ts @@ -43,11 +43,17 @@ const elastic = ( export const defaultEasing: TEasingFunction = (t, b, c, d) => -c * Math.cos((t / d) * halfPI) + c + b; +/** + * Cubic easing in + */ +export const easeInCubic: TEasingFunction = (t, b, c, d) => + c * (t / d) ** 3 + b; + /** * Cubic easing out */ export const easeOutCubic: TEasingFunction = (t, b, c, d) => - c * ((t /= d - 1) * t ** 2 + 1) + b; + c * ((t / d - 1) ** 3 + 1) + b; /** * Cubic easing in and out @@ -57,7 +63,7 @@ export const easeInOutCubic: TEasingFunction = (t, b, c, d) => { if (t < 1) { return (c / 2) * t ** 3 + b; } - return (c / 2) * ((t -= 2) * t ** 2 + 2) + b; + return (c / 2) * ((t - 2) ** 3 + 2) + b; }; /** @@ -87,13 +93,13 @@ export const easeInOutQuart: TEasingFunction = (t, b, c, d) => { * Quintic easing in */ export const easeInQuint: TEasingFunction = (t, b, c, d) => - c * (t /= d) * t ** 4 + b; + c * (t / d) ** 5 + b; /** * Quintic easing out */ export const easeOutQuint: TEasingFunction = (t, b, c, d) => - c * ((t /= d - 1) * t ** 4 + 1) + b; + c * ((t / d - 1) ** 5 + 1) + b; /** * Quintic easing in and out @@ -103,7 +109,7 @@ export const easeInOutQuint: TEasingFunction = (t, b, c, d) => { if (t < 1) { return (c / 2) * t ** 5 + b; } - return (c / 2) * ((t -= 2) * t ** 4 + 2) + b; + return (c / 2) * ((t - 2) ** 5 + 2) + b; }; /** @@ -326,9 +332,3 @@ export const easeInOutQuad: TEasingFunction = (t, b, c, d) => { } return (-c / 2) * (--t * (t - 2) - 1) + b; }; - -/** - * Cubic easing in - */ -export const easeInCubic: TEasingFunction = (t, b, c, d) => - c * (t /= d) * t * t + b; diff --git a/test/unit/animation.js b/test/unit/animation.js index 5878b8f6451..f980ef483c1 100644 --- a/test/unit/animation.js +++ b/test/unit/animation.js @@ -113,6 +113,22 @@ }); }); + QUnit.test('byValue', function (assert) { + var done = assert.async(); + fabric.util.animate({ + startValue: 0, + byValue: 10, + endValue: 5, + duration: 16, + onComplete: function (val, changePerc, timePerc) { + assert.equal(val, 10, 'endValue is ignored'); + assert.equal(changePerc, 1, 'change percentage is 100%'); + assert.equal(timePerc, 1, 'time percentage is 100%'); + done(); + } + }); + }); + QUnit.test('animation context', function (assert) { var done = assert.async(); var options = { foo: 'bar' }; From 43e76a2cea8f38205db188e256b1ed638987a129 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 09:58:50 +0300 Subject: [PATCH 060/108] comment --- src/util/animation/AnimationBase.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index 83f5c42847d..c9770bae466 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -112,7 +112,8 @@ export abstract class AnimationBase { this.state = 'aborted'; this.unregister(); } else if (durationMs >= this.duration) { - // calculate end value in since both byValue and endValue are populated with defaults if missing + // since both byValue and endValue are populated with defaults if missing, + // we must calculate end value // this means that if both byValue and endValue are passed in options endValue will be ignored const { value: endValue } = this.calculate(this.duration); this.durationRate = this.valueRate = 1; From 1b636223b67c3df233b761f4aa903b91a6057127 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 10:00:43 +0300 Subject: [PATCH 061/108] fix(): immutable state --- src/util/animation/AnimationBase.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index c9770bae466..ff83634c9e9 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -27,7 +27,7 @@ export abstract class AnimationBase { */ readonly target?: unknown; - state: 'pending' | 'running' | 'completed' | 'aborted' = 'pending'; + private _state: 'pending' | 'running' | 'completed' | 'aborted' = 'pending'; /** * time % */ @@ -73,6 +73,10 @@ export abstract class AnimationBase { this.target = target; } + get state() { + return this._state; + } + protected abstract calculate(currentTime: number): { value: T; changeRate: number; @@ -80,9 +84,9 @@ export abstract class AnimationBase { start() { const firstTick: FrameRequestCallback = (timestamp) => { - if (this.state !== 'pending') return; + if (this._state !== 'pending') return; this.startTime = timestamp || +new Date(); - this.state = 'running'; + this._state = 'running'; this._onStart(); this.tick(this.startTime); }; @@ -106,10 +110,10 @@ export abstract class AnimationBase { this.value = Array.isArray(value) ? (value.slice() as T) : value; this.valueRate = changeRate; - if (this.state === 'aborted') { + if (this._state === 'aborted') { return; } else if (this._abort(value, this.valueRate, this.durationRate)) { - this.state = 'aborted'; + this._state = 'aborted'; this.unregister(); } else if (durationMs >= this.duration) { // since both byValue and endValue are populated with defaults if missing, @@ -122,7 +126,7 @@ export abstract class AnimationBase { this.valueRate, this.durationRate ); - this.state = 'completed'; + this._state = 'completed'; this._onComplete(endValue, this.valueRate, this.durationRate); this.unregister(); } else { @@ -140,7 +144,7 @@ export abstract class AnimationBase { } abort() { - this.state = 'aborted'; + this._state = 'aborted'; this.unregister(); } } From 127d7a202871646415646b8349b9d7ae4e8835f0 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 10:10:38 +0300 Subject: [PATCH 062/108] cleanup endValue in class --- src/util/animation/Animation.ts | 1 - src/util/animation/AnimationBase.ts | 18 ++++++++++-------- src/util/animation/ArrayAnimation.ts | 1 - src/util/animation/ColorAnimation.ts | 1 - 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/util/animation/Animation.ts b/src/util/animation/Animation.ts index 511ced97d5b..9925e96fd70 100644 --- a/src/util/animation/Animation.ts +++ b/src/util/animation/Animation.ts @@ -11,7 +11,6 @@ export class Animation extends AnimationBase { super({ ...options, startValue, - endValue, byValue, }); } diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index ff83634c9e9..3e352367535 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -13,7 +13,6 @@ import { export abstract class AnimationBase { readonly startValue: T; - readonly endValue: T; readonly byValue: T; readonly duration: number; readonly delay: number; @@ -45,9 +44,12 @@ export abstract class AnimationBase { */ private startTime!: number; + /** + * since both `byValue` and `endValue` are accepted in subclass options and are populated with defaults if missing, + * we defer to `byValue` and ignore `endValue` to avoid conflict + */ constructor({ startValue, - endValue, byValue, duration = 500, delay = 0, @@ -58,9 +60,8 @@ export abstract class AnimationBase { abort = noop, target, }: Partial & TAnimationCallbacks> & - TAnimationValues) { + Omit, 'endValue'>) { this.startValue = startValue; - this.endValue = endValue; this.byValue = byValue; this.duration = duration; this.delay = delay; @@ -77,6 +78,10 @@ export abstract class AnimationBase { return this._state; } + get endValue() { + return this.calculate(this.duration).value; + } + protected abstract calculate(currentTime: number): { value: T; changeRate: number; @@ -116,10 +121,7 @@ export abstract class AnimationBase { this._state = 'aborted'; this.unregister(); } else if (durationMs >= this.duration) { - // since both byValue and endValue are populated with defaults if missing, - // we must calculate end value - // this means that if both byValue and endValue are passed in options endValue will be ignored - const { value: endValue } = this.calculate(this.duration); + const endValue = this.endValue; this.durationRate = this.valueRate = 1; this._onChange( (Array.isArray(endValue) ? endValue.slice() : endValue) as T, diff --git a/src/util/animation/ArrayAnimation.ts b/src/util/animation/ArrayAnimation.ts index 76bc5125c81..a2293c65749 100644 --- a/src/util/animation/ArrayAnimation.ts +++ b/src/util/animation/ArrayAnimation.ts @@ -11,7 +11,6 @@ export class ArrayAnimation extends AnimationBase { super({ ...options, startValue, - endValue, byValue, }); } diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index 08a273f3c1c..dc4f0154a9d 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -30,7 +30,6 @@ export class ColorAnimation extends AnimationBase { super({ ...options, startValue: startColor, - endValue: endColor, byValue: byValue ? new Color(byValue) .setAlpha(Array.isArray(byValue) && byValue[3] ? byValue[3] : 0) From d9609d2851d9df6439fd9ed43a5426e1875e6b42 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 10:12:04 +0300 Subject: [PATCH 063/108] Update types.ts --- src/util/animation/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index cb32ae7cf48..e329050b843 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -98,12 +98,14 @@ export type TAnimationValues = { /** * Ending value(s) + * Ignored if `byValue` exists * @default 100 */ endValue: T; /** * Difference between the start value(s) to the end value(s) + * Overrides `endValue` * @default [endValue - startValue] */ byValue: T; From abce738d350c13aad9c646ae684c42994a0de7cd Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 10:17:56 +0300 Subject: [PATCH 064/108] fix(): byValue endValue type https://stackoverflow.com/a/44425486/9068029 --- src/util/animation/types.ts | 48 +++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index e329050b843..e4884244bf6 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -89,27 +89,33 @@ export type TAnimationCallbacks = { abort: TAbortCallback; }; -export type TAnimationValues = { - /** - * Starting value(s) - * @default 0 - */ - startValue: T; - - /** - * Ending value(s) - * Ignored if `byValue` exists - * @default 100 - */ - endValue: T; - - /** - * Difference between the start value(s) to the end value(s) - * Overrides `endValue` - * @default [endValue - startValue] - */ - byValue: T; -}; +export type TAnimationValues = + | { + /** + * Starting value(s) + * @default 0 + */ + startValue: T; + } & ( + | { + /** + * Ending value(s) + * Ignored if `byValue` exists + * @default 100 + */ + endValue: T; + byValue?: never; + } + | { + /** + * Difference between the start value(s) to the end value(s) + * Overrides `endValue` + * @default [endValue - startValue] + */ + byValue: T; + endValue?: never; + } + ); export type TAnimationOptions = Partial< TAnimationBaseOptions & TAnimationValues & TAnimationCallbacks From 20778ecf3538b59fc26ec7e13c3e6bf7d187b16e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 10:22:37 +0300 Subject: [PATCH 065/108] fix type --- src/util/animation/AnimationBase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index 3e352367535..e06f532d248 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -60,7 +60,7 @@ export abstract class AnimationBase { abort = noop, target, }: Partial & TAnimationCallbacks> & - Omit, 'endValue'>) { + Required, 'endValue'>>) { this.startValue = startValue; this.byValue = byValue; this.duration = duration; From abfb814d44b1bbfb17c82d86c5bb7ff84c91f856 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 23 Oct 2022 16:35:33 +0300 Subject: [PATCH 066/108] extract AnimationState --- src/util/animation/AnimationBase.ts | 3 ++- src/util/animation/types.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index e06f532d248..9afb450c5e8 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -9,6 +9,7 @@ import { TEasingFunction, TOnAnimationChangeCallback, TAnimationCallbacks, + AnimationState, } from './types'; export abstract class AnimationBase { @@ -26,7 +27,7 @@ export abstract class AnimationBase { */ readonly target?: unknown; - private _state: 'pending' | 'running' | 'completed' | 'aborted' = 'pending'; + private _state: AnimationState = 'pending'; /** * time % */ diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index e4884244bf6..297e9b30575 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -1,5 +1,7 @@ import { TColorArg } from '../../color/color.class'; +export type AnimationState = 'pending' | 'running' | 'completed' | 'aborted'; + /** * Callback called every frame * @param {number | number[]} value current value of the animation. From 7bf61db3838bda99beecb37d0567a05e2ee43113 Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sun, 23 Oct 2022 16:33:31 -0500 Subject: [PATCH 067/108] change naming from rate to ratio --- src/util/animation/AnimationBase.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index 9afb450c5e8..facd82559bd 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -31,11 +31,11 @@ export abstract class AnimationBase { /** * time % */ - durationRate = 0; + durationRatio = 0; /** * value % */ - valueRate = 0; + valueRatio = 0; /** * current value */ @@ -111,29 +111,29 @@ export abstract class AnimationBase { private tick: FrameRequestCallback = (t) => { const durationMs = (t || +new Date()) - this.startTime; const boundDurationMs = Math.min(durationMs, this.duration); - this.durationRate = boundDurationMs / this.duration; + this.durationRatio = boundDurationMs / this.duration; const { value, changeRate } = this.calculate(boundDurationMs); this.value = Array.isArray(value) ? (value.slice() as T) : value; - this.valueRate = changeRate; + this.valueRatio = changeRate; if (this._state === 'aborted') { return; - } else if (this._abort(value, this.valueRate, this.durationRate)) { + } else if (this._abort(value, this.valueRatio, this.durationRatio)) { this._state = 'aborted'; this.unregister(); } else if (durationMs >= this.duration) { const endValue = this.endValue; - this.durationRate = this.valueRate = 1; + this.durationRatio = this.valueRatio = 1; this._onChange( (Array.isArray(endValue) ? endValue.slice() : endValue) as T, - this.valueRate, - this.durationRate + this.valueRatio, + this.durationRatio ); this._state = 'completed'; - this._onComplete(endValue, this.valueRate, this.durationRate); + this._onComplete(endValue, this.valueRatio, this.durationRatio); this.unregister(); } else { - this._onChange(value, this.valueRate, this.durationRate); + this._onChange(value, this.valueRatio, this.durationRatio); requestAnimFrame(this.tick); } }; From 587e8f810bd387265b0ec56623c715b074b92bdf Mon Sep 17 00:00:00 2001 From: kristpregracke Date: Sun, 23 Oct 2022 16:47:52 -0500 Subject: [PATCH 068/108] stray rate -> ratio --- src/util/animation/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 297e9b30575..966c7cb4e59 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -5,13 +5,13 @@ export type AnimationState = 'pending' | 'running' | 'completed' | 'aborted'; /** * Callback called every frame * @param {number | number[]} value current value of the animation. - * @param valueRate ∈ [0, 1], current value / end value. - * @param durationRate ∈ [0, 1], time passed / duration. + * @param valueRatio ∈ [0, 1], current value / end value. + * @param durationRatio ∈ [0, 1], time passed / duration. */ export type TOnAnimationChangeCallback = ( value: T, - valueRate: number, - durationRate: number + valueRatio: number, + durationRatio: number ) => R; /** From ee985b510c232f569e513f6f8239eadb24ed22ea Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 07:19:56 +0300 Subject: [PATCH 069/108] rate => ratio --- src/util/animation/Animation.ts | 2 +- src/util/animation/AnimationBase.ts | 6 +++--- src/util/animation/ArrayAnimation.ts | 2 +- src/util/animation/ColorAnimation.ts | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/util/animation/Animation.ts b/src/util/animation/Animation.ts index 9925e96fd70..cb499cf6ed6 100644 --- a/src/util/animation/Animation.ts +++ b/src/util/animation/Animation.ts @@ -23,7 +23,7 @@ export class Animation extends AnimationBase { ); return { value, - changeRate: Math.abs((value - this.startValue) / this.byValue), + changeRatio: Math.abs((value - this.startValue) / this.byValue), }; } } diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index facd82559bd..d98b9b1807d 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -85,7 +85,7 @@ export abstract class AnimationBase { protected abstract calculate(currentTime: number): { value: T; - changeRate: number; + changeRatio: number; }; start() { @@ -112,9 +112,9 @@ export abstract class AnimationBase { const durationMs = (t || +new Date()) - this.startTime; const boundDurationMs = Math.min(durationMs, this.duration); this.durationRatio = boundDurationMs / this.duration; - const { value, changeRate } = this.calculate(boundDurationMs); + const { value, changeRatio } = this.calculate(boundDurationMs); this.value = Array.isArray(value) ? (value.slice() as T) : value; - this.valueRatio = changeRate; + this.valueRatio = changeRatio; if (this._state === 'aborted') { return; diff --git a/src/util/animation/ArrayAnimation.ts b/src/util/animation/ArrayAnimation.ts index a2293c65749..b90ebc82daa 100644 --- a/src/util/animation/ArrayAnimation.ts +++ b/src/util/animation/ArrayAnimation.ts @@ -20,7 +20,7 @@ export class ArrayAnimation extends AnimationBase { ); return { value: values, - changeRate: Math.abs((values[0] - this.startValue[0]) / this.byValue[0]), + changeRatio: Math.abs((values[0] - this.startValue[0]) / this.byValue[0]), }; } } diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index dc4f0154a9d..708d4363b4b 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -7,8 +7,8 @@ import { ColorAnimationOptions, TOnAnimationChangeCallback } from './types'; const wrapColorCallback = (callback: TOnAnimationChangeCallback) => - (rgba: TColorAlphaSource, valueRate: number, durationRate: number) => - callback(new Color(rgba).toRgba(), valueRate, durationRate); + (rgba: TColorAlphaSource, valueRatio: number, durationRatio: number) => + callback(new Color(rgba).toRgba(), valueRatio, durationRatio); export class ColorAnimation extends AnimationBase { constructor({ @@ -16,9 +16,9 @@ export class ColorAnimation extends AnimationBase { endValue, byValue, easing = (currentTime, startValue, byValue, duration) => { - const durationRate = + const durationRatio = 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); - return startValue + byValue * durationRate; + return startValue + byValue * durationRatio; }, onChange = noop, onComplete = noop, @@ -50,8 +50,8 @@ export class ColorAnimation extends AnimationBase { const rgb = [r, g, b].map(Math.round); return { value: [...rgb, capValue(0, a, 1)] as TColorAlphaSource, - changeRate: - // to correctly calculate the change rate we must find a changed value + changeRatio: + // to correctly calculate the change ratio we must find a changed value rgb .map((p, i) => this.byValue[i] !== 0 From ffc83f7871bb2fd15b7e7c332c47d168a25af5e8 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 07:27:52 +0300 Subject: [PATCH 070/108] currentTime => timeElapsed --- src/util/animation/Animation.ts | 4 ++-- src/util/animation/AnimationBase.ts | 2 +- src/util/animation/ArrayAnimation.ts | 4 ++-- src/util/animation/ColorAnimation.ts | 8 ++++---- src/util/animation/types.ts | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/util/animation/Animation.ts b/src/util/animation/Animation.ts index cb499cf6ed6..bd1781cd15b 100644 --- a/src/util/animation/Animation.ts +++ b/src/util/animation/Animation.ts @@ -14,9 +14,9 @@ export class Animation extends AnimationBase { byValue, }); } - protected calculate(currentTime: number) { + protected calculate(timeElapsed: number) { const value = this.easing( - currentTime, + timeElapsed, this.startValue, this.byValue, this.duration diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index d98b9b1807d..86ae506587d 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -83,7 +83,7 @@ export abstract class AnimationBase { return this.calculate(this.duration).value; } - protected abstract calculate(currentTime: number): { + protected abstract calculate(timeElapsed: number): { value: T; changeRatio: number; }; diff --git a/src/util/animation/ArrayAnimation.ts b/src/util/animation/ArrayAnimation.ts index b90ebc82daa..8d519715bb5 100644 --- a/src/util/animation/ArrayAnimation.ts +++ b/src/util/animation/ArrayAnimation.ts @@ -14,9 +14,9 @@ export class ArrayAnimation extends AnimationBase { byValue, }); } - protected calculate(currentTime: number) { + protected calculate(timeElapsed: number) { const values = this.startValue.map((value, i) => - this.easing(currentTime, value, this.byValue[i], this.duration, i) + this.easing(timeElapsed, value, this.byValue[i], this.duration, i) ); return { value: values, diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index 708d4363b4b..dc8157a0d1a 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -15,9 +15,9 @@ export class ColorAnimation extends AnimationBase { startValue, endValue, byValue, - easing = (currentTime, startValue, byValue, duration) => { + easing = (timeElapsed, startValue, byValue, duration) => { const durationRatio = - 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); + 1 - Math.cos((timeElapsed / duration) * (Math.PI / 2)); return startValue + byValue * durationRatio; }, onChange = noop, @@ -43,9 +43,9 @@ export class ColorAnimation extends AnimationBase { abort: wrapColorCallback(abort), }); } - protected calculate(currentTime: number) { + protected calculate(timeElapsed: number) { const [r, g, b, a] = this.startValue.map((value, i) => - this.easing(currentTime, value, this.byValue[i], this.duration, i) + this.easing(timeElapsed, value, this.byValue[i], this.duration, i) ) as TColorAlphaSource; const rgb = [r, g, b].map(Math.round); return { diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 966c7cb4e59..7e9c9b3b136 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -22,7 +22,7 @@ export type TAbortCallback = TOnAnimationChangeCallback; /** * An easing function - * @param currentTime ms elapsed + * @param timeElapsed ms elapsed since start * @param startValue * @param byValue * @param duration in ms @@ -30,14 +30,14 @@ export type TAbortCallback = TOnAnimationChangeCallback; */ export type TEasingFunction = T extends any[] ? ( - currentTime: number, + timeElapsed: number, startValue: number, byValue: number, duration: number, index: number ) => number : ( - currentTime: number, + timeElapsed: number, startValue: number, byValue: number, duration: number From f23970404aa87376e1666c36a7a67c6026c15a7c Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 07:32:06 +0300 Subject: [PATCH 071/108] Update types.ts --- src/util/animation/types.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 7e9c9b3b136..2fcdb391e55 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -28,7 +28,7 @@ export type TAbortCallback = TOnAnimationChangeCallback; * @param duration in ms * @returns next value */ -export type TEasingFunction = T extends any[] +export type TEasingFunction = T extends number[] ? ( timeElapsed: number, startValue: number, @@ -119,8 +119,10 @@ export type TAnimationValues = } ); -export type TAnimationOptions = Partial< - TAnimationBaseOptions & TAnimationValues & TAnimationCallbacks +export type TAnimationOptions = Partial< + TAnimationBaseOptions & + TAnimationValues & + TAnimationCallbacks >; export type AnimationOptions = TAnimationOptions; From 6098d0a2ef3b7b32a83e00c6390f05cebceb8982 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 07:45:05 +0300 Subject: [PATCH 072/108] comment --- src/util/animation/types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 2fcdb391e55..7199099bf3b 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -21,7 +21,9 @@ export type TOnAnimationChangeCallback = ( export type TAbortCallback = TOnAnimationChangeCallback; /** - * An easing function + * An easing function used to calculate the current value + * @see {@link AnimationBase['calculate']} + * * @param timeElapsed ms elapsed since start * @param startValue * @param byValue From c35ad7e5fa538adb49170e76ca899eff8a21fd94 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 07:58:21 +0300 Subject: [PATCH 073/108] types(): no any --- src/util/animation/AnimationBase.ts | 8 +++++--- src/util/animation/AnimationRegistry.ts | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index 86ae506587d..95b829bead1 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -12,7 +12,9 @@ import { AnimationState, } from './types'; -export abstract class AnimationBase { +export abstract class AnimationBase< + T extends number | number[] = number | number[] +> { readonly startValue: T; readonly byValue: T; readonly duration: number; @@ -139,11 +141,11 @@ export abstract class AnimationBase { }; private register() { - runningAnimations.push(this); + runningAnimations.push(this as unknown as AnimationBase); } private unregister() { - runningAnimations.remove(this); + runningAnimations.remove(this as unknown as AnimationBase); } abort() { diff --git a/src/util/animation/AnimationRegistry.ts b/src/util/animation/AnimationRegistry.ts index 968950e359d..782e958db15 100644 --- a/src/util/animation/AnimationRegistry.ts +++ b/src/util/animation/AnimationRegistry.ts @@ -5,8 +5,8 @@ import { AnimationBase } from './AnimationBase'; /** * Array holding all running animations */ -class AnimationRegistry> extends Array { - remove(context: T) { +class AnimationRegistry extends Array { + remove(context: AnimationBase) { const index = this.indexOf(context); index > -1 && this.splice(index, 1); } @@ -39,7 +39,7 @@ class AnimationRegistry> extends Array { /** * cancel all running animations for target at the next requestAnimFrame */ - cancelByTarget(target: T['target']) { + cancelByTarget(target: AnimationBase['target']) { if (!target) { return []; } From fb206799cc6d58abefa6723d0d002cf172d37d4e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 09:29:01 +0300 Subject: [PATCH 074/108] fix return type --- src/util/animation/animate.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index ebed78514e5..0d5334f86a2 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -43,12 +43,18 @@ const isArrayAnimation = ( * }); * */ -export const animate = (options: AnimationOptions | ArrayAnimationOptions) => { - const animation = isArrayAnimation(options) - ? new ArrayAnimation(options) - : new Animation(options); +export const animate = < + T extends AnimationOptions | ArrayAnimationOptions, + R = T extends ArrayAnimationOptions ? ArrayAnimation : Animation +>( + options?: T +): R => { + const opt = options || {}; + const animation = isArrayAnimation(opt) + ? new ArrayAnimation(opt) + : new Animation(opt); animation.start(); - return animation; + return animation as R; }; export const animateColor = (options: ColorAnimationOptions) => { From dee235c79e7fe7f5e111a7e9f694b831597c13ad Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 09:37:25 +0300 Subject: [PATCH 075/108] revert(): no options --- src/util/animation/animate.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 0d5334f86a2..77835bb2cc5 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -47,12 +47,11 @@ export const animate = < T extends AnimationOptions | ArrayAnimationOptions, R = T extends ArrayAnimationOptions ? ArrayAnimation : Animation >( - options?: T + options: T ): R => { - const opt = options || {}; - const animation = isArrayAnimation(opt) - ? new ArrayAnimation(opt) - : new Animation(opt); + const animation = isArrayAnimation(options) + ? new ArrayAnimation(options) + : new Animation(options); animation.start(); return animation as R; }; From 422ec25a506d0812f712336e6086c95a71bf6e82 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 13:07:29 +0300 Subject: [PATCH 076/108] fix ts --- src/util/animation/animate.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 77835bb2cc5..318c2cdabd4 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -45,15 +45,17 @@ const isArrayAnimation = ( */ export const animate = < T extends AnimationOptions | ArrayAnimationOptions, - R = T extends ArrayAnimationOptions ? ArrayAnimation : Animation + R extends T extends ArrayAnimationOptions ? ArrayAnimation : Animation >( options: T ): R => { - const animation = isArrayAnimation(options) - ? new ArrayAnimation(options) - : new Animation(options); + const animation = ( + isArrayAnimation(options) + ? new ArrayAnimation(options) + : new Animation(options) + ) as R; animation.start(); - return animation as R; + return animation; }; export const animateColor = (options: ColorAnimationOptions) => { From a637f81569a319d3f6e56c8e0ab472c90feb9611 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 15:26:24 +0300 Subject: [PATCH 077/108] add ts tests --- test/ts/animation.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/ts/animation.ts diff --git a/test/ts/animation.ts b/test/ts/animation.ts new file mode 100644 index 00000000000..6e4d0fd17ab --- /dev/null +++ b/test/ts/animation.ts @@ -0,0 +1,42 @@ +import { IsExact } from 'conditional-type-checks'; +import { animate } from '../../src/util/animation'; + +function assertStrict(assertTrue: IsExact) { + return assertTrue; +} + +animate({ + endValue: 3, +}); +animate({ + byValue: 2, +}); +// @ts-expect-error only one of (`byValue` | `endValue`) is allowed +animate({ + endValue: 3, + byValue: 2, +}); + +const context = animate({ + startValue: 1, + endValue: 3, + onChange(a, b, c) { + assertStrict(true); + assertStrict(true); + assertStrict(true); + }, +}); + +assertStrict(true); + +const arrayContext = animate({ + startValue: [5], + byValue: [1], + onChange(a, b, c) { + assertStrict(true); + assertStrict(true); + assertStrict(true); + }, +}); + +assertStrict(true); From d6b9b3eedd65137fe8b4ccfc3e53fe238d5e0696 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 21:39:03 +0300 Subject: [PATCH 078/108] remove getter --- src/util/animation/AnimationBase.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index 95b829bead1..d7e23ca2e6c 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -17,6 +17,7 @@ export abstract class AnimationBase< > { readonly startValue: T; readonly byValue: T; + readonly endValue: T; readonly duration: number; readonly delay: number; protected readonly easing: TEasingFunction; @@ -64,8 +65,6 @@ export abstract class AnimationBase< target, }: Partial & TAnimationCallbacks> & Required, 'endValue'>>) { - this.startValue = startValue; - this.byValue = byValue; this.duration = duration; this.delay = delay; this.easing = easing; @@ -73,18 +72,18 @@ export abstract class AnimationBase< this._onChange = onChange; this._onComplete = onComplete; this._abort = abort; - this.value = this.startValue; this.target = target; + + this.startValue = startValue; + this.byValue = byValue; + this.value = this.startValue; + this.endValue = this.calculate(this.duration).value; } get state() { return this._state; } - get endValue() { - return this.calculate(this.duration).value; - } - protected abstract calculate(timeElapsed: number): { value: T; changeRatio: number; From 89e7fe016653353d8f966dc627414ab670fab898 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 24 Oct 2022 22:05:29 +0300 Subject: [PATCH 079/108] Update animation.ts --- test/ts/animation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ts/animation.ts b/test/ts/animation.ts index 6e4d0fd17ab..be5fcd04400 100644 --- a/test/ts/animation.ts +++ b/test/ts/animation.ts @@ -34,8 +34,8 @@ const arrayContext = animate({ byValue: [1], onChange(a, b, c) { assertStrict(true); - assertStrict(true); - assertStrict(true); + assertStrict(true); + assertStrict(true); }, }); From fcccc6940d49b93b3d19b21fd301e71ae70c820a Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 27 Nov 2022 14:42:22 +0200 Subject: [PATCH 080/108] Update AnimationRegistry.ts --- src/util/animation/AnimationRegistry.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/util/animation/AnimationRegistry.ts b/src/util/animation/AnimationRegistry.ts index 782e958db15..b67f4268758 100644 --- a/src/util/animation/AnimationRegistry.ts +++ b/src/util/animation/AnimationRegistry.ts @@ -1,5 +1,6 @@ import { fabric } from '../../../HEADER'; -import { Canvas, TObject } from '../../__types__'; +import type { FabricObject } from '../../shapes/fabricObject.class'; +import { Canvas } from '../../__types__'; import { AnimationBase } from './AnimationBase'; /** @@ -30,7 +31,7 @@ class AnimationRegistry extends Array { const animations = this.filter( (animation) => typeof animation.target === 'object' && - (animation.target as TObject)?.canvas === canvas + (animation.target as FabricObject)?.canvas === canvas ); animations.forEach((animation) => animation.abort()); return animations; From 1e655c5f2c20b09e7d44aef3b5681ea6bc4a03ca Mon Sep 17 00:00:00 2001 From: Shachar <34343793+ShaMan123@users.noreply.github.com> Date: Thu, 1 Dec 2022 07:59:24 +0200 Subject: [PATCH 081/108] fix merge --- src/mixins/animation.mixin.ts | 253 ---------------------------------- 1 file changed, 253 deletions(-) delete mode 100644 src/mixins/animation.mixin.ts diff --git a/src/mixins/animation.mixin.ts b/src/mixins/animation.mixin.ts deleted file mode 100644 index ea736655e85..00000000000 --- a/src/mixins/animation.mixin.ts +++ /dev/null @@ -1,253 +0,0 @@ -//@ts-nocheck -import { FabricObject } from '../shapes/fabricObject.class'; - -(function (global) { - var fabric = global.fabric; - fabric.util.object.extend( - fabric.StaticCanvas.prototype, - /** @lends fabric.StaticCanvas.prototype */ { - /** - * Animation duration (in ms) for fx* methods - * @type Number - * @default - */ - FX_DURATION: 500, - - /** - * Centers object horizontally with animation. - * @param {fabric.Object} object Object to center - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.AnimationContext} context - */ - fxCenterObjectH: function (object, callbacks) { - callbacks = callbacks || {}; - - var empty = function () {}, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - return fabric.util.animate({ - target: this, - startValue: object.getX(), - endValue: this.getCenterPoint().x, - duration: this.FX_DURATION, - onChange: function (value) { - object.setX(value); - _this.requestRenderAll(); - onChange(); - }, - onComplete: function () { - object.setCoords(); - onComplete(); - }, - }); - }, - - /** - * Centers object vertically with animation. - * @param {fabric.Object} object Object to center - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.AnimationContext} context - */ - fxCenterObjectV: function (object, callbacks) { - callbacks = callbacks || {}; - - var empty = function () {}, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - return fabric.util.animate({ - target: this, - startValue: object.getY(), - endValue: this.getCenterPoint().y, - duration: this.FX_DURATION, - onChange: function (value) { - object.setY(value); - _this.requestRenderAll(); - onChange(); - }, - onComplete: function () { - object.setCoords(); - onComplete(); - }, - }); - }, - - /** - * Same as `fabric.Canvas#remove` but animated - * @param {fabric.Object} object Object to remove - * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties - * @param {Function} [callbacks.onComplete] Invoked on completion - * @param {Function} [callbacks.onChange] Invoked on every step of animation - * @return {fabric.AnimationContext} context - */ - fxRemove: function (object, callbacks) { - callbacks = callbacks || {}; - - var empty = function () {}, - onComplete = callbacks.onComplete || empty, - onChange = callbacks.onChange || empty, - _this = this; - - return fabric.util.animate({ - target: this, - startValue: object.opacity, - endValue: 0, - duration: this.FX_DURATION, - onChange: function (value) { - object.set('opacity', value); - _this.requestRenderAll(); - onChange(); - }, - onComplete: function () { - _this.remove(object); - onComplete(); - }, - }); - }, - } - ); - - fabric.util.object.extend( - FabricObject.prototype, - /** @lends FabricObject.prototype */ { - /** - * Animates object's properties - * @param {String|Object} property Property to animate (if string) or properties to animate (if object) - * @param {Number|Object} value Value to animate property to (if string was given first) or options object - * @return {fabric.Object} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} - * @return {fabric.AnimationContext | fabric.AnimationContext[]} animation context (or an array if passed multiple properties) - * - * As object — multiple properties - * - * object.animate({ left: ..., top: ... }); - * object.animate({ left: ..., top: ... }, { duration: ... }); - * - * As string — one property - * - * object.animate('left', ...); - * object.animate('left', { duration: ... }); - * - */ - animate: function () { - if (arguments[0] && typeof arguments[0] === 'object') { - var propsToAnimate = [], - prop, - skipCallbacks, - out = []; - for (prop in arguments[0]) { - propsToAnimate.push(prop); - } - for (var i = 0, len = propsToAnimate.length; i < len; i++) { - prop = propsToAnimate[i]; - skipCallbacks = i !== len - 1; - out.push( - this._animate( - prop, - arguments[0][prop], - arguments[1], - skipCallbacks - ) - ); - } - return out; - } else { - return this._animate.apply(this, arguments); - } - }, - - /** - * @private - * @param {String} property Property to animate - * @param {String} to Value to animate to - * @param {Object} [options] Options object - * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked - */ - _animate: function (property, to, options, skipCallbacks) { - var _this = this, - propPair; - - to = to.toString(); - - options = Object.assign({}, options); - - if (~property.indexOf('.')) { - propPair = property.split('.'); - } - - var propIsColor = - _this.colorProperties.indexOf(property) > -1 || - (propPair && _this.colorProperties.indexOf(propPair[1]) > -1); - - var currentValue = propPair - ? this.get(propPair[0])[propPair[1]] - : this.get(property); - - if (!('from' in options)) { - options.from = currentValue; - } - - if (!propIsColor) { - if (~to.indexOf('=')) { - to = currentValue + parseFloat(to.replace('=', '')); - } else { - to = parseFloat(to); - } - } - - var _options = { - target: this, - startValue: options.from, - endValue: to, - byValue: options.by, - easing: options.easing, - duration: options.duration, - abort: - options.abort && - function (value, valueProgress, timeProgress) { - return options.abort.call( - _this, - value, - valueProgress, - timeProgress - ); - }, - onChange: function (value, valueProgress, timeProgress) { - if (propPair) { - _this[propPair[0]][propPair[1]] = value; - } else { - _this.set(property, value); - } - if (skipCallbacks) { - return; - } - options.onChange && - options.onChange(value, valueProgress, timeProgress); - }, - onComplete: function (value, valueProgress, timeProgress) { - if (skipCallbacks) { - return; - } - - _this.setCoords(); - options.onComplete && - options.onComplete(value, valueProgress, timeProgress); - }, - }; - - if (propIsColor) { - return fabric.util.animateColor(_options); - } else { - return fabric.util.animate(_options); - } - }, - } - ); -})(typeof exports !== 'undefined' ? exports : window); From f8f220e86179418acb054a91c70816fc85d8fb20 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:02:12 +0200 Subject: [PATCH 082/108] type color --- src/color/color.class.ts | 67 ++++++++++++++++------------------------ test/unit/color.js | 15 +++++++-- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index 014478e0dbf..e0561e1f8f0 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -1,4 +1,3 @@ -//@ts-nocheck import { ColorNameMap } from './color_map'; import { reHSLa, reHex, reRGBa } from './constants'; import { hue2rgb, hexify } from './util'; @@ -27,29 +26,26 @@ export class Color { const [r, g, b, a = 1] = color; this.setSource([r, g, b, a]); } else { - this._tryParsingColor(color); + this.setSource(this._tryParsingColor(color)); } } /** * @private * @param {string} [color] Color value to parse + * @returns {TColorAlphaSource} */ - _tryParsingColor(color?: string) { - if (color in ColorNameMap) { - color = ColorNameMap[color]; + protected _tryParsingColor(color: string) { + if (color && color in ColorNameMap) { + color = ColorNameMap[color as keyof typeof ColorNameMap]; } - - const source = - color === 'transparent' - ? [255, 255, 255, 0] - : Color.sourceFromHex(color) || + return color === 'transparent' + ? ([255, 255, 255, 0] as TColorAlphaSource) + : Color.sourceFromHex(color) || Color.sourceFromRgb(color) || - Color.sourceFromHsl(color) || [0, 0, 0, 1]; // color is not recognize let's default to black as canvas does - - if (source) { - this.setSource(source); - } + Color.sourceFromHsl(color) || + // color is not recognize let's default to black as canvas does + ([0, 0, 0, 1] as TColorAlphaSource); } /** @@ -67,7 +63,7 @@ export class Color { const maxValue = Math.max(r, g, b), minValue = Math.min(r, g, b); - let h, s; + let h!: number, s: number; const l = (maxValue + minValue) / 2; if (maxValue === minValue) { @@ -228,20 +224,14 @@ export class Color { otherColor = new Color(otherColor); } - const result = [], - alpha = this.getAlpha(), + const [r, g, b, alpha] = this.getSource(), otherAlpha = 0.5, - source = this.getSource(), - otherSource = otherColor.getSource(); - - for (let i = 0; i < 3; i++) { - result.push( - Math.round(source[i] * (1 - otherAlpha) + otherSource[i] * otherAlpha) + otherSource = otherColor.getSource(), + [R, G, B] = [r, g, b].map((value, index) => + Math.round(value * (1 - otherAlpha) + otherSource[index] * otherAlpha) ); - } - result[3] = alpha; - this.setSource(result); + this.setSource([R, G, B, alpha]); return this; } @@ -264,7 +254,7 @@ export class Color { * @return {Color} */ static fromRgba(color: string): Color { - return Color.fromSource(Color.sourceFromRgb(color)); + return new Color(Color.sourceFromRgb(color)); } /** @@ -286,12 +276,7 @@ export class Color { (parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1)) * (/%$/.test(match[3]) ? 255 : 1); - return [ - parseInt(r, 10), - parseInt(g, 10), - parseInt(b, 10), - match[4] ? parseFloat(match[4]) : 1, - ]; + return [r, g, b, match[4] ? parseFloat(match[4]) : 1]; } } @@ -314,7 +299,7 @@ export class Color { * @return {Color} */ static fromHsla(color: string): Color { - return Color.fromSource(Color.sourceFromHsl(color)); + return new Color(Color.sourceFromHsl(color)); } /** @@ -334,7 +319,7 @@ export class Color { const h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1); - let r, g, b; + let r: number, g: number, b: number; if (s === 0) { r = g = b = l; @@ -363,7 +348,7 @@ export class Color { * @return {Color} */ static fromHex(color: string): Color { - return Color.fromSource(Color.sourceFromHex(color)); + return new Color(Color.sourceFromHex(color)); } /** @@ -409,9 +394,9 @@ export class Color { * @param {TColorSource | TColorAlphaSource} source * @return {Color} */ - static fromSource(source: TColorSource | TColorAlphaSource): Color { - const oColor = new Color(); - oColor.setSource(source); - return oColor; + static fromSource([r, b, g, a = 1]: TColorSource | TColorAlphaSource): Color { + const color = new Color(); + color.setSource([r, b, g, a]); + return color; } } diff --git a/test/unit/color.js b/test/unit/color.js index 145b8c35fb2..3ade5dc11a8 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -381,9 +381,8 @@ assert.deepEqual(fabric.Color.sourceFromHex('fff'), [255,255,255,1]); }); - QUnit.test('fromSource', function(assert) { - assert.ok(typeof fabric.Color.fromSource === 'function'); - var oColor = fabric.Color.fromSource([255,255,255,0.37]); + QUnit.test('from rgba', function(assert) { + var oColor = new fabric.Color([255,255,255,0.37]); assert.ok(oColor); assert.ok(oColor instanceof fabric.Color); @@ -392,6 +391,16 @@ assert.equal(oColor.getAlpha(), 0.37); }); + QUnit.test('from rgb', function(assert) { + var oColor = new fabric.Color([255,255,255]); + + assert.ok(oColor); + assert.ok(oColor instanceof fabric.Color); + assert.equal(oColor.toRgba(), 'rgba(255,255,255,1)'); + assert.equal(oColor.toHex(), 'FFFFFF'); + assert.equal(oColor.getAlpha(), 1); + }); + QUnit.test('overlayWith', function(assert) { var oColor = new fabric.Color('FF0000'); assert.ok(typeof oColor.overlayWith === 'function'); From f8601df0eae35969aec3c6a80d5b35573f264494 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:04:08 +0200 Subject: [PATCH 083/108] dep(): fromSource --- src/color/color.class.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index e0561e1f8f0..ee23512b3f2 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -389,14 +389,11 @@ export class Color { /** * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) - * @static - * @memberOf Color + * @deprecated use `new Color(source)` instead * @param {TColorSource | TColorAlphaSource} source * @return {Color} */ - static fromSource([r, b, g, a = 1]: TColorSource | TColorAlphaSource): Color { - const color = new Color(); - color.setSource([r, b, g, a]); - return color; + static fromSource(source: TColorSource | TColorAlphaSource) { + return new Color(source); } } From dad8a09866900cdad2cca1e96c7bf37050b59b98 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:05:15 +0200 Subject: [PATCH 084/108] Update color.class.ts --- src/color/color.class.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index ee23512b3f2..2d4303aa665 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -36,7 +36,7 @@ export class Color { * @returns {TColorAlphaSource} */ protected _tryParsingColor(color: string) { - if (color && color in ColorNameMap) { + if (color in ColorNameMap) { color = ColorNameMap[color as keyof typeof ColorNameMap]; } return color === 'transparent' From f8d98707e63104e30efed2a15a206e78b75c4af4 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:06:51 +0200 Subject: [PATCH 085/108] Update util.ts --- src/color/util.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/color/util.ts b/src/color/util.ts index 2c25ebf85e8..3fb9a2d5940 100644 --- a/src/color/util.ts +++ b/src/color/util.ts @@ -1,5 +1,4 @@ /** - * @private * @param {Number} p * @param {Number} q * @param {Number} t @@ -25,9 +24,7 @@ export function hue2rgb(p: number, q: number, t: number): number { } /** - * Convert a [0, 255] value to hex - * @param value - * @returns + * Convert a value ∈ [0, 255] to hex */ export function hexify(value: number) { const hexValue = value.toString(16).toUpperCase(); From 5c752851a35ee186e51240c6a0397e52501c9baa Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:12:02 +0200 Subject: [PATCH 086/108] Update object_animation.mixin.ts --- src/mixins/object_animation.mixin.ts | 98 +++++++++++++++++----------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/src/mixins/object_animation.mixin.ts b/src/mixins/object_animation.mixin.ts index d84da45470e..84fb085c744 100644 --- a/src/mixins/object_animation.mixin.ts +++ b/src/mixins/object_animation.mixin.ts @@ -1,15 +1,18 @@ -// @ts-nocheck +import { TColorArg } from '../color/color.class'; import { noop } from '../constants'; import { ObjectEvents } from '../EventTypeDefs'; import { TDegree } from '../typedefs'; -import { animate } from '../util/animate'; -import { animateColor } from '../util/animate_color'; +import { + animate, + animateColor, + AnimationOptions, + ColorAnimationOptions, +} from '../util/animation'; import { StackedObject } from './object_ancestry.mixin'; -/** - * TODO remove transient - */ -type TAnimationOptions = Record; +type TAnimationOptions = T extends number + ? AnimationOptions + : ColorAnimationOptions; export abstract class AnimatableObject< EventSpec extends ObjectEvents = ObjectEvents @@ -34,7 +37,7 @@ export abstract class AnimatableObject< * @param {String|Object} property Property to animate (if string) or properties to animate (if object) * @param {Number|Object} value Value to animate property to (if string was given first) or options object * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} - * @return {AnimationContext | AnimationContext[]} animation context (or an array if passed multiple properties) + * @return {(ColorAnimation | Animation)[]} animation context (or an array if passed multiple properties) * * As object — multiple properties * @@ -47,21 +50,28 @@ export abstract class AnimatableObject< * object.animate('left', ..., { duration: ... }); * */ - animate(key: string, toValue: T, options?: TAnimationOptions): void; - animate(animatable: Record, options?: TAnimationOptions): void; - animate>( + animate( + key: string, + toValue: T, + options?: Partial> + ): void; + animate( + animatable: Record, + options?: Partial> + ): void; + animate>( arg0: S, - arg1: S extends string ? T : TAnimationOptions, - arg2?: S extends string ? TAnimationOptions : never + arg1: S extends string ? T : Partial>, + arg2?: S extends string ? Partial> : never ) { const animatable = ( typeof arg0 === 'string' ? { [arg0]: arg1 } : arg0 ) as Record; const keys = Object.keys(animatable); - const options = ( - typeof arg0 === 'string' ? arg2 : arg1 - ) as TAnimationOptions; - keys.map((key, index) => + const options = (typeof arg0 === 'string' ? arg2 : arg1) as Partial< + TAnimationOptions + >; + return keys.map((key, index) => this._animate( key, animatable[key], @@ -78,7 +88,11 @@ export abstract class AnimatableObject< * @param {String} to Value to animate to * @param {Object} [options] Options object */ - _animate(key: string, to: T, options: TAnimationOptions = {}) { + _animate( + key: string, + to: T, + options: Partial> = {} + ) { const path = key.split('.'); const propIsColor = this.colorProperties.includes(path[path.length - 1]); const currentValue = path.reduce((deep: any, key) => deep[key], this); @@ -94,42 +108,50 @@ export abstract class AnimatableObject< const animationOptions = { target: this, - startValue: options.from ?? currentValue, + startValue: + options.startValue ?? + // backward compat + (options as any).from ?? + currentValue, endValue: to, - byValue: options.by, + // `byValue` takes precedence over `endValue` + byValue: + options.byValue ?? + // backward compat + (options as any).by, easing: options.easing, duration: options.duration, - abort: - options.abort && - ((value, valueProgress, timeProgress) => { - return options.abort.call(this, value, valueProgress, timeProgress); - }), - onChange: (value, valueProgress, timeProgress) => { + onChange: ( + value: string | number, + valueRatio: number, + durationRatio: number + ) => { path.reduce((deep: any, key, index) => { if (index === path.length - 1) { deep[key] = value; } return deep[key]; - }, this); + }); options.onChange && - options.onChange(value, valueProgress, timeProgress); + // @ts-expect-error generic callback arg0 is wrong + options.onChange(value, valueRatio, durationRatio); }, - onComplete: (value, valueProgress, timeProgress) => { + onComplete: ( + value: string | number, + valueRatio: number, + durationRatio: number + ) => { this.setCoords(); options.onComplete && - options.onComplete(value, valueProgress, timeProgress); + // @ts-expect-error generic callback arg0 is wrong + options.onComplete(value, valueRatio, durationRatio); }, - }; + } as TAnimationOptions; if (propIsColor) { - return animateColor( - animationOptions.startValue, - animationOptions.endValue, - animationOptions.duration, - animationOptions - ); + return animateColor(animationOptions as ColorAnimationOptions); } else { - return animate(animationOptions); + return animate(animationOptions as AnimationOptions); } } From 70cf087d14b4444cf79f2dec0cb962ad8d5c4346 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:14:19 +0200 Subject: [PATCH 087/108] Update color.class.ts --- src/color/color.class.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index 2d4303aa665..62839e86c79 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -21,6 +21,7 @@ export class Color { */ constructor(color?: TColorArg) { if (!color) { + // we default to black as canvas does this.setSource([0, 0, 0, 1]); } else if (Array.isArray(color)) { const [r, g, b, a = 1] = color; @@ -44,7 +45,8 @@ export class Color { : Color.sourceFromHex(color) || Color.sourceFromRgb(color) || Color.sourceFromHsl(color) || - // color is not recognize let's default to black as canvas does + // color is not recognized + // we default to black as canvas does ([0, 0, 0, 1] as TColorAlphaSource); } From e582c7ed0d7756d737e40b85cd4765376ace7bfb Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:21:09 +0200 Subject: [PATCH 088/108] fix --- src/mixins/object_animation.mixin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mixins/object_animation.mixin.ts b/src/mixins/object_animation.mixin.ts index 84fb085c744..560a29a471f 100644 --- a/src/mixins/object_animation.mixin.ts +++ b/src/mixins/object_animation.mixin.ts @@ -131,7 +131,7 @@ export abstract class AnimatableObject< deep[key] = value; } return deep[key]; - }); + }, this); options.onChange && // @ts-expect-error generic callback arg0 is wrong options.onChange(value, valueRatio, durationRatio); From c128245fb007df29784219f713e6da623befbf25 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:21:52 +0200 Subject: [PATCH 089/108] Update object_animation.mixin.ts --- src/mixins/object_animation.mixin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mixins/object_animation.mixin.ts b/src/mixins/object_animation.mixin.ts index 560a29a471f..096405f6b26 100644 --- a/src/mixins/object_animation.mixin.ts +++ b/src/mixins/object_animation.mixin.ts @@ -126,7 +126,7 @@ export abstract class AnimatableObject< valueRatio: number, durationRatio: number ) => { - path.reduce((deep: any, key, index) => { + path.reduce((deep: Record, key, index) => { if (index === path.length - 1) { deep[key] = value; } From b2b203e2a01385e57ef5110128bfafcf919b24d0 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:34:28 +0200 Subject: [PATCH 090/108] refactor very old code --- src/mixins/object_animation.mixin.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/mixins/object_animation.mixin.ts b/src/mixins/object_animation.mixin.ts index 096405f6b26..7f225b54e1e 100644 --- a/src/mixins/object_animation.mixin.ts +++ b/src/mixins/object_animation.mixin.ts @@ -97,13 +97,10 @@ export abstract class AnimatableObject< const propIsColor = this.colorProperties.includes(path[path.length - 1]); const currentValue = path.reduce((deep: any, key) => deep[key], this); - to = to.toString(); - if (!propIsColor) { - if (~to.indexOf('=')) { - to = currentValue + parseFloat(to.replace('=', '')); - } else { - to = parseFloat(to); - } + if (!propIsColor && typeof to === 'string') { + to = to.includes('=') + ? currentValue + parseFloat(to.replace('=', '')) + : parseFloat(to); } const animationOptions = { From 2a78e5b3fbf0b81aa744d358b010f1df53b925f9 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:41:01 +0200 Subject: [PATCH 091/108] restore abort --- src/mixins/object_animation.mixin.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mixins/object_animation.mixin.ts b/src/mixins/object_animation.mixin.ts index 7f225b54e1e..6095e027d32 100644 --- a/src/mixins/object_animation.mixin.ts +++ b/src/mixins/object_animation.mixin.ts @@ -118,6 +118,7 @@ export abstract class AnimatableObject< (options as any).by, easing: options.easing, duration: options.duration, + abort: options.abort?.bind(this), onChange: ( value: string | number, valueRatio: number, From 167ea19fa645d95cea781e24c068dbcc71435569 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:58:18 +0200 Subject: [PATCH 092/108] default abort --- src/constants.ts | 2 +- src/util/animation/AnimationBase.ts | 4 +++- src/util/animation/ColorAnimation.ts | 17 +++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 30932a1811e..2f8d1c39130 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,7 +2,7 @@ import { TMat2D } from './typedefs'; // TODO: consider using https://github.com/swiing/rollup-plugin-import-assertions so we can import json in node and have rollup build pass export { version as VERSION } from '../package.json'; -export function noop(...args: unknown[]): any {} +export function noop() {} export const halfPI = Math.PI / 2; export const twoMathPi = Math.PI * 2; export const PiBy180 = Math.PI / 180; diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index d7e23ca2e6c..b70002a2011 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -12,6 +12,8 @@ import { AnimationState, } from './types'; +export const defaultAbort = () => false; + export abstract class AnimationBase< T extends number | number[] = number | number[] > { @@ -61,7 +63,7 @@ export abstract class AnimationBase< onStart = noop, onChange = noop, onComplete = noop, - abort = noop, + abort = defaultAbort, target, }: Partial & TAnimationCallbacks> & Required, 'endValue'>>) { diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index dc8157a0d1a..18309f7ba7d 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -1,14 +1,15 @@ import { Color } from '../../color'; import { TColorAlphaSource } from '../../color/color.class'; -import { noop } from '../../constants'; import { capValue } from '../misc/capValue'; import { AnimationBase } from './AnimationBase'; import { ColorAnimationOptions, TOnAnimationChangeCallback } from './types'; -const wrapColorCallback = - (callback: TOnAnimationChangeCallback) => - (rgba: TColorAlphaSource, valueRatio: number, durationRatio: number) => - callback(new Color(rgba).toRgba(), valueRatio, durationRatio); +const wrapColorCallback = ( + callback?: TOnAnimationChangeCallback +) => + callback && + ((rgba: TColorAlphaSource, valueRatio: number, durationRatio: number) => + callback(new Color(rgba).toRgba(), valueRatio, durationRatio)); export class ColorAnimation extends AnimationBase { constructor({ @@ -20,9 +21,9 @@ export class ColorAnimation extends AnimationBase { 1 - Math.cos((timeElapsed / duration) * (Math.PI / 2)); return startValue + byValue * durationRatio; }, - onChange = noop, - onComplete = noop, - abort = noop, + onChange, + onComplete, + abort, ...options }: ColorAnimationOptions) { const startColor = new Color(startValue).getSource(); From 5e902983c55baeb5ec127cf537e14bc9fda068c1 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Thu, 1 Dec 2022 09:59:13 +0200 Subject: [PATCH 093/108] Update AnimationBase.ts --- src/util/animation/AnimationBase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index b70002a2011..277a9be7bd8 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -12,7 +12,7 @@ import { AnimationState, } from './types'; -export const defaultAbort = () => false; +const defaultAbort = () => false; export abstract class AnimationBase< T extends number | number[] = number | number[] From fbb08d9e1b4ab0a6852c5bf99535036d9e417059 Mon Sep 17 00:00:00 2001 From: Lazauya Date: Sun, 4 Dec 2022 18:12:29 -0600 Subject: [PATCH 094/108] Doc additions and fixes --- src/color/color.class.ts | 6 ++++ src/util/animation/Animation.ts | 1 + src/util/animation/AnimationBase.ts | 43 +++++++++++++++++++++---- src/util/animation/AnimationRegistry.ts | 12 +++++-- src/util/animation/easing.ts | 10 ++---- 5 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index 014478e0dbf..258aa7e4023 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -3,8 +3,14 @@ import { ColorNameMap } from './color_map'; import { reHSLa, reHex, reRGBa } from './constants'; import { hue2rgb, hexify } from './util'; +/** + * RGB format + */ export type TColorSource = [number, number, number]; +/** + * RGBA format + */ export type TColorAlphaSource = [number, number, number, number]; export type TColorArg = string | TColorSource | TColorAlphaSource; diff --git a/src/util/animation/Animation.ts b/src/util/animation/Animation.ts index bd1781cd15b..c08dca0c61c 100644 --- a/src/util/animation/Animation.ts +++ b/src/util/animation/Animation.ts @@ -14,6 +14,7 @@ export class Animation extends AnimationBase { byValue, }); } + protected calculate(timeElapsed: number) { const value = this.easing( timeElapsed, diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index d7e23ca2e6c..90a7b2d84aa 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -15,42 +15,66 @@ import { export abstract class AnimationBase< T extends number | number[] = number | number[] > { + /** + * @see TAnimationBaseOptions + */ readonly startValue: T; readonly byValue: T; readonly endValue: T; readonly duration: number; readonly delay: number; protected readonly easing: TEasingFunction; + /** + * @private + * @see TAnimationCallbacks + */ private readonly _onStart: VoidFunction; private readonly _onChange: TOnAnimationChangeCallback; private readonly _onComplete: TOnAnimationChangeCallback; private readonly _abort: TAbortCallback; /** - * used to register the animation to a target object so it can be cancelled within hte object context + * Used to register the animation to a target object + * so that it can be cancelled within the object context */ readonly target?: unknown; private _state: AnimationState = 'pending'; /** - * time % + * Time %, or the ratio of `timeElapsed / duration` + * @see tick */ durationRatio = 0; /** - * value % + * Value %, of the ratio of `(currentValue - startValue) / (endValue - startValue)` */ valueRatio = 0; /** - * current value + * Current value */ value: T; /** - * animation start time ms + * Animation start time ms */ private startTime!: number; /** - * since both `byValue` and `endValue` are accepted in subclass options and are populated with defaults if missing, - * we defer to `byValue` and ignore `endValue` to avoid conflict + * Constructor + * Since both `byValue` and `endValue` are accepted in subclass options + * and are populated with defaults if missing, we defer to `byValue` and + * ignore `endValue` to avoid conflict + * @see TAnimationBaseOptions + * @see TAnimationValues + * @see TAnimationCallbacks + * @param startValue + * @param byValue + * @param duration + * @param delay + * @param easing + * @param onStart + * @param onChange + * @param onComplete + * @param abort + * @param target */ constructor({ startValue, @@ -84,6 +108,11 @@ export abstract class AnimationBase< return this._state; } + /** + * Calculate the current value based on the easing parameters + * @param timeElapsed in ms + * @protected + */ protected abstract calculate(timeElapsed: number): { value: T; changeRatio: number; diff --git a/src/util/animation/AnimationRegistry.ts b/src/util/animation/AnimationRegistry.ts index b67f4268758..2acc4036f01 100644 --- a/src/util/animation/AnimationRegistry.ts +++ b/src/util/animation/AnimationRegistry.ts @@ -7,13 +7,17 @@ import { AnimationBase } from './AnimationBase'; * Array holding all running animations */ class AnimationRegistry extends Array { + /** + * Remove a single animation using an animation context + * @param {AnimationBase} context + */ remove(context: AnimationBase) { const index = this.indexOf(context); index > -1 && this.splice(index, 1); } /** - * cancel all running animations at the next requestAnimFrame + * Cancel all running animations at the next requestAnimFrame */ cancelAll() { const animations = this.splice(0); @@ -22,7 +26,8 @@ class AnimationRegistry extends Array { } /** - * cancel all running animations attached to canvas at the next requestAnimFrame + * Cancel all running animations attached to a Canvas at the next requestAnimFrame + * @param {Canvas} canvas */ cancelByCanvas(canvas: Canvas) { if (!canvas) { @@ -38,7 +43,8 @@ class AnimationRegistry extends Array { } /** - * cancel all running animations for target at the next requestAnimFrame + * Cancel all running animations for target at the next requestAnimFrame + * @param target */ cancelByTarget(target: AnimationBase['target']) { if (!target) { diff --git a/src/util/animation/easing.ts b/src/util/animation/easing.ts index ea2b13521c3..ff9889e96a2 100644 --- a/src/util/animation/easing.ts +++ b/src/util/animation/easing.ts @@ -6,13 +6,6 @@ import { twoMathPi, halfPI } from '../../constants'; import { TEasingFunction } from './types'; -/** - * TODO: ask about docs for this, I don't understand it - * @param a - * @param c - * @param p - * @param s - */ const normalize = (a: number, c: number, p: number, s: number) => { if (a < Math.abs(c)) { a = c; @@ -28,6 +21,9 @@ const normalize = (a: number, c: number, p: number, s: number) => { return { a, c, p, s }; }; +/** + * Bounce ease + */ const elastic = ( a: number, s: number, From d488aef1835e26f713a2edc32db39fd86f5ba3b3 Mon Sep 17 00:00:00 2001 From: Lazauya Date: Fri, 9 Dec 2022 19:00:57 -0600 Subject: [PATCH 095/108] Doc additions and fixes --- src/color/color.class.ts | 45 ++++++++++++++----------- src/util/animation/AnimationBase.ts | 2 +- src/util/animation/AnimationRegistry.ts | 6 ++-- src/util/animation/ColorAnimation.ts | 12 +++---- src/util/animation/easing.ts | 8 ++++- src/util/animation/types.ts | 6 ++-- 6 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index 7f9999da98b..2b3488f98db 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -5,21 +5,26 @@ import { hue2rgb, hexify } from './util'; /** * RGB format */ -export type TColorSource = [number, number, number]; +export type TRGBColorSource = [red: number, green: number, blue: number]; /** * RGBA format */ -export type TColorAlphaSource = [number, number, number, number]; +export type TRGBAColorSource = [ + red: number, + green: number, + blue: number, + alpha: number +]; -export type TColorArg = string | TColorSource | TColorAlphaSource; +export type TColorArg = string | TRGBColorSource | TRGBAColorSource; /** * @class Color common color operations * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors colors} */ export class Color { - private _source: TColorAlphaSource; + private _source: TRGBAColorSource; /** * @@ -40,20 +45,20 @@ export class Color { /** * @private * @param {string} [color] Color value to parse - * @returns {TColorAlphaSource} + * @returns {TRGBAColorSource} */ protected _tryParsingColor(color: string) { if (color in ColorNameMap) { color = ColorNameMap[color as keyof typeof ColorNameMap]; } return color === 'transparent' - ? ([255, 255, 255, 0] as TColorAlphaSource) + ? ([255, 255, 255, 0] as TRGBAColorSource) : Color.sourceFromHex(color) || Color.sourceFromRgb(color) || Color.sourceFromHsl(color) || // color is not recognized // we default to black as canvas does - ([0, 0, 0, 1] as TColorAlphaSource); + ([0, 0, 0, 1] as TRGBAColorSource); } /** @@ -62,9 +67,9 @@ export class Color { * @param {Number} r Red color value * @param {Number} g Green color value * @param {Number} b Blue color value - * @return {TColorSource} Hsl color + * @return {TRGBColorSource} Hsl color */ - _rgbToHsl(r: number, g: number, b: number): TColorSource { + _rgbToHsl(r: number, g: number, b: number): TRGBColorSource { r /= 255; g /= 255; b /= 255; @@ -98,7 +103,7 @@ export class Color { /** * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @return {TColorAlphaSource} + * @return {TRGBAColorSource} */ getSource() { return this._source; @@ -106,9 +111,9 @@ export class Color { /** * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @param {TColorAlphaSource} source + * @param {TRGBAColorSource} source */ - setSource(source: TColorAlphaSource) { + setSource(source: TRGBAColorSource) { this._source = source; } @@ -269,9 +274,9 @@ export class Color { * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format * @memberOf Color * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) - * @return {TColorAlphaSource | undefined} source + * @return {TRGBAColorSource | undefined} source */ - static sourceFromRgb(color: string): TColorAlphaSource | undefined { + static sourceFromRgb(color: string): TRGBAColorSource | undefined { const match = color.match(reRGBa); if (match) { const r = @@ -315,10 +320,10 @@ export class Color { * Adapted from https://github.com/mjijackson * @memberOf Color * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) - * @return {TColorAlphaSource | undefined} source + * @return {TRGBAColorSource | undefined} source * @see http://http://www.w3.org/TR/css3-color/#hsl-color */ - static sourceFromHsl(color: string): TColorAlphaSource | undefined { + static sourceFromHsl(color: string): TRGBAColorSource | undefined { const match = color.match(reHSLa); if (!match) { return; @@ -364,9 +369,9 @@ export class Color { * @static * @memberOf Color * @param {String} color ex: FF5555 or FF5544CC (RGBa) - * @return {TColorAlphaSource | undefined} source + * @return {TRGBAColorSource | undefined} source */ - static sourceFromHex(color: string): TColorAlphaSource | undefined { + static sourceFromHex(color: string): TRGBAColorSource | undefined { if (color.match(reHex)) { const value = color.slice(color.indexOf('#') + 1), isShortNotation = value.length === 3 || value.length === 4, @@ -398,10 +403,10 @@ export class Color { /** * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) * @deprecated use `new Color(source)` instead - * @param {TColorSource | TColorAlphaSource} source + * @param {TRGBColorSource | TRGBAColorSource} source * @return {Color} */ - static fromSource(source: TColorSource | TColorAlphaSource) { + static fromSource(source: TRGBColorSource | TRGBAColorSource) { return new Color(source); } } diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index ff1c3e2b791..5c6f5fe7c38 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -47,7 +47,7 @@ export abstract class AnimationBase< */ durationRatio = 0; /** - * Value %, of the ratio of `(currentValue - startValue) / (endValue - startValue)` + * Value %, or the ratio of `(currentValue - startValue) / (endValue - startValue)` */ valueRatio = 0; /** diff --git a/src/util/animation/AnimationRegistry.ts b/src/util/animation/AnimationRegistry.ts index 2acc4036f01..979d4004284 100644 --- a/src/util/animation/AnimationRegistry.ts +++ b/src/util/animation/AnimationRegistry.ts @@ -17,7 +17,7 @@ class AnimationRegistry extends Array { } /** - * Cancel all running animations at the next requestAnimFrame + * Cancel all running animations on the next frame */ cancelAll() { const animations = this.splice(0); @@ -26,7 +26,7 @@ class AnimationRegistry extends Array { } /** - * Cancel all running animations attached to a Canvas at the next requestAnimFrame + * Cancel all running animations attached to a Canvas on the next frame * @param {Canvas} canvas */ cancelByCanvas(canvas: Canvas) { @@ -43,7 +43,7 @@ class AnimationRegistry extends Array { } /** - * Cancel all running animations for target at the next requestAnimFrame + * Cancel all running animations for target on the next frame * @param target */ cancelByTarget(target: AnimationBase['target']) { diff --git a/src/util/animation/ColorAnimation.ts b/src/util/animation/ColorAnimation.ts index 18309f7ba7d..fe4be4ec021 100644 --- a/src/util/animation/ColorAnimation.ts +++ b/src/util/animation/ColorAnimation.ts @@ -1,5 +1,5 @@ import { Color } from '../../color'; -import { TColorAlphaSource } from '../../color/color.class'; +import { TRGBAColorSource } from '../../color/color.class'; import { capValue } from '../misc/capValue'; import { AnimationBase } from './AnimationBase'; import { ColorAnimationOptions, TOnAnimationChangeCallback } from './types'; @@ -8,10 +8,10 @@ const wrapColorCallback = ( callback?: TOnAnimationChangeCallback ) => callback && - ((rgba: TColorAlphaSource, valueRatio: number, durationRatio: number) => + ((rgba: TRGBAColorSource, valueRatio: number, durationRatio: number) => callback(new Color(rgba).toRgba(), valueRatio, durationRatio)); -export class ColorAnimation extends AnimationBase { +export class ColorAnimation extends AnimationBase { constructor({ startValue, endValue, @@ -37,7 +37,7 @@ export class ColorAnimation extends AnimationBase { .getSource() : (endColor.map( (value, i) => value - startColor[i] - ) as TColorAlphaSource), + ) as TRGBAColorSource), easing, onChange: wrapColorCallback(onChange), onComplete: wrapColorCallback(onComplete), @@ -47,10 +47,10 @@ export class ColorAnimation extends AnimationBase { protected calculate(timeElapsed: number) { const [r, g, b, a] = this.startValue.map((value, i) => this.easing(timeElapsed, value, this.byValue[i], this.duration, i) - ) as TColorAlphaSource; + ) as TRGBAColorSource; const rgb = [r, g, b].map(Math.round); return { - value: [...rgb, capValue(0, a, 1)] as TColorAlphaSource, + value: [...rgb, capValue(0, a, 1)] as TRGBAColorSource, changeRatio: // to correctly calculate the change ratio we must find a changed value rgb diff --git a/src/util/animation/easing.ts b/src/util/animation/easing.ts index 3c76141f3ff..d25de7145b7 100644 --- a/src/util/animation/easing.ts +++ b/src/util/animation/easing.ts @@ -3,7 +3,7 @@ * See Easing Equations by Robert Penner */ -import { twoMathPi, halfPI } from '../constants'; +import { twoMathPi, halfPI } from '../../constants'; type TEasingFunction = ( currentTime: number, @@ -27,6 +27,12 @@ const normalize = (a: number, c: number, p: number, s: number) => { return { a, c, p, s }; }; +/** + * Default sinusoidal easing + */ +export const defaultEasing: TEasingFunction = (t, b, c, d) => + -c * Math.cos((t / d) * halfPI) + c + b; + const elastic = ( a: number, s: number, diff --git a/src/util/animation/types.ts b/src/util/animation/types.ts index 7199099bf3b..b5c3af11004 100644 --- a/src/util/animation/types.ts +++ b/src/util/animation/types.ts @@ -50,19 +50,19 @@ export type TAnimationBaseOptions = { * Duration of the animation in ms * @default 500 */ - duration: number; + duration?: number; /** * Delay to start the animation in ms * @default 0 */ - delay: number; + delay?: number; /** * Easing function * @default {defaultEasing} */ - easing: TEasingFunction; + easing?: TEasingFunction; /** * The object this animation is being performed on From a9cd9b88ccf94a992fe67ee61660ccf7573afd36 Mon Sep 17 00:00:00 2001 From: Lazauya Date: Sat, 10 Dec 2022 20:48:41 -0600 Subject: [PATCH 096/108] import fix, commenting, and removed unused color code --- src/color/color.class.ts | 10 ---------- src/mixins/object_animation.mixin.ts | 6 ++++++ src/shapes/object.class.ts | 2 +- src/static_canvas.class.ts | 3 +-- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index 2b3488f98db..8c6ed02efab 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -399,14 +399,4 @@ export class Color { ]; } } - - /** - * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) - * @deprecated use `new Color(source)` instead - * @param {TRGBColorSource | TRGBAColorSource} source - * @return {Color} - */ - static fromSource(source: TRGBColorSource | TRGBAColorSource) { - return new Color(source); - } } diff --git a/src/mixins/object_animation.mixin.ts b/src/mixins/object_animation.mixin.ts index 6095e027d32..c0a6988f7a4 100644 --- a/src/mixins/object_animation.mixin.ts +++ b/src/mixins/object_animation.mixin.ts @@ -45,10 +45,14 @@ export abstract class AnimatableObject< * object.animate({ left: ..., top: ... }, { duration: ... }); * * As string — one property + * Supports +=N and -=N for animating N units in a given direction * * object.animate('left', ...); * object.animate('left', ..., { duration: ... }); * + * Example of +=/-= + * object.animate('right', '-=50'); + * object.animate('top', '+=50', { duration: ... }); */ animate( key: string, @@ -98,6 +102,8 @@ export abstract class AnimatableObject< const currentValue = path.reduce((deep: any, key) => deep[key], this); if (!propIsColor && typeof to === 'string') { + // check for things like +=50 + // which should animate so that the thing moves by 50 units in the positive direction to = to.includes('=') ? currentValue + parseFloat(to.replace('=', '')) : parseFloat(to); diff --git a/src/shapes/object.class.ts b/src/shapes/object.class.ts index f27fbbb37e4..8bc415f4cd5 100644 --- a/src/shapes/object.class.ts +++ b/src/shapes/object.class.ts @@ -14,7 +14,7 @@ import type { TSize, TCacheCanvasDimensions, } from '../typedefs'; -import { runningAnimations } from '../util/animation_registry'; +import { runningAnimations } from '../util/animation'; import { clone } from '../util/lang_object'; import { capitalize } from '../util/lang_string'; import { capValue } from '../util/misc/capValue'; diff --git a/src/static_canvas.class.ts b/src/static_canvas.class.ts index 5fa8f434445..80a8705f255 100644 --- a/src/static_canvas.class.ts +++ b/src/static_canvas.class.ts @@ -10,7 +10,6 @@ import { TSVGReviver } from './mixins/object.svg_export'; import { CommonMethods } from './mixins/shared_methods.mixin'; import { Pattern } from './pattern.class'; import { Point } from './point.class'; -import { requestAnimFrame } from './util/animation'; import type { FabricObject } from './shapes/fabricObject.class'; import { TCachedFabricObject } from './shapes/object.class'; import { Rect } from './shapes/rect.class'; @@ -21,7 +20,7 @@ import type { TSize, TValidToObjectMethod, } from './typedefs'; -import { cancelAnimFrame, requestAnimFrame } from './util/animate'; +import { cancelAnimFrame, requestAnimFrame } from './util/animation'; import { cleanUpJsdomNode, getElementOffset, From 76823da3245584a97da3031596fad096e2f0ac2e Mon Sep 17 00:00:00 2001 From: Lazauya Date: Sat, 10 Dec 2022 20:50:31 -0600 Subject: [PATCH 097/108] @see fix --- src/util/animation/easing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/animation/easing.ts b/src/util/animation/easing.ts index d25de7145b7..97ea30c3543 100644 --- a/src/util/animation/easing.ts +++ b/src/util/animation/easing.ts @@ -1,6 +1,6 @@ /** * Easing functions - * See Easing Equations by Robert Penner + * @see {@link http://gizma.com/easing/ Easing Equations by Robert Penner} */ import { twoMathPi, halfPI } from '../../constants'; From a89ce35920707019313f16daf941e02ab683af66 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 11 Dec 2022 07:51:21 +0200 Subject: [PATCH 098/108] cleanup JSDOC --- src/util/animation/AnimationBase.ts | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index 5c6f5fe7c38..ef1200a4534 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -10,6 +10,7 @@ import { TOnAnimationChangeCallback, TAnimationCallbacks, AnimationState, + TAnimationOptions, } from './types'; const defaultAbort = () => false; @@ -17,23 +18,18 @@ const defaultAbort = () => false; export abstract class AnimationBase< T extends number | number[] = number | number[] > { - /** - * @see TAnimationBaseOptions - */ readonly startValue: T; readonly byValue: T; readonly endValue: T; readonly duration: number; readonly delay: number; protected readonly easing: TEasingFunction; - /** - * @private - * @see TAnimationCallbacks - */ + private readonly _onStart: VoidFunction; private readonly _onChange: TOnAnimationChangeCallback; private readonly _onComplete: TOnAnimationChangeCallback; private readonly _abort: TAbortCallback; + /** * Used to register the animation to a target object * so that it can be cancelled within the object context @@ -67,16 +63,6 @@ export abstract class AnimationBase< * @see TAnimationBaseOptions * @see TAnimationValues * @see TAnimationCallbacks - * @param startValue - * @param byValue - * @param duration - * @param delay - * @param easing - * @param onStart - * @param onChange - * @param onComplete - * @param abort - * @param target */ constructor({ startValue, From 98f9966f5b2cdfbc55f429944fa906f417691287 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 11 Dec 2022 08:06:45 +0200 Subject: [PATCH 099/108] fix merge conflict @Lazauya you accidently overriden the fixed file with the stale file from master --- src/util/animation/easing.ts | 42 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/util/animation/easing.ts b/src/util/animation/easing.ts index 97ea30c3543..54eaf0d120e 100644 --- a/src/util/animation/easing.ts +++ b/src/util/animation/easing.ts @@ -4,13 +4,7 @@ */ import { twoMathPi, halfPI } from '../../constants'; - -type TEasingFunction = ( - currentTime: number, - startValue: number, - byValue: number, - duration: number -) => number; +import { TEasingFunction } from './types'; const normalize = (a: number, c: number, p: number, s: number) => { if (a < Math.abs(c)) { @@ -27,12 +21,6 @@ const normalize = (a: number, c: number, p: number, s: number) => { return { a, c, p, s }; }; -/** - * Default sinusoidal easing - */ -export const defaultEasing: TEasingFunction = (t, b, c, d) => - -c * Math.cos((t / d) * halfPI) + c + b; - const elastic = ( a: number, s: number, @@ -42,11 +30,23 @@ const elastic = ( ): number => a * Math.pow(2, 10 * (t -= 1)) * Math.sin(((t * d - s) * twoMathPi) / p); +/** + * Default sinusoidal easing + */ +export const defaultEasing: TEasingFunction = (t, b, c, d) => + -c * Math.cos((t / d) * halfPI) + c + b; + +/** + * Cubic easing in + */ +export const easeInCubic: TEasingFunction = (t, b, c, d) => + c * (t / d) ** 3 + b; + /** * Cubic easing out */ export const easeOutCubic: TEasingFunction = (t, b, c, d) => - c * ((t /= d - 1) * t ** 2 + 1) + b; + c * ((t / d - 1) ** 3 + 1) + b; /** * Cubic easing in and out @@ -56,7 +56,7 @@ export const easeInOutCubic: TEasingFunction = (t, b, c, d) => { if (t < 1) { return (c / 2) * t ** 3 + b; } - return (c / 2) * ((t -= 2) * t ** 2 + 2) + b; + return (c / 2) * ((t - 2) ** 3 + 2) + b; }; /** @@ -86,13 +86,13 @@ export const easeInOutQuart: TEasingFunction = (t, b, c, d) => { * Quintic easing in */ export const easeInQuint: TEasingFunction = (t, b, c, d) => - c * (t /= d) * t ** 4 + b; + c * (t / d) ** 5 + b; /** * Quintic easing out */ export const easeOutQuint: TEasingFunction = (t, b, c, d) => - c * ((t /= d - 1) * t ** 4 + 1) + b; + c * ((t / d - 1) ** 5 + 1) + b; /** * Quintic easing in and out @@ -102,7 +102,7 @@ export const easeInOutQuint: TEasingFunction = (t, b, c, d) => { if (t < 1) { return (c / 2) * t ** 5 + b; } - return (c / 2) * ((t -= 2) * t ** 4 + 2) + b; + return (c / 2) * ((t - 2) ** 5 + 2) + b; }; /** @@ -325,9 +325,3 @@ export const easeInOutQuad: TEasingFunction = (t, b, c, d) => { } return (-c / 2) * (--t * (t - 2) - 1) + b; }; - -/** - * Cubic easing in - */ -export const easeInCubic: TEasingFunction = (t, b, c, d) => - c * (t /= d) * t * t + b; From 36cd48ad83c346ded926ffe22e8aa652a7cd18c8 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 11 Dec 2022 08:10:07 +0200 Subject: [PATCH 100/108] imports --- src/util/animation/AnimationBase.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index ef1200a4534..f5733e5bc22 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -3,14 +3,13 @@ import { requestAnimFrame } from './AnimationFrameProvider'; import { runningAnimations } from './AnimationRegistry'; import { defaultEasing } from './easing'; import { + AnimationState, + TAbortCallback, TAnimationBaseOptions, + TAnimationCallbacks, TAnimationValues, - TAbortCallback, TEasingFunction, TOnAnimationChangeCallback, - TAnimationCallbacks, - AnimationState, - TAnimationOptions, } from './types'; const defaultAbort = () => false; @@ -60,9 +59,6 @@ export abstract class AnimationBase< * Since both `byValue` and `endValue` are accepted in subclass options * and are populated with defaults if missing, we defer to `byValue` and * ignore `endValue` to avoid conflict - * @see TAnimationBaseOptions - * @see TAnimationValues - * @see TAnimationCallbacks */ constructor({ startValue, From 68fdc1198e63360c7fe8fd7f33b68e6055083db2 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 27 Dec 2022 12:25:42 +0200 Subject: [PATCH 101/108] accept color instance --- src/color/color.class.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index 8c6ed02efab..1109606a1b8 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -17,7 +17,7 @@ export type TRGBAColorSource = [ alpha: number ]; -export type TColorArg = string | TRGBColorSource | TRGBAColorSource; +export type TColorArg = string | TRGBColorSource | TRGBAColorSource | Color; /** * @class Color common color operations @@ -34,6 +34,8 @@ export class Color { if (!color) { // we default to black as canvas does this.setSource([0, 0, 0, 1]); + } else if (color instanceof Color) { + this.setSource([...color._source]); } else if (Array.isArray(color)) { const [r, g, b, a = 1] = color; this.setSource([r, g, b, a]); From 6e8861e16b1c0d48278563443709db93ed7858e2 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 27 Dec 2022 12:27:23 +0200 Subject: [PATCH 102/108] Update color.js --- test/unit/color.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/unit/color.js b/test/unit/color.js index 3ade5dc11a8..2e4a7745ab2 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -401,6 +401,16 @@ assert.equal(oColor.getAlpha(), 1); }); + QUnit.test('from Color instance', function(assert) { + var oColor = new fabric.Color(new fabric.Color([255,255,255])); + + assert.ok(oColor); + assert.ok(oColor instanceof fabric.Color); + assert.equal(oColor.toRgba(), 'rgba(255,255,255,1)'); + assert.equal(oColor.toHex(), 'FFFFFF'); + assert.equal(oColor.getAlpha(), 1); + }); + QUnit.test('overlayWith', function(assert) { var oColor = new fabric.Color('FF0000'); assert.ok(typeof oColor.overlayWith === 'function'); From 53779e63f50afd243384791317972f64cde35293 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Tue, 27 Dec 2022 12:37:26 +0200 Subject: [PATCH 103/108] fix imports --- src/util/animation/AnimationRegistry.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/animation/AnimationRegistry.ts b/src/util/animation/AnimationRegistry.ts index 979d4004284..4313181d05d 100644 --- a/src/util/animation/AnimationRegistry.ts +++ b/src/util/animation/AnimationRegistry.ts @@ -1,7 +1,7 @@ import { fabric } from '../../../HEADER'; -import type { FabricObject } from '../../shapes/fabricObject.class'; -import { Canvas } from '../../__types__'; -import { AnimationBase } from './AnimationBase'; +import type { Canvas } from '../../canvas.class'; +import type { FabricObject } from '../../shapes/Object/FabricObject'; +import type { AnimationBase } from './AnimationBase'; /** * Array holding all running animations From e1e2c0bf562b5b76aca1c6dc379bc8b3351ad6ff Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Thu, 29 Dec 2022 19:59:47 +0100 Subject: [PATCH 104/108] removed unnecessary changes to object.ts --- src/shapes/Object/Object.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shapes/Object/Object.ts b/src/shapes/Object/Object.ts index efe56ea17d6..3c941f96328 100644 --- a/src/shapes/Object/Object.ts +++ b/src/shapes/Object/Object.ts @@ -4,14 +4,15 @@ import { cache } from '../../cache'; import { config } from '../../config'; import { ALIASING_LIMIT, iMatrix, VERSION } from '../../constants'; import { ObjectEvents } from '../../EventTypeDefs'; +import { AnimatableObject } from './AnimatableObject'; import { Point } from '../../point.class'; import { Shadow } from '../../shadow.class'; import type { - TCacheCanvasDimensions, TClassProperties, TDegree, TFiller, TSize, + TCacheCanvasDimensions, } from '../../typedefs'; import { classRegistry } from '../../util/class_registry'; import { runningAnimations } from '../../util/animation'; @@ -32,7 +33,6 @@ import { import { pick } from '../../util/misc/pick'; import { toFixed } from '../../util/misc/toFixed'; import type { Group } from '../group.class'; -import { AnimatableObject } from './AnimatableObject'; export type TCachedFabricObject = FabricObject & Required< From e5f3cd71dcd2c5e8e5cf330f569d7a798f4e902f Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Thu, 29 Dec 2022 21:11:13 +0100 Subject: [PATCH 105/108] ooops --- src/canvas/static_canvas.class.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvas/static_canvas.class.ts b/src/canvas/static_canvas.class.ts index fe972567787..ecc98102541 100644 --- a/src/canvas/static_canvas.class.ts +++ b/src/canvas/static_canvas.class.ts @@ -22,7 +22,7 @@ import { TToCanvasElementOptions, TValidToObjectMethod, } from '../typedefs'; -import { cancelAnimFrame, requestAnimFrame } from '../util/animate'; +import { cancelAnimFrame, requestAnimFrame } from '../util/animation'; import { cleanUpJsdomNode, getElementOffset, From 30c517261e2de3d474c8797a77574eecd7be6a0f Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Thu, 29 Dec 2022 21:45:07 +0100 Subject: [PATCH 106/108] it builds now --- src/util/animation/AnimationRegistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/animation/AnimationRegistry.ts b/src/util/animation/AnimationRegistry.ts index 4313181d05d..989a5ccccb8 100644 --- a/src/util/animation/AnimationRegistry.ts +++ b/src/util/animation/AnimationRegistry.ts @@ -1,5 +1,5 @@ import { fabric } from '../../../HEADER'; -import type { Canvas } from '../../canvas.class'; +import type { Canvas } from '../../canvas/canvas_events'; import type { FabricObject } from '../../shapes/Object/FabricObject'; import type { AnimationBase } from './AnimationBase'; From 3366fe633eaaceb74d1fc8539f53fdbd1d445943 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 30 Dec 2022 00:47:30 +0200 Subject: [PATCH 107/108] rename: Animation => ValueAnimation --- src/shapes/Object/AnimatableObject.ts | 10 ++++++---- src/util/animation/{Animation.ts => ValueAnimation.ts} | 2 +- src/util/animation/animate.ts | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) rename src/util/animation/{Animation.ts => ValueAnimation.ts} (90%) diff --git a/src/shapes/Object/AnimatableObject.ts b/src/shapes/Object/AnimatableObject.ts index 065d951288e..6590d666e4d 100644 --- a/src/shapes/Object/AnimatableObject.ts +++ b/src/shapes/Object/AnimatableObject.ts @@ -8,6 +8,8 @@ import { AnimationOptions, ColorAnimationOptions, } from '../../util/animation'; +import type { ColorAnimation } from '../../util/animation/ColorAnimation'; +import type { ValueAnimation } from '../../util/animation/ValueAnimation'; import { StackedObject } from './StackedObject'; type TAnimationOptions = T extends number @@ -37,7 +39,7 @@ export abstract class AnimatableObject< * @param {String|Object} property Property to animate (if string) or properties to animate (if object) * @param {Number|Object} value Value to animate property to (if string was given first) or options object * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} - * @return {(ColorAnimation | Animation)[]} animation context (or an array if passed multiple properties) + * @return {(ColorAnimation | ValueAnimation)[]} animation context (or an array if passed multiple properties) * * As object — multiple properties * @@ -58,16 +60,16 @@ export abstract class AnimatableObject< key: string, toValue: T, options?: Partial> - ): void; + ): (ColorAnimation | ValueAnimation)[]; animate( animatable: Record, options?: Partial> - ): void; + ): (ColorAnimation | ValueAnimation)[]; animate>( arg0: S, arg1: S extends string ? T : Partial>, arg2?: S extends string ? Partial> : never - ) { + ): (ColorAnimation | ValueAnimation)[] { const animatable = ( typeof arg0 === 'string' ? { [arg0]: arg1 } : arg0 ) as Record; diff --git a/src/util/animation/Animation.ts b/src/util/animation/ValueAnimation.ts similarity index 90% rename from src/util/animation/Animation.ts rename to src/util/animation/ValueAnimation.ts index c08dca0c61c..e0d329b49c5 100644 --- a/src/util/animation/Animation.ts +++ b/src/util/animation/ValueAnimation.ts @@ -1,7 +1,7 @@ import { AnimationBase } from './AnimationBase'; import { AnimationOptions } from './types'; -export class Animation extends AnimationBase { +export class ValueAnimation extends AnimationBase { constructor({ startValue = 0, endValue = 100, diff --git a/src/util/animation/animate.ts b/src/util/animation/animate.ts index 318c2cdabd4..cfcb17fd23e 100644 --- a/src/util/animation/animate.ts +++ b/src/util/animation/animate.ts @@ -1,4 +1,4 @@ -import { Animation } from './Animation'; +import { ValueAnimation } from './ValueAnimation'; import { ArrayAnimation } from './ArrayAnimation'; import { ColorAnimation } from './ColorAnimation'; import { @@ -45,14 +45,14 @@ const isArrayAnimation = ( */ export const animate = < T extends AnimationOptions | ArrayAnimationOptions, - R extends T extends ArrayAnimationOptions ? ArrayAnimation : Animation + R extends T extends ArrayAnimationOptions ? ArrayAnimation : ValueAnimation >( options: T ): R => { const animation = ( isArrayAnimation(options) ? new ArrayAnimation(options) - : new Animation(options) + : new ValueAnimation(options) ) as R; animation.start(); return animation; From f644fd2a7c6a9d60786e1fadacf060e8e6170c0c Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 30 Dec 2022 00:50:05 +0200 Subject: [PATCH 108/108] no arrow methods --- src/util/animation/AnimationBase.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/util/animation/AnimationBase.ts b/src/util/animation/AnimationBase.ts index f5733e5bc22..ec358a2ab99 100644 --- a/src/util/animation/AnimationBase.ts +++ b/src/util/animation/AnimationBase.ts @@ -73,6 +73,8 @@ export abstract class AnimationBase< target, }: Partial & TAnimationCallbacks> & Required, 'endValue'>>) { + this.tick = this.tick.bind(this); + this.duration = duration; this.delay = delay; this.easing = easing; @@ -122,7 +124,7 @@ export abstract class AnimationBase< } } - private tick: FrameRequestCallback = (t) => { + private tick(t: number) { const durationMs = (t || +new Date()) - this.startTime; const boundDurationMs = Math.min(durationMs, this.duration); this.durationRatio = boundDurationMs / this.duration; @@ -150,7 +152,7 @@ export abstract class AnimationBase< this._onChange(value, this.valueRatio, this.durationRatio); requestAnimFrame(this.tick); } - }; + } private register() { runningAnimations.push(this as unknown as AnimationBase);