animare
Version:
Advanced animation library for modern JavaScript.
102 lines (85 loc) • 4.63 kB
text/typescript
import { Timing } from '../types.js';
import timeline from './timeline.js';
import type {
AnimationGroupOptions,
AnimationOptions,
AnimationOptionsParam,
Direction,
EaseFn,
GroupOnUpdateCallback,
GroupTimelineObject,
PartialExcept,
} from '../types.js';
/**
* Allows for a different input method where you can use an object with arrays of values for each property instead of an array of animation values.
*
* You can also use a single value as a default for all animations.
*
* ⚠️ **WARNING** ⚠️ All values must have the same length as the `to` value.
*
* 💁 Animation names become their index. For example, `myAnimation.updateValues([{ name: '0', duration: 5000 }])`.
*
* @param animation - An object containing the animation options.
* @param callback - A callback function that is called on each animation frame.
* @returns An object that contains a collection of useful methods and events.
*
* @example
* animare.group({ from: 50, to: [100, 200, 300], delay: [500, 600, 700] }, info => {
* console.log(info[0].value);
* });
*/
export default function group(animation: AnimationGroupOptions, callback: GroupOnUpdateCallback): GroupTimelineObject {
if (typeof animation.to === 'undefined') throw new Error('[group] The `to` value is required');
animation.to = typeof animation.to === 'number' ? [animation.to] : animation.to;
const length = animation.to.length;
const isNumber = (value: unknown): value is number => typeof value === 'number';
const isDirection = (value: unknown): value is Direction => typeof value === 'object' && !Array.isArray(value);
const isTiming = (value: unknown): value is Timing => typeof value === 'object' && !Array.isArray(value);
const isEase = (value: unknown): value is EaseFn => typeof value === 'function';
const fill = <T>(value: T) => new Array<T>(length).fill(value);
const prepared = {
to: animation.to,
from: isNumber(animation.from) ? fill(animation.from) : animation.from,
offset: isNumber(animation.offset) ? fill(animation.offset) : animation.offset,
delay: isNumber(animation.delay) ? fill(animation.delay) : animation.delay,
delayCount: isNumber(animation.delayCount) ? fill(animation.delayCount) : animation.delayCount,
playCount: isNumber(animation.playCount) ? fill(animation.playCount) : animation.playCount,
direction: isDirection(animation.direction) ? fill(animation.direction) : animation.direction,
timing: isTiming(animation.timing) ? fill(animation.timing) : animation.timing,
duration: isNumber(animation.duration) ? fill(animation.duration) : animation.duration,
ease: isEase(animation.ease) ? fill(animation.ease) : animation.ease,
};
const animationOptions: AnimationOptions[] = new Array(length);
for (let i = 0; i < length; i++) {
animationOptions[i] = {
name: i.toString(),
to: prepared.to[i],
from: Array.isArray(prepared.from) ? prepared.from[i] : prepared.from,
offset: Array.isArray(prepared.offset) ? prepared.offset[i] : prepared.offset,
delay: Array.isArray(prepared.delay) ? prepared.delay[i] : prepared.delay,
delayCount: Array.isArray(prepared.delayCount) ? prepared.delayCount[i] : prepared.delayCount,
playCount: Array.isArray(prepared.playCount) ? prepared.playCount[i] : prepared.playCount,
direction: Array.isArray(prepared.direction) ? prepared.direction[i] : prepared.direction,
timing: i === 0 ? Timing.FromStart : Array.isArray(prepared.timing) ? prepared.timing[i] : prepared.timing,
duration: Array.isArray(prepared.duration) ? prepared.duration[i] : prepared.duration,
ease: Array.isArray(prepared.ease) ? prepared.ease[i] : prepared.ease,
};
}
const timelineReturnObj = timeline(animationOptions as AnimationOptionsParam<`${number}`>, callback, {
autoPlay: animation.autoPlay,
timelinePlayCount: animation.timelinePlayCount,
timelineSpeed: animation.timelineSpeed,
});
const timelineUpdateValues = timelineReturnObj.updateValues;
const updateValues: GroupTimelineObject['updateValues'] = newValues => {
const mapped: PartialExcept<AnimationOptions<`${number}`>, 'name'>[] = [];
for (let i = 0; i < newValues.length; i++) {
if (typeof newValues[i].index !== 'number') throw new Error('[updateValues] Animation index is required.');
mapped[i] = { name: `${newValues[i].index}`, ...newValues[i] };
}
timelineUpdateValues(mapped);
};
const groupReturnObj = Object.assign(timelineReturnObj, { updateValues });
return groupReturnObj as GroupTimelineObject;
}
export type Group = typeof group;