webdevpuneet.com

Hero with Mouse-Parallax Layers — Free HTML CSS JS Snippet

Hero with Mouse-Parallax Layers · Heroes · Plain HTML, CSS & JS · Live preview

CategoryHeroes

Copy & paste

Source Code

<section class="pml-hero" id="pmlHero">
  <div class="pml-layer pml-layer-back" data-depth="0.02">
    <span class="pml-shape pml-shape-1"></span>
    <span class="pml-shape pml-shape-2"></span>
  </div>

  <div class="pml-layer pml-layer-mid" data-depth="0.05">
    <span class="pml-icon pml-icon-1">✦</span>
    <span class="pml-icon pml-icon-2">◆</span>
    <span class="pml-icon pml-icon-3">●</span>
  </div>

  <div class="pml-layer pml-layer-device" data-depth="0.09">
    <div class="pml-device">
      <div class="pml-device-notch"></div>
      <div class="pml-device-screen">
        <div class="pml-device-row"></div>
        <div class="pml-device-row"></div>
        <div class="pml-device-row" style="width:60%"></div>
      </div>
    </div>
  </div>

  <div class="pml-content">
    <span class="pml-eyebrow">Now available on all platforms</span>
    <h1 class="pml-h1">Focus lives<br>in the details</h1>
    <p class="pml-sub">Move your cursor — every layer here drifts at its own depth, closer things reacting more than the ones behind them.</p>
    <a href="#" class="pml-cta">Get the app</a>
  </div>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Shared cursor, per-layer depth
One position drives three independently scaled offsets.
Exponential smoothing
Layers ease toward the target, never snap.
requestAnimationFrame loop
DOM writes decoupled from raw pointermove frequency.
GPU-cheap transforms
translate3d + will-change, no layout thrash.
Depth-proportional motion
Closer elements genuinely move more than distant ones.
Graceful resting state
Reuses the same loop on pointerleave, no jump cut.
Data-driven layers
New layers just need a data-depth attribute.
CSS-only mockup and shapes
No image assets required to demo the effect.

About this UI Snippet

Hero with Mouse-Parallax Layers — Real Depth From Live Cursor Position

Screenshot of the Hero with Mouse-Parallax Layers snippet rendered live

Parallax depth only reads as "real" when closer elements genuinely move more than distant ones in response to the same cursor movement — not when several layers loop independent CSS animations that happen to run at different speeds. This snippet computes every layer's offset from one shared cursor position on every animation frame, scaled by each layer's own depth factor.

One cursor position, three depth factors

onMove() normalizes the cursor's position within the hero to a -1..1 range on both axes (targetX, targetY) — center is (0,0), an edge is ±1. Three layers (.pml-layer-back, .pml-layer-mid, .pml-layer-device) each carry a data-depth attribute (0.02, 0.05, 0.09). Inside the render loop, every layer's pixel shift is currentX * maxShift * (depth * 10) — the *same* cursor position feeding three different multipliers, so the device mockup (highest depth) visibly moves several times farther than the background blobs (lowest depth) for identical cursor movement. That ratio, not just "some things move," is what makes it read as depth rather than three unrelated wobbles.

Eased toward the target, not snapped to it

Rather than setting each layer's transform directly to the raw cursor position, currentX/currentY chase targetX/targetY with simple exponential smoothing (current += (target - current) * 0.08) inside a requestAnimationFrame loop that runs continuously. This produces the soft, slightly-lagging drift real parallax has — a layer catching up to where the cursor now is, rather than teleporting to a new position on every mouse event.

Why requestAnimationFrame and not the pointermove handler directly

The pointermove listener only updates the lightweight targetX/targetY numbers; the actual DOM writes (layer.style.transform) happen inside the separate animate() loop, decoupled from event frequency. This means the visual update rate is capped to the display's real refresh rate regardless of how often pointermove fires, and the easing math has a consistent per-frame timestep to work against.

translate3d and will-change for cheap compositing

Every layer moves via translate3d(x, y, 0) (not top/left), which the browser can composite on the GPU without triggering layout, and will-change: transform hints the compositor to keep the layer on its own paint layer ahead of time.

A resting state on pointer leave

onLeave() resets targetX/targetY to zero, and because the same easing loop is always running, every layer glides back to its resting position rather than snapping — the leave state reuses the identical animation path as normal movement.

Customizing it

Add more layers with their own data-depth values (the loop reads them generically), tune maxShift or the 0.08 easing factor for snappier or dreamier motion, and swap the CSS-drawn device mockup for a real product screenshot. Pair it with parallax hero or hero parallax grid for scroll-driven parallax variants instead of cursor-driven.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSThree layered elements sit centered at rest.
  2. 2
    Move the cursor over the heroEach layer drifts toward the opposite side, the device layer moving noticeably more than the back layer.
  3. 3
    Move the cursor to an edgeOffsets scale up smoothly, capped by maxShift and each layer's own depth.
  4. 4
    Move the cursor off the heroAll layers ease back to rest via the same animation loop.
  5. 5
    Add a new layerGive it a data-depth attribute — the render loop picks it up automatically.
  6. 6
    Tune the feelAdjust maxShift or the 0.08 easing factor in animate().

Real-world uses

Common Use Cases

App and product pages
Pair with an app hero for a device surrounded by layers that move by different amounts as the cursor moves.
Agency heroes
Add tactile depth to an otherwise static opener, with exponential smoothing so layers ease toward their targets instead of snapping.
Luxury brand pages
Signal craft on a luxury brand page through cursor-reactive motion, with requestAnimationFrame decoupling DOM writes from raw pointermove frequency.
Interactive product experiences
Match depth cues to the playful nature of a game or interactive product, using translate3d and will-change for cheap transforms.
Design software pages
Complement 3D card tilt tiles elsewhere on the page, so both hero and cards respond to the pointer.
Related: Hero with Floating Glassmorphic Cards
See the Hero with Floating Glassmorphic Cards for a related heroes pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Every layer element carries a data-depth attribute (0.02, 0.05, 0.09 for the back, mid, and device layers). Inside the shared animation loop, each layer's pixel offset is computed as the same smoothed cursor position multiplied by maxShift and that layer's own depth factor — so all three layers respond to the identical cursor input, just scaled differently, which is what produces a real sense of relative depth rather than independent motion.

The pointermove handler only updates lightweight target values (targetX, targetY). A separate requestAnimationFrame loop eases a second pair of current values toward those targets on every frame using simple exponential smoothing (current += (target - current) * 0.08), and only the smoothed current values are ever written to a layer's transform — so the visual motion always trails slightly behind the raw cursor position instead of teleporting to it.

Decoupling the DOM writes from the pointermove event means the visual update rate is capped to the display's real repaint cycle rather than however often pointermove happens to fire (which can be very high-frequency on some devices), and it gives the easing math a consistent loop to run continuously against, including the ability to animate back to rest after the pointer leaves.

translate3d (even with a 0 z-value) promotes the element onto its own GPU compositor layer and animates without triggering layout recalculation or repaint of surrounding content, unlike changing left/top which forces the browser to recompute layout on every frame. Combined with will-change: transform, this keeps the parallax smooth even with several layers animating simultaneously.

Add a new .pml-layer element with its own data-depth value (a larger number moves more, a smaller number moves less) and put your shapes, icons, or mockup markup inside it with position: absolute or centered flex layout as needed. Since the animation loop selects all .pml-layer elements generically and reads each one's own data-depth, no JavaScript changes are required.