transition.mixer
Motion blends numbers, colors and strings that share the same shape, such as
two paths with the same commands. transition.mixer (Motion 14.8) lets you
blend the rest yourself. The typical case is morphing SVG paths with different
numbers of points.
<motion.path
animate={{ d }}
transition={{ mixer: morph }}
/><motion.path
animate={{ d }}
transition={{ mixer: morph }}
/>What a mixer is
A mixer is a function that takes the value an animation starts from and the
value it goes to. It returns either a function that maps progress to a value,
or undefined to blend them as usual.
Progress runs from 0 to 1, and can go beyond them with springs and
overshooting easing.
mixer?: (from: any, to: any) => ((progress: number) => any) | undefinedmixer?: (from: any, to: any) => ((progress: number) => any) | undefinedIt works anywhere a transition does: motion.* components, animate() (including sequences) and useAnimate().
Morphing SVG paths
This mixer slides a square’s last corner out of a triangle’s. The default mixer can’t do it, as the two paths have different numbers of points:
<script>
import { motion } from '@humanspeak/svelte-motion'
const triangle = 'M10 10L90 10L90 90Z'
const square = 'M10 10L90 10L90 90L10 90Z'
const corner = () => (p) =>
p >= 1 ? square : `M10 10L90 10L90 90L${90 - 80 * p} 90Z`
</script>
<svg viewBox="0 0 100 100">
<motion.path
d={triangle}
animate={{ d: square }}
transition={{ duration: 1, mixer: corner }}
/>
</svg><script>
import { motion } from '@humanspeak/svelte-motion'
const triangle = 'M10 10L90 10L90 90Z'
const square = 'M10 10L90 10L90 90L10 90Z'
const corner = () => (p) =>
p >= 1 ? square : `M10 10L90 10L90 90L${90 - 80 * p} 90Z`
</script>
<svg viewBox="0 0 100 100">
<motion.path
d={triangle}
animate={{ d: square }}
transition={{ duration: 1, mixer: corner }}
/>
</svg>For arbitrary shapes, a library like flubber can be the mixer:
import { interpolate } from 'flubber'
const morph = (from: string, to: string) => interpolate(from, to)import { interpolate } from 'flubber'
const morph = (from: string, to: string) => interpolate(from, to)Path data is written to the d attribute. Setting d to undefined removes
the attribute.
Return undefined to blend as usual
A mixer can choose which values it handles. When it returns undefined, Motion
blends that pair the way it would without a mixer:
const mixer = (from, to) =>
typeof from === 'string' && from.startsWith('M') ? morph(from, to) : undefinedconst mixer = (from, to) =>
typeof from === 'string' && from.startsWith('M') ? morph(from, to) : undefinedNumbers still blend normally
Animations between numbers, like opacity or x, never call the mixer. They
run exactly as they would without it.
Where it runs
Animations whose mixer returns a function for at least one pair run on the main
thread, because the browser can’t call a JavaScript mixer. The rest, including
numbers and pairs where the mixer returns undefined, run as they would
without it, so they can still be hardware accelerated.
Animations that only run in the browser, like those from animateMini (Motion’s motion/mini), don’t use a mixer.
Tip: a mixer can be asked for the same pair more than once, so keep any costly setup inside the function it returns, for when that function is first called:
const morph = (from: string, to: string) => { let mix: ReturnType<typeof interpolate> | undefined return (progress: number) => { mix ??= interpolate(from, to) return mix(progress) } }const morph = (from: string, to: string) => { let mix: ReturnType<typeof interpolate> | undefined return (progress: number) => { mix ??= interpolate(from, to) return mix(progress) } }
API Reference
type Transition = {
// ...
mixer?: (from: any, to: any) => ((progress: number) => any) | undefined
}type Transition = {
// ...
mixer?: (from: any, to: any) => ((progress: number) => any) | undefined
}Based on Motion’s transition.mixer (Motion 14.8).