Files
TechRadarAJR/src/animation.ts
2021-06-16 11:53:05 +02:00

136 lines
3.2 KiB
TypeScript

import React from "react";
export interface AnimationConfig {
[k: string]: Animation | Animation[];
}
export type Animations = {
[k: string]: Animation[];
};
export type AnimationStates = {
[k: string]: React.CSSProperties[];
};
export type Animation = {
stateA: React.CSSProperties;
stateB: React.CSSProperties;
delay: number;
run?(callback: (state: any) => any): any; // todo fix
prepare?(callback: (state: any) => any): any; // todo fix
};
export type AnimationRunner = {
getState(): AnimationStates;
run(): any;
awaitAnimationComplete(callback: () => void): any;
};
export const createAnimation = (
stateA: React.CSSProperties,
stateB: React.CSSProperties,
delay: number
): Animation => ({
stateA,
stateB,
delay,
});
const getAnimationStates = (
animations: Animation[],
stateName: "stateA" | "stateB" = "stateA"
): React.CSSProperties[] => {
return animations.map((animation) => animation[stateName]);
};
const getMaxTransitionTime = (transition: string) => {
const re = /(\d+)ms/g;
const times: number[] = [];
let matches;
while ((matches = re.exec(transition)) != null) {
times.push(parseInt(matches[1], 10));
}
return Math.max(...times);
};
const getAnimationDuration = (animation: Animation | Animation[]): number => {
if (animation instanceof Array) {
return animation.reduce((maxDuration, a) => {
const duration = getAnimationDuration(a);
if (duration > maxDuration) {
return duration;
}
return maxDuration;
}, 0);
}
const state = animation.stateB;
const maxTransition = state.transition
? getMaxTransitionTime(state.transition)
: 0;
return maxTransition + animation.delay;
};
const getMaxAnimationsDuration = (animations: Animations) =>
Math.max(
...Object.values(animations).map((animations) =>
getAnimationDuration(Object.values(animations))
)
);
export const createAnimationRunner = (
animationsIn: AnimationConfig,
subscriber: () => void = () => {}
): AnimationRunner => {
const animations = Object.entries(animationsIn).reduce(
(state, [name, animation]) => ({
...state,
[name]:
animation instanceof Array ? animation : ([animation] as Animation[]),
}),
{} as Animations
);
let state = Object.entries(animations).reduce(
(state, [name, animation]) => ({
...state,
[name]: getAnimationStates(animation),
}),
{} as AnimationStates
);
const animationsDuration = getMaxAnimationsDuration(animations);
const animate = (name: string, animation: Animation[]) => {
animation.forEach((a, index) => {
window.requestAnimationFrame(() => {
window.setTimeout(() => {
state = {
...state,
[name]: [
...state[name]?.slice(0, index),
a.stateB,
...state[name]?.slice(index + 1, state[name].length),
],
};
subscriber();
}, a.delay);
});
});
};
return {
getState() {
return state;
},
run() {
Object.entries(animations).forEach(([name, animation]) => {
animate(name, animation);
});
},
awaitAnimationComplete(callback) {
window.setTimeout(callback, animationsDuration);
},
};
};