Skip to content

Mouse parallax

Mouse parallax gives stages a sense of depth: as the visitor moves their cursor, layers drift in 3D space. Done well it’s a subtle “alive” feel; done aggressively it’s nausea. This page covers how the engine works, how to tune it, and how it respects accessibility.

Parallax is per layer, not global. Each layer can independently opt in via the Layer Style modal → Parallax tab, where you set:

  • Depth (−1 to 1) — translate offset multiplier.
  • Tilt (0 to 1) — rotateX / rotateY multiplier.
  • Respect reduced-motion preference — skip parallax for visitors with prefers-reduced-motion: reduce.

The stage’s Parallax intensity (0–100) (Stage settings → Parallax) is the global gain. Each layer’s transform on storefront is:

translate3d(depth × intensity × Xratio, depth × intensity × Yratio, 0)
rotateX(tilt × intensity × Yratio deg)
rotateY(tilt × intensity × −Xratio deg)

where Xratio / Yratio are the cursor’s normalized position relative to the shell (-1 at the left/top edge, +1 at the right/bottom edge).

A good visual hierarchy uses small absolute values:

  • Background layers — depth ≈ 0 to 0.1. Or slightly negative (-0.05) so they drift opposite to foreground, exaggerating depth.
  • Mid-ground — depth ≈ 0.2 to 0.4.
  • Foreground / hero element — depth up to 0.6 or 0.7.

Tilt is more aggressive than translate; values above 0.3 usually feel too much. Start with 0.10.2.

The stage intensity is the master fader. With every layer set to its design values, the intensity slider lets you tune the overall parallax strength without re-editing layers. Set to 0 to disable parallax stage-wide without touching individual layers.

The plugin’s parallax engine attaches a mousemove listener to the .p2lab-stage__shell root (or the slider root for slider stages). On each mousemove:

  1. Compute cursor position relative to the shell.
  2. Normalize to (-1, -1)(+1, +1) range.
  3. For every layer with data-depth / data-tilt, compute the transform and apply it to .p2lab-stage__layer-wrap via style.transform.

The translate and rotate go on the .p2lab-stage__layer-wrap (positioning element). The actual layer content (.p2lab-stage__layer) keeps its own animation transforms — they compose on a separate DOM element so parallax and timeline animations don’t fight.

Disable conditions:

  • prefers-reduced-motion: reduce — the engine doesn’t attach the listener (assuming the layer has Respect reduced-motion enabled — and it should be on by default for accessibility).
  • Coarse pointer (pointer: coarse) — touch devices don’t have a meaningful cursor; the engine disables itself rather than producing erratic touch-driven motion.

Slider stages get the same engine, attached to the slider’s outer wrapper. Each slide can have its own parallax layers; transitioning between slides smoothly hands over the parallax origin from one slide’s centre to the next.

A few things to keep in mind:

  • Respect reduced-motion is on by default for every parallax layer. Leave it on. Visitors who set the OS-level “Reduce motion” preference (or who use assistive tech) will see the stage without the cursor drift.
  • Vestibular disorders are real — even mild parallax can trigger discomfort for some users. Conservative depth / tilt values reduce risk.
  • Stage intensity = 0 is a kill switch you can flip if you need to ship a stage with parallax disabled for a specific campaign.

The engine throttles via requestAnimationFrame — even on fast machines it does at most one update per frame. Transforms are GPU-composited (translate3d, rotate3d), so CPU cost is low even on slow devices.

If parallax doesn’t appear to work:

  1. Open DevTools, inspect a layer’s .p2lab-stage__layer-wrap. Confirm data-depth / data-tilt are emitted. If not, the layer’s Parallax effect isn’t enabled or has both values at 0.
  2. Check the stage’s intensity slider — if it’s at 0, nothing moves.
  3. Confirm the OS isn’t reporting reduced-motion (System Preferences → Accessibility → Display → Reduce Motion on macOS, or the equivalent toggle in Windows).
  4. Confirm the device isn’t reporting coarse pointer (parallax is intentionally disabled on touch).