diff --git a/js/animation.js b/js/animation.js new file mode 100644 index 0000000..3af22c9 --- /dev/null +++ b/js/animation.js @@ -0,0 +1,134 @@ +export const createAnimationController = (animations, component) => { + return { + animations, + start: () => { + Object.entries(animations).map(([name, animation]) => animation.run((state) => { + component.setState({ + ...component.state, + [name]: state, + }); + })); + }, + prepare: () => { + Object.entries(animations).map(([name, animation]) => animation.prepare((state) => { + component.setState({ + ...component.state, + [name]: state, + }); + })); + } + }; +} + +export const createAnimation = (stateA, stateB, delay) => ({ + stateA, + stateB, + delay, +}); + +const getAnimationState = (animation, stateName = 'stateA') => { + if (animation instanceof Array) { + return animation.map(a => getAnimationState(a, stateName)); + } + + return animation[stateName]; +}; + +const getMaxTransitionTime = (transition) => { + const re = /(\d+)ms/g; + const times = []; + let matches; + while ((matches = re.exec(transition)) != null) { + times.push(parseInt(matches[1], 10)); + } + return Math.max.apply(null, times); +}; + +const getAnimationDuration = (animation) => { + 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) => ( + getAnimationDuration(Object.values(animations)) +); + +export const createAnimationRunner = (animations, subscriber) => { + let state = Object.entries(animations).reduce((state, [name, animation]) => ({ + ...state, + [name]: getAnimationState(animation), + }), {}); + + const animationsDuration = getMaxAnimationsDuration(animations); + + const animate = (name, animation, stateName, getDelay) => { + if (animation instanceof Array) { + animation.map((a, index) => { + window.requestAnimationFrame(() => { + window.setTimeout(() => { + state = { + ...state, + [name]: [ + ...(state[name].slice(0, index)), + a[stateName], + ...(state[name].slice(index + 1, state[name].length)), + ], + }; + subscriber(); + }, getDelay(a)) + }); + }); + } else { + window.requestAnimationFrame(() => { + window.setTimeout(() => { + state = { + ...state, + [name]: animation[stateName], + }; + subscriber(); + }, getDelay(animation)) + }); + } + } + + return { + getState() { + return state; + }, + run() { + Object.entries(animations).forEach(([name, animation]) => { + animate(name, animation, 'stateB', a => a.delay) + }) + }, + runReverse() { + Object.entries(animations).reverse().forEach(([name, animation]) => { + animate(name, animation, 'stateA', a => animationsDuration - a.delay) + }) + }, + awaitAnimationComplete(callback) { + window.setTimeout(callback, animationsDuration); + }, + } +} + +// prepare(callback) { +// callback(stateA); +// }, +// run(callback) { +// window,requestAnimationFrame(() => { +// window.setTimeout(() => { +// callback(stateB); +// }, delay) +// }); +// }, diff --git a/js/components/Item.js b/js/components/Item.js index 262cf54..f88f720 100644 --- a/js/components/Item.js +++ b/js/components/Item.js @@ -2,7 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import Link from './Link'; -export default function Item({ item, noLeadingBorder = false, active = false}) { +export default function Item({ item, noLeadingBorder = false, active = false, style = {}}) { return (