Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

Animate

Animate elements with Motion's animation props, using the mercury attachment.

Overview

mercury() is Mercury’s equivalent of a <motion.div>. It is a Svelte attachment that takes the props of a Motion component — initial, animate, exit, transition, variants, the while* gestures, drag and the rest — and applies them to the element it’s on:

<script lang="ts">
	import { mercury } from '@omicrxn/mercury';
</script>

<div {@attach mercury({ animate: { scale: 1.5, x: 50 } })} />

Under the hood each element gets a Motion VisualElement and Motion’s own animation state machine, the same engine motion/react runs on. Gesture states, variants and their propagation to children, transitionEnd, reduced motion and value velocity all behave as they do in Motion.

State-driven animation

Attachments re-run when the state they read changes. Each run updates the same Motion node — like a React re-render of a motion component — so derive animate from $state and Mercury animates whatever changed:

<script lang="ts">
	let isSilent = $state(false);
</script>

<div
	{@attach mercury({
		animate: { width: isSilent ? 148 : 128 },
		transition: { type: 'spring', bounce: 0.5 }
	})}
/>

Values that didn’t change don’t replay, gestures in progress carry on, and an interrupted animation continues from its current value and velocity.

initial

initial is the state the element renders before it animates to animate on mount:

<div
	{@attach mercury({
		initial: { opacity: 0, y: 16 },
		animate: { opacity: 1, y: 0 }
	})}
/>

Set initial: false to render the element directly in its animate state, so only later changes animate (keyframe arrays start at their last frame):

<!-- no wiggle on mount; wiggles on every isSilent change -->
<div
	{@attach mercury({
		initial: false,
		animate: {
			rotate: isSilent ? [0, -15, 5, -2, 0] : [0, 20, -15, 12.5, -10, 10, -7.5, 7.5, -5, 5, 0]
		}
	})}
/>

Without initial, the element animates from its current styles.

exit

exit is the state the element animates to before it’s removed. Like Motion’s exit, which needs <AnimatePresence>, it needs presence on the element so Svelte keeps it in the page until the exit has played:

{#if show}
	<div
		transition:presence
		{@attach mercury({
			initial: { opacity: 0, scale: 0.9 },
			animate: { opacity: 1, scale: 1 },
			exit: { opacity: 0, scale: 0.9 }
		})}
	/>
{/if}

Keyframes

Arrays animate through each value in turn. null as the first keyframe means “the current value”:

<div {@attach mercury({ animate: { y: [64, 0], scale: [null, 1.2, 1] } })} />

Position keyframes with times (one 0–1 value per keyframe) in the transition.

Transition

transition takes Motion’s transition options — duration, delay, ease, times, repeat, springs, inertia, and more:

<div
	{@attach mercury({
		animate: { scale: 1.5 },
		transition: { ease: 'circInOut', duration: 1, delay: 0.3 }
	})}
/>

Override it per value, with default for the rest:

<div
	{@attach mercury({
		animate: { x: 100, opacity: 1 },
		transition: { default: { type: 'spring' }, opacity: { duration: 0.2, ease: 'linear' } }
	})}
/>

A target can carry its own transition, which wins over the attachment’s: whileHover: { scale: 1.1, transition: { duration: 0.1 } }. transitionEnd sets values once the animation has finished:

<div {@attach mercury({ animate: { opacity: 0, transitionEnd: { display: 'none' } } })} />

Easing

Pass any Motion easing as ease:

  • Named easings: linear, easeIn, easeOut, easeInOut, circIn, circOut, circInOut, backIn, backOut, backInOut, anticipate
  • Cubic bezier: ease: [0.39, 0.24, 0.3, 1]
  • Functions: ease: (progress) => progress * progress, or Motion’s steps(4), cubicBezier()…
  • Per keyframe: an array of easings, one between each pair of keyframes

Repetition

  • repeat: number of repetitions (Infinity supported).
  • repeatType: 'loop', 'reverse' or 'mirror'.
  • repeatDelay: delay in seconds between repetitions.
<div
	{@attach mercury({
		animate: { backgroundColor: ['#ff0088', '#0d63f8'] },
		transition: { duration: 2, repeat: Infinity, repeatType: 'reverse', ease: 'linear' }
	})}
></div>

Springs

Set type: 'spring'. Like Motion, springs are configured in one of two ways:

  • Duration-based — duration (or visualDuration, the time to visually reach the target) and bounce (0 to 1, default 0.25).
  • Physics-based — stiffness, damping and mass, plus velocity, restSpeed and restDelta. These pick up the velocity of an interrupted animation or gesture.
{@attach mercury({
	animate: { rotate: 180 },
	transition: { type: 'spring', stiffness: 150, damping: 20 }
})}

Stagger

Give each element its own delay:

{#each { length: 3 }, i}
	<div
		{@attach mercury({
			animate: { opacity: 1, y: [64, 0] },
			transition: { delay: i * 0.05 }
		})}
	/>
{/each}

To stagger children from a parent — and in reverse on the way out — use variants with staggerChildren.

Animatable values

Anything Motion animates: transforms as independent values (x, y, scale, rotate, skewX…), colors, CSS variables ('--progress'), filter, clipPath, boxShadow, width / height (including to and from 'auto'), and SVG attributes such as pathLength, pathOffset, cx or d on SVG elements:

<svg viewBox="0 0 100 100">
	<path
		d="M10 50 L90 50"
		{@attach mercury({ initial: { pathLength: 0 }, animate: { pathLength: 1 } })}
	/>
</svg>

See Motion’s docs for the full list.

Events

These mirror Motion’s component events:

  • onAnimationStart(definition) — an animation started.
  • onAnimationComplete(definition) — it finished. definition is the target or variant label.
  • onUpdate(latest) — every frame, with the latest values.
{@attach mercury({
	animate: { x: 100 },
	onAnimationStart: () => console.log('started'),
	onAnimationComplete: (definition) => console.log('done', definition)
})}

Playback controls

For imperative control, pass instance. It’s called with the playback controls of the animations each change starts — play(), pause(), stop(), cancel(), a finished promise, a completed flag, and Motion’s underlying controls:

<script lang="ts">
	import { mercury, type AnimationInstance } from '@omicrxn/mercury';
	let animation = $state<AnimationInstance>();
</script>

<div
	{@attach mercury({
		animate: { scale: 1.5 },
		transition: { duration: 1 },
		instance: (i) => (animation = i)
	})}
></div>

<button onclick={() => animation?.play()}>Play</button>
<button onclick={() => animation?.pause()}>Pause</button>

For timelines and sequences, use Motion’s animate() directly — on an element Mercury animates it shares the same motion values.

Was this page helpful?