Skip to content

Commit

Permalink
refactor(): BREAKING Animation removing byValue, removing fxStraigthe…
Browse files Browse the repository at this point in the history
…nand '+=' syntax (#8547)

Co-authored-by: ShaMan123 <shacharnen@gmail.com>
  • Loading branch information
asturur and ShaMan123 committed Jan 9, 2023
1 parent a7696d7 commit 8627774
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 321 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [next]

- refactor(Animation): BREAKING: Animation api reduction and semplification (byValue is removed, '+=' syntax is removed, callbacks fired 100%) [#8547](https://github.com/fabricjs/fabric.js/pull/8547)
- feat(PolyControl): modify the shape of a poly with control points [#8556](https://github.com/fabricjs/fabric.js/pull/8556)
- BREAKING: remove Object.stateful and Object.statefulCache [#8573](https://github.com/fabricjs/fabric.js/pull/8573)
- fix(IText): refactor clearing context top logic of itext to align with brush pattern, using the canvas rendering cycle in order to guard from edge cases #8560
Expand Down
7 changes: 3 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,12 @@ import {
} from './src/util/dom_misc';
import { isTransparent } from './src/util/misc/isTransparent';
import { mergeClipPaths } from './src/util/misc/mergeClipPaths';
import { animate, animateColor } from './src/util/animation/animate';
import * as ease from './src/util/animation/easing';
import {
animate,
animateColor,
ease,
requestAnimFrame,
cancelAnimFrame,
} from './src/util/animation';
} from './src/util/animation/AnimationFrameProvider';
import { classRegistry } from './src/util/class_registry';
import { removeFromArray } from './src/util/internals/removeFromArray';
import { getRandomInt } from './src/util/internals/getRandomInt';
Expand Down
5 changes: 4 additions & 1 deletion src/canvas/static_canvas.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import {
TToCanvasElementOptions,
TValidToObjectMethod,
} from '../typedefs';
import { cancelAnimFrame, requestAnimFrame } from '../util/animation';
import {
cancelAnimFrame,
requestAnimFrame,
} from '../util/animation/AnimationFrameProvider';
import {
cleanUpJsdomNode,
getElementOffset,
Expand Down
58 changes: 58 additions & 0 deletions src/parkinglot/straighten.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// @ts-nocheck
import { noop } from '../constants';
import { FabricObject } from '../shapes/Object/FabricObject';
import { TDegree } from '../typedefs';
import { animate } from '../util/animation/animate';

Object.assign(FabricObject.prototype, {
/**
* @private
* @return {Number} angle value
*/
_getAngleValueForStraighten(this: FabricObject) {
const angle = this.angle % 360;
if (angle > 0) {
return Math.round((angle - 1) / 90) * 90;
}
return Math.round(angle / 90) * 90;
},

/**
* Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer)
*/
straighten(this: FabricObject) {
this.rotate(this._getAngleValueForStraighten());
},

/**
* Same as {@link straighten} but with animation
* @param {Object} callbacks Object with callback functions
* @param {Function} [callbacks.onComplete] Invoked on completion
* @param {Function} [callbacks.onChange] Invoked on every step of animation
*/
fxStraighten(
this: FabricObject,
callbacks: {
onChange?(value: TDegree): any;
onComplete?(): any;
} = {}
) {
const onComplete = callbacks.onComplete || noop,
onChange = callbacks.onChange || noop;

return animate({
target: this,
startValue: this.angle,
endValue: this._getAngleValueForStraighten(),
duration: this.FX_DURATION,
onChange: (value: TDegree) => {
this.rotate(value);
onChange(value);
},
onComplete: () => {
this.setCoords();
onComplete();
},
});
},
});
163 changes: 27 additions & 136 deletions src/shapes/Object/AnimatableObject.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
import { TColorArg } from '../../color/color.class';
import { noop } from '../../constants';
import { ObjectEvents } from '../../EventTypeDefs';
import { TDegree } from '../../typedefs';
import {
animate,
animateColor,
AnimationOptions,
import { animate, animateColor } from '../../util/animation/animate';
import type {
ValueAnimationOptions,
ColorAnimationOptions,
} from '../../util/animation';
} from '../../util/animation/types';
import { ArrayAnimation } from '../../util/animation/ArrayAnimation';
import type { ColorAnimation } from '../../util/animation/ColorAnimation';
import type { ValueAnimation } from '../../util/animation/ValueAnimation';
import { StackedObject } from './StackedObject';

type TAnimationOptions<T extends number | TColorArg> = T extends number
? AnimationOptions
? ValueAnimationOptions
: ColorAnimationOptions;

export abstract class AnimatableObject<
EventSpec extends ObjectEvents = ObjectEvents
> extends StackedObject<EventSpec> {
/**
* Animation duration (in ms) for fx* methods
* @type Number
* @default
*/
FX_DURATION: number;

/**
* List of properties to consider for animating colors.
* @type String[]
*/
colorProperties: string[];

abstract rotate(deg: TDegree): void;

/**
* Animates object's properties
* @param {String|Object} property Property to animate (if string) or properties to animate (if object)
Expand All @@ -45,46 +34,13 @@ export abstract class AnimatableObject<
*
* object.animate({ left: ..., top: ... });
* 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<T extends number | TColorArg>(
key: string,
toValue: T,
options?: Partial<TAnimationOptions<T>>
): (ColorAnimation | ValueAnimation)[];
animate<T extends number | TColorArg>(
animatable: Record<string, T>,
options?: Partial<TAnimationOptions<T>>
): (ColorAnimation | ValueAnimation)[];
animate<T extends number | TColorArg, S extends string | Record<string, T>>(
arg0: S,
arg1: S extends string ? T : Partial<TAnimationOptions<T>>,
arg2?: S extends string ? Partial<TAnimationOptions<T>> : never
): (ColorAnimation | ValueAnimation)[] {
const animatable = (
typeof arg0 === 'string' ? { [arg0]: arg1 } : arg0
) as Record<string, T>;
const keys = Object.keys(animatable);
const options = (typeof arg0 === 'string' ? arg2 : arg1) as Partial<
TAnimationOptions<T>
>;
return keys.map((key, index) =>
this._animate(
key,
animatable[key],
index === keys.length - 1
? options
: { ...options, onChange: undefined, onComplete: undefined }
)
): (ColorAnimation | ValueAnimation | ArrayAnimation)[] {
return Object.entries(animatable).map(([key, endValue]) =>
this._animate(key, endValue, options)
);
}

Expand All @@ -96,118 +52,53 @@ export abstract class AnimatableObject<
*/
_animate<T extends number | TColorArg>(
key: string,
to: T,
endValue: T,
options: Partial<TAnimationOptions<T>> = {}
) {
const path = key.split('.');
const propIsColor = this.colorProperties.includes(path[path.length - 1]);
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);
}

const { easing, duration, abort, startValue, onChange, onComplete } =
options;
const animationOptions = {
target: this,
// path.reduce... is the current value in case start value isn't provided
startValue:
options.startValue ??
// backward compat
(options as any).from ??
currentValue,
endValue: to,
// `byValue` takes precedence over `endValue`
byValue:
options.byValue ??
// backward compat
(options as any).by,
easing: options.easing,
duration: options.duration,
abort: options.abort?.bind(this),
startValue ?? path.reduce((deep: any, key) => deep[key], this),
endValue,
easing,
duration,
abort: abort?.bind(this),
onChange: (
value: string | number,
valueRatio: number,
durationRatio: number
valueProgress: number,
durationProgress: number
) => {
path.reduce((deep: Record<string, any>, key, index) => {
if (index === path.length - 1) {
deep[key] = value;
}
return deep[key];
}, this);
options.onChange &&
onChange &&
// @ts-expect-error generic callback arg0 is wrong
options.onChange(value, valueRatio, durationRatio);
onChange(value, valueProgress, durationProgress);
},
onComplete: (
value: string | number,
valueRatio: number,
durationRatio: number
valueProgress: number,
durationProgress: number
) => {
this.setCoords();
options.onComplete &&
onComplete &&
// @ts-expect-error generic callback arg0 is wrong
options.onComplete(value, valueRatio, durationRatio);
onComplete(value, valueProgress, durationProgress);
},
} as TAnimationOptions<T>;

if (propIsColor) {
return animateColor(animationOptions as ColorAnimationOptions);
} else {
return animate(animationOptions as AnimationOptions);
return animate(animationOptions as ValueAnimationOptions);
}
}

/**
* @private
* @return {Number} angle value
*/
protected _getAngleValueForStraighten() {
const angle = this.angle % 360;
if (angle > 0) {
return Math.round((angle - 1) / 90) * 90;
}
return Math.round(angle / 90) * 90;
}

/**
* Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer)
*/
straighten() {
this.rotate(this._getAngleValueForStraighten());
}

/**
* Same as {@link straighten} but with animation
* @param {Object} callbacks Object with callback functions
* @param {Function} [callbacks.onComplete] Invoked on completion
* @param {Function} [callbacks.onChange] Invoked on every step of animation
*/
fxStraighten(
callbacks: {
onChange?(value: TDegree): any;
onComplete?(): any;
} = {}
) {
const onComplete = callbacks.onComplete || noop,
onChange = callbacks.onChange || noop;

return animate({
target: this,
startValue: this.angle,
endValue: this._getAngleValueForStraighten(),
duration: this.FX_DURATION,
onChange: (value: TDegree) => {
this.rotate(value);
onChange(value);
},
onComplete: () => {
this.setCoords();
onComplete();
},
});
}
}
3 changes: 1 addition & 2 deletions src/shapes/Object/Object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {
TCacheCanvasDimensions,
} from '../../typedefs';
import { classRegistry } from '../../util/class_registry';
import { runningAnimations } from '../../util/animation';
import { runningAnimations } from '../../util/animation/AnimationRegistry';
import { clone } from '../../util/lang_object';
import { capitalize } from '../../util/lang_string';
import { capValue } from '../../util/misc/capValue';
Expand Down Expand Up @@ -2082,7 +2082,6 @@ export const fabricObjectDefaultValues = {
clipPath: undefined,
inverted: false,
absolutePositioned: false,
FX_DURATION: 500,
};

Object.assign(FabricObject.prototype, fabricObjectDefaultValues);
Expand Down

0 comments on commit 8627774

Please sign in to comment.