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’ssteps(4),cubicBezier()… - Per keyframe: an array of easings, one between each pair of keyframes
Repetition
repeat: number of repetitions (Infinitysupported).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(orvisualDuration, the time to visually reach the target) andbounce(0to1, default0.25). - Physics-based —
stiffness,dampingandmass, plusvelocity,restSpeedandrestDelta. 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.definitionis 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.