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 }}
/>
mode · live running open
// transition-mixer
star: 10 points square: 4 points

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) | undefined
mixer?: (from: any, to: any) => ((progress: number) => any) | undefined

It 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) : undefined
const mixer = (from, to) =>
  typeof from === 'string' && from.startsWith('M') ? morph(from, to) : undefined

Numbers 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).