animare
Version:
Advanced animation library for modern JavaScript.
699 lines (585 loc) • 21.4 kB
text/typescript
import type Animation from './animation';
export enum Direction {
/** Play the animation in the normal direction. */
Forward = 'forward',
/** Play the animation in reverse direction. */
Reverse = 'reverse',
/** Alternate the animation direction on each iteration, starting with normal. */
Alternate = 'alternate',
/** Alternate the animation direction on each iteration, starting with reverse. */
AlternateReverse = 'alternate-reverse',
}
export enum Timing {
/** Play the animation after the previous one finishes. */
AfterPrevious = 'after-previous',
/** Play the animation simultaneously with the previous one. */
WithPrevious = 'with-previous',
/** Play the animation from the start of the timeline. */
FromStart = 'from-start',
}
export enum Event {
/** Event triggered when the animation starts playing. */
Play = 'play',
/** Event triggered when the animation resumes. */
Resume = 'resume',
/** Event triggered when the animation is paused. */
Pause = 'pause',
/** Event triggered when the animation stops. */
Stop = 'stop',
/** Event triggered when the animation completes. */
Complete = 'complete',
/** Event triggered when the animation is repeated. */
Repeat = 'repeat',
}
export enum ScrollAxis {
/** Track the element scroll vertically on the `y` axis. */
Vertical = 'y',
/** Track the element scroll horizontally on the `x` axis. */
Horizontal = 'x',
}
export enum ScrollElementEdge {
Top = 'top',
Bottom = 'bottom',
Left = 'left',
Right = 'right',
}
/**
* **Note:** This function does nothing, used only for type checking.
*/
export function createAnimations<Name extends string>(animations: AnimationOptionsParam<Name>) {
return animations;
}
// utils
export type RemoveFunction<T> = T extends (index: number) => unknown ? never : T;
type RemoveFunctionType<T> = {
[K in keyof T]: K extends 'ease'
? Extract<T[K], unknown[]> extends never
? T[K]
: Exclude<T[K], (index: number) => unknown>
: T[K] extends undefined
? T[K]
: RemoveFunction<T[K]>;
};
/** Extends the types of properties in the given type `T` to allow arrays and functions, except for the 'to' property which only allows arrays. */
type AllowArray<T> = {
[K in keyof T]: K extends 'ease'
? T[K] | Exclude<T[K], undefined>[]
: T[K] | Exclude<T[K], undefined | ((i: number) => unknown)>[];
};
export type PartialExcept<T, K extends keyof T> = Partial<T> & Pick<T, K>;
// ...
export type EventCallback = () => void;
export type EventUnsubscribe = () => boolean;
export type EaseFn = (t: number) => number;
export type PercentageString = `${number}%`;
export type AnimationOptions<Name extends string = string> = {
/**
* The name of the animation, used to identify the animation in the timeline.
* **Required**
*/
readonly name: Name;
/**
* The ending value of the animation.
* **Required**
*/
to: number;
/**
* The starting value of the animation.
* @default 0
*/
from?: number | ((index: number) => number);
/**
* The duration of the animation in milliseconds.
* @default 350
*/
duration?: number | ((index: number) => number);
/**
* This property specifies the delay before the animation starts, in milliseconds.
* @default 0
*/
delay?: number | ((index: number) => number);
/**
* Offsets the animation by a specific value in milliseconds.
* a positive value will act like a delay, while a negative value will play the animation earlier.
* @default 0
*/
offset?: number | ((index: number) => number);
/**
* The number of times the delay should be applied on each animation play.
*
* For example, with `delayCount: 1` and `playCount: 4`, the delay will be applied only once on the first play.
*
* A value higher than `playCount` will be ignored.
* @default playCount
*/
delayCount?: number | ((index: number) => number);
/**
* The number of times the animation should play.
* A value of `0` means this animation will be ignored.
* @default 1
*/
playCount?: number | ((index: number) => number);
/**
* The direction in which the animation should play.
* @default Direction.Forward
*/
direction?: Direction | ((index: number) => Direction);
/**
* The position of the animation in the timeline, determining when it should start relative to the timeline.
* @default Timing.AfterPrevious
*/
timing?: Timing | ((index: number) => Timing);
/**
* The easing function for the animation, defining the rate of change of the animated value over time.
* @default ease.linear
*/
ease?: EaseFn;
};
export type AnimationOptionsWithoutFn<Name extends string = string> = RemoveFunctionType<AnimationOptions<Name>>;
export type FirstAnimationOptions<Name extends string = string> = Omit<AnimationOptions<Name>, 'timing'>;
export type AnimationOptionsParam<Name extends string = string> = [FirstAnimationOptions<Name>, ...AnimationOptions<Name>[]];
export type TimelineOptions = {
/**
* The number of times the timeline should play.
*
* Use `-1` for infinite plays.
*/
timelinePlayCount?: number;
/**
* The speed of the timeline, the normal speed is `1`.
* - E.g. `2` means the animation plays twice as fast.
* - E.g. `0.5` means the animation plays half as fast.
* @default 1
*/
timelineSpeed?: number;
/** Indicates whether the animation should start playing automatically. */
autoPlay?: boolean;
};
export type TimelineGlobalOptions = Omit<AnimationOptionsWithoutFn, 'name' | 'to'> & TimelineOptions;
export type AnimationPreparedOptions = Required<AnimationOptionsWithoutFn>;
export type AnimationInfo<Name extends string = string> = {
/** The name of the animation. */
name: Name;
/** The index of the animation in the timeline. */
index: number;
/** The animated value over time. */
value: number;
/** The progress of the animation, excluding any delays. */
progress: number;
/** The overall progress of the animation, including delays and repeats. */
overallProgress: number;
/** The elapsed time of the animation in milliseconds. */
elapsedTime: number;
/** The current delay count, starting from `0` (not started) up to the specified delay count. */
delayCount: number;
/** The current play count, starting from `0` (not started) up to the specified play count. */
playCount: number;
/** Indicates whether the animation has finished. */
isFinished: boolean;
/** Indicates whether the animation is currently playing, not considering any delays. */
isPlaying: boolean;
/**
* Checks if a given progress value is within the current progress.
* @param progress - The progress value to check, between `0` and `1`.
* @param tolerance - The allowable tolerance for the check. Default is `0.001`.
* @returns `true` if the progress is within the specified tolerance, otherwise `false`.
*/
isProgressAt(progress: number, tolerance?: number): boolean;
/**
* Checks if a given time value is within the current elapsed time.
* @param time - The time value to check, in milliseconds.
* @param tolerance - The allowable tolerance for the check, in milliseconds. Default is `5` ms.
* @returns `true` if the time is within the specified tolerance, otherwise `false`.
*/
isTimeAt(time: number, tolerance?: number): boolean;
};
export type CallbackInfo<Name extends string = string> = {
[key: number]: AnimationInfo<Name>;
length: number;
} & { [key in Name]: AnimationInfo<Name> };
export type TimelineInfo = {
/** The current elapsed time in milliseconds. */
elapsedTime: number;
/** The total duration of the timeline in milliseconds. */
duration: number;
/**
* The speed of the timeline, the normal speed is `1`.
* - E.g. `2` means the animation plays twice as fast.
* - E.g. `0.5` means the animation plays half as fast.
* @default 1
*/
speed: number;
/** The current progress of the timeline, typically a value between `0` and `1`. */
progress: number;
/** Indicates whether the timeline is currently paused. */
isPaused: boolean;
/** Indicates whether the timeline is currently playing. */
isPlaying: boolean;
/** Indicates whether the timeline has finished. */
isFinished: boolean;
/**
* - Indicates if this is the first update callback call.
* - It does not indicate if the animation is started from `0` progress.
* - Useful for initialization purposes.
*/
isFirstFrame: boolean;
/** The current play count of the timeline. */
playCount: number;
/** The frames per second. */
fps: number;
/**
* Checks if a given progress value is within the current progress.
* @param progress - The progress value to check, between `0` and `1`.
* @param tolerance - The allowable tolerance for the check. Default is `0.001`.
* @returns `true` if the progress is within the specified tolerance, otherwise `false`.
*/
isProgressAt(progress: number, tolerance?: number): boolean;
/**
* Checks if a given time value is within the current elapsed time.
* @param time - The time value to check, in milliseconds.
* @param tolerance - The allowable tolerance for the check, in milliseconds. Default is `5` ms.
* @returns `true` if the time is within the specified tolerance, otherwise `false`.
*/
isTimeAt(time: number, tolerance?: number): boolean;
};
export type PrivateTimelineInfo = {
/** The start time of the animation in milliseconds. */
__startTime: number;
/** The time of the last frame in milliseconds. */
__lastFrameTime: number;
/** The starting progress of the timeline. */
__startProgress: number;
/** The list of animations included in the timeline. */
__animations: Animation[];
/** The request animation id. */
__requestAnimationId: number | null;
/** The time when the timeline was paused in milliseconds. */
__pauseTime: number;
};
export type OnUpdateCallback<T extends AnimationOptions[]> = (
info: CallbackInfo<T[number]['name']>,
timelineInfo: TimelineInfo,
) => void;
export type TimelineObject<Name extends string = string> = {
/**
* Retrieves information about the timeline.
*
* Returns the same object that is passed to the `onUpdate` callback.
*
* ⚠️ **Warning** ⚠️ This object values will be updated on every frame update if the timeline is playing.
*
* @example
* timelineInfo.isPaused;
*/
timelineInfo: TimelineInfo;
/**
* Retrieves information about all animations.
*
* Returns the same object that is passed to the `onUpdate` callback.
*
* ⚠️ **Warning** ⚠️ This object values will be updated on every frame update if the timeline is playing.
*
* @example
* animations[0].progress; // Accessing the first animation by index
* animations['myFirstAnimation'].value; // Accessing the first animation by name
*/
animationsInfo: CallbackInfo<Name>;
/**
* Updates animations values.
*
* The `name` property is **required** to target a specific animation for updating.
*
* ⚠️ **Warning** ⚠️ This method will throw an error if the some of the values are invalid.
*
* **Note:** Updating the animation values while the timeline is playing might result in flickering.
*
* @param newValues - An array of objects containing the new values.
*
* @example
* updateValues([{
* name: 'myAnimation', // Animation name to update
* duration: 500
* }]);
*/
updateValues: (newValues: PartialExcept<AnimationOptions<Name>, 'name'>[]) => void;
/**
* Updates the options of the timeline.
*
* ⚠️ **Warning** ⚠️ This method will throw an error if the some of the values are invalid.
* @param newOptions - An object containing the new options.
*
* @example
* updateTimelineOptions({
* timelineSpeed: 0.5
*})
*/
updateTimelineOptions: (newOptions: Partial<TimelineOptions>) => void;
/**
* Plays the timeline.
*
* - Accepts an optional `startFrom` parameter that can be a `time` in milliseconds or a percentage value.
* - Optionally, you can specify a `playCount` to seek to a specific repeat count.
*
* @param startFrom - The point to start from, specified as a `time` in milliseconds or a percentage value.
* @param playCount - The repeat count to seek to before playing.
*
* @example
* play(); // Play from the start
* play(500); // Play from 500 milliseconds
* play('50%'); // Play from 50% of the timeline duration
* play('50%', 2); // Play from 50% of the timeline duration on the second repeat
*/
play: (startFrom?: number | PercentageString, playCount?: number) => void;
/**
* Plays only one frame of the timeline.
*
* - If the timeline is already playing, this method does nothing.
* - It will not trigger `play` events.
*
* @example
* seek("50%");
* playOneFrame();
*/
playOneFrame: () => void;
/**
* Resumes the timeline from a paused state.
*
* - If the timeline is not paused, it will start playing from the beginning.
* - If the timeline is already playing, this method does nothing.
*/
resume: () => void;
/**
* Pauses the timeline.
*
* - If the timeline is already paused, it remains paused.
* - If the timeline is not playing, it will not be paused.
*/
pause: () => void;
/**
* Stops the timeline from playing.
*
* - If no parameters are passed, the animation will skip to the end and stop.
* - You can pass parameters to stop at a specific point in the timeline.
* - If the timeline is not currently playing, it plays only one frame at the specified stop point.
*
* @param stopAt - The point to stop at, specified as a `time` in milliseconds or a percentage string.
* @param playCount - The repeat count to stop at.
* @example
* stop(); // Skip to the end and stop
* stop("50%"); // Skip to 50% of the timeline and stop
*/
stop: (stopAt?: number | PercentageString, playCount?: number) => void;
/**
* Seeks the timeline to a specified `time` or `percentage` value.
*
* - If the timeline is not playing, it will not start playing.
*
* @param seekTo - The point to seek to, specified as a `time` in milliseconds or a percentage value.
* @param playCount - The repeat count to seek to before playing.
*
* @example
* seek(500); // Seek to 500 milliseconds
* seek('50%'); // Seek to 50% of the timeline duration
* seek('50%', 2); // Seek to 50% of the timeline duration on the second repeat
*/
seek: (seekTo: number | PercentageString, playCount?: number) => void;
/**
* Attaches an event listener to the timeline.
*
* @param event - The event to listen for.
* @param callback - The callback function to be executed when the event is triggered.
* @returns A function to unsubscribe the event listener.
*
* @example
* const unsubscribe = on(Event.Play, () => {
* // do something
* });
*
* unsubscribe(); // To remove the event listener
*/
on: (event: Event, callback: EventCallback) => EventUnsubscribe;
/**
* Attaches an event listener to the timeline that will be triggered only once.
*
* @param event - The event to listen for.
* @param callback - The callback function to be executed when the event is triggered.
* @returns A function to unsubscribe the event listener.
*
* @example
* const unsubscribe = once(Event.Play, () => {
* // do something
* });
*
* unsubscribe(); // To remove the event listener
*/
once: (event: Event, callback: EventCallback) => EventUnsubscribe;
/**
* Waits until the timeline completes.
* @example
* await onCompleteAsync();
*/
onCompleteAsync: () => Promise<unknown> | undefined;
/**
* Waits until the timeline starts playing.
* @example
* await onPlayAsync();
*/
onPlayAsync: () => Promise<unknown> | undefined;
/**
* Waits until the timeline resumes.
* @example
* await onResumeAsync();
*/
onResumeAsync: () => Promise<unknown> | undefined;
/**
* Waits until the timeline pauses.
* @example
* await onPauseAsync();
*/
onPauseAsync: () => Promise<unknown> | undefined;
/**
* Waits until the timeline stops.
* @example
* await onStopAsync();
*/
onStopAsync: () => Promise<unknown> | undefined;
/**
* Waits until the timeline repeats.
* @example
* await onRepeatAsync();
*/
onRepeatAsync: () => Promise<unknown> | undefined;
/** Removes all attached event listeners. */
clearEvents: () => void;
};
export type SingleAnimationOptions = Omit<AnimationOptionsWithoutFn, 'name' | 'timing'> & { autoPlay?: boolean };
export type SingleAnimationOptionsWithoutFn = RemoveFunctionType<SingleAnimationOptions>;
export type SingleOnUpdateCallback = (info: Omit<AnimationInfo, 'name' | 'index'>) => void;
export type SingleObject = Omit<TimelineObject, 'updateValues' | 'animationsInfo' | 'updateTimelineOptions'> & {
/**
* Updates the animation values.
*
* ⚠️ **Warning** ⚠️ This method will throw an error if the some of the values are invalid.
*
* **Note:** Updating the animation values while the timeline is playing might result in flickering.
*
* @param newValues - An object containing the new values.
*
* @example
* updateValues({ duration: 500 });
*/
updateValues: (newValues: Partial<AnimationOptionsWithoutFn>) => void;
/**
* Retrieves information about the animation.
*
* Returns the same object that is passed to the `onUpdate` callback.
*
* ⚠️ **Warning** ⚠️ This object values will be updated on every frame update if the timeline is playing.
*
* @example
* animations.progress; // Accessing the animation progress
* animations.value; // Accessing the animation value
*/
animationsInfo: AnimationInfo;
};
export type AnimationGroupOptions = Omit<AllowArray<AnimationOptions>, 'name'> & TimelineOptions;
export type GroupOnUpdateCallback = (info: CallbackInfo<`${number}`>, timelineInfo: TimelineInfo) => void;
export type GroupTimelineObject = Omit<TimelineObject<`${number}`>, 'updateValues'> & {
/**
* Updates the animation values.
*
* ⚠️ **Warning** ⚠️ This method will throw an error if the some of the values are invalid.
*
* **Note:** Updating the animation values while the timeline is playing might result in flickering.
*
* @param newValues - An array of objects containing the new values.
*
* @example
* updateValues([{ index: 0, duration: 500 }]);
*/
updateValues: (newValues: (Partial<Omit<AnimationOptions<`${number}`>, 'name'>> & { index: number })[]) => void;
};
export type AutoPauseOptions = {
root?: Element | Document | null;
rootMargin?: string;
threshold?: number | number[];
forcePlay?: boolean;
onVisibilityChange?: (isVisible: boolean) => void;
};
export type ScrollAnimationOptions<Name extends string = string> = {
/**
* The returned animation object.
*
* **Required**
*/
timeline: TimelineObject<Name> | GroupTimelineObject | SingleObject;
/**
* The HTML element to track when entering and exiting the viewport.
*
* **Required**
*/
element: HTMLElement;
/**
* The root element that has the scrollable area.
*
* @default document.documentElement
*/
root?: HTMLElement;
/**
* The scroll axis to track.
*
* @default ScrollAxis.Vertical
*/
axis?: ScrollAxis;
/**
* Start the animation when the element edge enters the scroll area.
*
* @default ScrollElementEdge.Top
*/
start?: ScrollElementEdge;
/**
* End the animation when the element edge exits the scroll area.
*
* @default ScrollElementEdge.Bottom
*/
end?: ScrollElementEdge;
/**
* The offset to start the animation.
*
* @default 0
*/
startOffset?: number;
/**
* The offset to end the animation.
*
* @default 0
*/
endOffset?: number;
};
export type Vec1Array = [number];
export type Vec2Array = [number, number];
export type Vec3Array = [number, number, number];
export type Vec4Array = [number, number, number, number];
export type Vec1Object = {
x: number;
};
export type Vec2Object = {
x: number;
y: number;
};
export type Vec3Object = {
x: number;
y: number;
z: number;
};
export type Vec4Object = {
x: number;
y: number;
z: number;
w: number;
};
export type Vec1 = Vec1Array | Vec1Object | number;
export type Vec2 = Vec2Array | Vec2Object;
export type Vec3 = Vec3Array | Vec3Object;
export type Vec4 = Vec4Array | Vec4Object;
export type Vec = Vec1 | Vec2 | Vec3 | Vec4;