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.

typescript
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:

OptionTypeDefaultDescription
type'axis' | 'distance''distance'How mouse position maps to progress. See Type Modes.
targetstring | ElementviewportThe element whose bounds define the movement area. Accepts a CSS selector or Element reference.
smoothnumber0.1Lag applied to progress tracking. 0 = instant follow, 1 = maximum lag.
startProgressnumber0.5Animation progress when the mouse enters the target area.
leaveProgressnumber0.5Animation progress to return to when the mouse leaves.
eachbooleanfalseWhen 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 0from state
  • Mouse at right edge → progress 1to state
  • Same applies vertically: top → 0, bottom → 1

This makes axis mode ideal for parallax depth layers and directional follow effects.

typescript
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 (default 0.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.

typescript
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.

typescript
// 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.

typescript
// 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:

typescript
// 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.

ValueEffect
0Instant — animation snaps to cursor with no lag
0.05–0.1Subtle easing, natural parallax feel (recommended)
0.15–0.2Noticeable lag, dreamy or heavy effect
1Maximum lag — very slow to follow

Use different smooth values across depth layers to create convincing parallax — farther layers should feel slower:

typescript
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.

typescript
// 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:

typescript
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:

typescript
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:

typescript
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:

typescript
Motion("cursor-x", ".follower", {
  from: { x: -48 },
  to:   { x: 48  },
  axis: "x",
}).onMouseMove({
  type: "axis",
  smooth: 0.06,
  startProgress: 0,
  leaveProgress: 0,
});