Mouse Movement
Animate based on cursor position with axis and distance modes.
.onMouseMove() maps the cursor position to animation progress in real time — no play/pause, just a continuous value between 0 and 1 driven by the mouse. Use it for parallax depth layers, magnetic follow effects, and interactive tilt animations.
Basic Usage
Chain .onMouseMove() after defining from and to states. Both endpoints are typically specified because neither represents the element’s natural CSS position.
import { Motion } from "@motion.page/sdk";
Motion("parallax", ".layer", {
from: { x: -20, y: -20 },
to: { x: 20, y: 20 },
}).onMouseMove({ type: "axis", smooth: 0.1 }); The timeline progress follows the mouse: left edge = 0, right edge = 1 (for x); top edge = 0, bottom edge = 1 (for y).
Options
.onMouseMove() accepts an optional MouseMoveConfig object:
| Option | Type | Default | Description |
|---|---|---|---|
type | 'axis' | 'distance' | 'distance' | How mouse position maps to progress. See Type Modes. |
target | string | Element | viewport | The element whose bounds define the movement area. Accepts a CSS selector or Element reference. |
smooth | number | 0.1 | Lag applied to progress tracking. 0 = instant follow, 1 = maximum lag. |
startProgress | number | 0.5 | Animation progress when the mouse enters the target area. |
leaveProgress | number | 0.5 | Animation progress to return to when the mouse leaves. |
each | boolean | false | When true, every element matching target gets its own independent tracking zone. Animated elements must be children of the target. |
Type Modes — Axis vs Distance
type: 'axis'
The axis mode maps mouse position across the full span of the viewport (or target element). The start is at one edge and the end is at the opposite edge.
- Mouse at left edge → progress
0→fromstate - Mouse at right edge → progress
1→tostate - Same applies vertically: top →
0, bottom →1
This makes axis mode ideal for parallax depth layers and directional follow effects.
Motion("tilt", ".card", {
from: { rotateY: -15, rotateX: 10 },
to: { rotateY: 15, rotateX: -10 },
}).onMouseMove({ type: "axis", smooth: 0.08 }); type: 'distance'
The distance mode animates based on how far the cursor is from the center of the viewport (or target element). The animation progress reflects distance — not direction — from the center point.
- Mouse at center → progress equals
startProgress(default0.5) - Mouse at edge → full or zero progress based on distance
This is well-suited for radial proximity effects such as glow halos, magnetic pull, or attention-drawing reveals.
Motion("glow", ".glow-ring", {
from: { opacity: 0, scale: 0.8 },
to: { opacity: 1, scale: 1.2 },
}).onMouseMove({ type: "distance", smooth: 0.12 }); Per-Animation Axis Binding
When using type: 'axis', you can lock an individual animation node to respond only to a single axis by setting axis on the AnimationConfig — not on the trigger config. This is separate from the type option.
// Horizontal axis only — ignores vertical mouse movement
Motion("follow-x", ".marker", {
from: { x: -30 },
to: { x: 30 },
axis: "x",
}).onMouseMove({ type: "axis", smooth: 0.08, startProgress: 0, leaveProgress: 0 });
// Vertical axis only
Motion("follow-y", ".marker", {
from: { y: -20 },
to: { y: 20 },
axis: "y",
}).onMouseMove({ type: "axis", smooth: 0.08 }); axis: 'x' makes the node track horizontal mouse position only; axis: 'y' tracks vertical only. Omit axis to track both.
Movement Area
By default, the entire viewport is the movement area — mouse at the left edge of the screen is progress 0, right edge is progress 1. Set target to restrict tracking to a specific element’s bounds.
// Only react to mouse within the .hero section
Motion("title", ".hero-title", {
from: { x: -12, y: -8 },
to: { x: 12, y: 8 },
}).onMouseMove({ type: "axis", smooth: 0.1, target: ".hero" }); When the cursor leaves the target element, the animation eases to leaveProgress.
Progress at Rest and on Leave
startProgress sets the animation progress at the moment the mouse enters the target area. The default 0.5 places the element at the midpoint between from and to on entry — visually centered.
leaveProgress is where the animation settles after the cursor leaves. The default 0.5 keeps the element at the midpoint.
Set both to 0 to start elements at their from state and return there on leave:
// Elements rest at `from` position; animate toward `to` as mouse approaches
Motion("magnetic", ".icon", {
from: { x: 0, y: 0 },
to: { x: 16, y: 16 },
}).onMouseMove({ type: "distance", smooth: 0.1, startProgress: 0, leaveProgress: 0 }); Set both to 0.5 (the default) for a centered, symmetrical parallax where elements begin and end in the middle of their range.
Smooth Following
smooth controls how quickly the animation catches up to the cursor. Think of it as inertia.
| Value | Effect |
|---|---|
0 | Instant — animation snaps to cursor with no lag |
0.05–0.1 | Subtle easing, natural parallax feel (recommended) |
0.15–0.2 | Noticeable lag, dreamy or heavy effect |
1 | Maximum lag — very slow to follow |
Use different smooth values across depth layers to create convincing parallax — farther layers should feel slower:
Motion("bg", ".layer-bg", { from: { x: -10, y: -10 }, to: { x: 10, y: 10 } })
.onMouseMove({ type: "axis", smooth: 0.15 }); // slow background
Motion("mid", ".layer-mid", { from: { x: -25, y: -25 }, to: { x: 25, y: 25 } })
.onMouseMove({ type: "axis", smooth: 0.1 }); // medium midground
Motion("fg", ".layer-fg", { from: { x: -50, y: -50 }, to: { x: 50, y: 50 } })
.onMouseMove({ type: "axis", smooth: 0.06 }); // snappy foreground Per-Element Instances
Set each: true together with a class-based target to give every matching element its own independent tracking zone. Animated elements must live inside the target container — the target acts as both the movement boundary and the wrapper.
// Each .card gets its own mouse zone; the tilt tracks the cursor within that card
Motion("card-tilt", ".card", {
from: { rotateY: -8, rotateX: 4 },
to: { rotateY: 8, rotateX: -4 },
}).onMouseMove({ type: "axis", smooth: 0.08, target: ".card", each: true }); Without each: true, all matching elements share a single tracking zone (the viewport, or the single target element).
Common Patterns
Parallax Depth Layers
Multiple timelines at the same type: 'axis' with progressively higher smooth values and wider offset ranges create convincing depth:
Motion("bg", ".layer-bg", { from: { x: -8, y: -6 }, to: { x: 8, y: 6 } })
.onMouseMove({ type: "axis", smooth: 0.15 });
Motion("mid", ".layer-mid", { from: { x: -20, y: -16 }, to: { x: 20, y: 16 } })
.onMouseMove({ type: "axis", smooth: 0.1 });
Motion("fg", ".layer-fg", { from: { x: -40, y: -32 }, to: { x: 40, y: 32 } })
.onMouseMove({ type: "axis", smooth: 0.06 }); 3D Card Tilt
Combine rotateX and rotateY with axis mode for a classic interactive tilt:
Motion("card-3d", ".card", {
from: { rotateY: -12, rotateX: 8 },
to: { rotateY: 12, rotateX: -8 },
}).onMouseMove({
type: "axis",
target: ".card",
smooth: 0.08,
startProgress: 0.5,
leaveProgress: 0.5,
}); Proximity Fade-In
Use type: 'distance' with startProgress: 0, leaveProgress: 0 to reveal elements as the cursor approaches them:
Motion("proximity", ".reveal-on-hover", {
from: { opacity: 0, scale: 0.9 },
to: { opacity: 1, scale: 1 },
}).onMouseMove({
type: "distance",
smooth: 0.12,
startProgress: 0,
leaveProgress: 0,
}); Axis-Locked Horizontal Follow
Lock a follower element to horizontal movement only using axis: 'x' on the AnimationConfig:
Motion("cursor-x", ".follower", {
from: { x: -48 },
to: { x: 48 },
axis: "x",
}).onMouseMove({
type: "axis",
smooth: 0.06,
startProgress: 0,
leaveProgress: 0,
}); Related
- Hover — react to mouse enter and leave events
- Scroll Trigger — drive progress from scroll position
- 3D Transforms —
rotateX,rotateY,perspectivefor tilt effects - Page Load Trigger — auto-play on DOM ready