diff --git a/.gitignore b/.gitignore index bd99cf130..06671edc2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ node_modules /easing.js /motion.* /transition.js +/animate.js /scratch/ /coverage/ /coverage.lcov/ diff --git a/animate.mjs b/animate.mjs new file mode 100644 index 000000000..f22fabe40 --- /dev/null +++ b/animate.mjs @@ -0,0 +1,25 @@ +import { cubicOut } from './easing'; +import { is_function } from './internal'; + +export function flip(node, animation, params) { + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; + + const dx = animation.from.left - animation.to.left; + const dy = animation.from.top - animation.to.top; + + const d = Math.sqrt(dx * dx + dy * dy); + + const { + delay = 0, + duration = d => Math.sqrt(d) * 120, + easing = cubicOut + } = params; + + return { + delay, + duration: is_function(duration) ? duration(d) : duration, + easing, + css: (t, u) => `transform: ${transform} translate(${u * dx}px, ${u * dy}px);` + }; +} \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js index c523cb004..f7b2d07d4 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -88,7 +88,7 @@ export default [ }, // everything else - ...['index', 'store', 'easing', 'transition'].map(name => ({ + ...['index', 'store', 'easing', 'transition', 'animate'].map(name => ({ input: `${name}.mjs`, output: { file: `${name}.js`, diff --git a/transition.mjs b/transition.mjs index d8dd5ca34..5bbefcf8e 100644 --- a/transition.mjs +++ b/transition.mjs @@ -1,4 +1,5 @@ import { cubicOut, cubicInOut } from './easing'; +import { assign, is_function } from './internal'; export function fade(node, { delay = 0, @@ -118,4 +119,62 @@ export function draw(node, { easing, css: (t, u) => `stroke-dasharray: ${t * len} ${u * len}` }; +} + +export function crossfade({ fallback, ...defaults }) { + let to_receive = new Map(); + let to_send = new Map(); + + function crossfade(from, node, params) { + const { + delay = 0, + duration = d => Math.sqrt(d) * 30, + easing = cubicOut + } = assign(assign({}, defaults), params); + + const to = node.getBoundingClientRect(); + const dx = from.left - to.left; + const dy = from.top - to.top; + + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; + + return { + delay, + duration: is_function(duration) ? duration(d) : duration, + easing, + css: (t, u) => ` + opacity: ${t}; + transform: ${transform} translate(${u * dx}px,${u * dy}px); + ` + }; + } + + function transition(items, counterparts, intro) { + return (node, params) => { + items.set(params.key, { + rect: node.getBoundingClientRect() + }); + + return () => { + if (counterparts.has(params.key)) { + const { rect } = counterparts.get(params.key); + counterparts.delete(params.key); + + return crossfade(rect, node, params); + } + + // if the node is disappearing altogether + // (i.e. wasn't claimed by the other list) + // then we need to supply an outro + items.delete(params.key); + return fallback && fallback(node, params, intro); + }; + }; + } + + return [ + transition(to_send, to_receive, false), + transition(to_receive, to_send, true) + ]; } \ No newline at end of file