Floating Elements with Mouse Parallax

Design floating UI elements that move in a smooth parallax based on mouse position. Includes multiple depth layers, smooth lerp easing, and mobile gyroscope support.


Floating Elements with Mouse Parallax


Elevate your landing pages by implementing a smooth, multi-layered parallax effect driven by the user’s mouse position or mobile device gyroscope. This approach uses pure JavaScript with requestAnimationFrame and Linear Interpolation (Lerp) to guarantee completely buttery-smooth easings without relying on external animation libraries.

HTML Structure

The structure assumes a full-screen relative wrapper. Inside, absolute-positioned elements will contain a unique data attribute data-depth.

<div class="parallax-wrapper" id="parallax-wrapper">
  
  <div class="center-content">
    <h1>Fluid Spaces</h1>
  </div>

  <div class="floating-elements">
    <!-- Depth 0.1 moving same dir slowly -->
    <div class="parallax-node pos-5 orb-1" data-depth="0.05"></div>
    <div class="parallax-node pos-6 orb-2" data-depth="-0.08"></div>

    <!-- Medium Depth items -->
    <div class="parallax-node pos-1" data-depth="0.15">
      <div class="glass-card">...</div>
    </div>
    
    <div class="parallax-node pos-3" data-depth="-0.2">
      <div class="glass-card">...</div>
    </div>
  </div>
</div>

CSS Styling Guidelines

Absolute positioning should be tied via layout classes (like .pos-1, .pos-2). Group floating graphics inside .parallax-node wrappers which are exclusively used for pushing the CSS transforms in JS. This guarantees the animation logic won’t conflict with responsive flex/grid layouts.

.parallax-wrapper {
  position: relative;
  width: 100vw; height: 100vh;
  overflow: hidden;
}

.floating-elements {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.parallax-node {
  position: absolute;
  will-change: transform;
}

JavaScript Execution

Here we track the mouse coordinates normalized between -1 and 1 from the exact center of the screen. requestAnimationFrame maintains the 60fps loop, and the custom lerp function smoothly glides the shapes towards the current mouse target.

const nodes = document.querySelectorAll(".parallax-node");

// Targets
let targetX = 0;
let targetY = 0;

// Current properties
let currentX = 0;
let currentY = 0;

// 1. Mouse Event Tracking
window.addEventListener("mousemove", (e) => {
  const centerX = window.innerWidth / 2;
  const centerY = window.innerHeight / 2;
  
  // Normalize coordinates from -1 to 1 based on screen quarters 
  targetX = (e.clientX - centerX) / centerX; 
  targetY = (e.clientY - centerY) / centerY; 
});

// 2. Mobile Gyroscope Support
window.addEventListener("deviceorientation", (e) => {
  if (e.gamma === null || e.beta === null) return;
  
  const maxTilt = 30; // maximum degree constraint
  let tiltX = Math.max(-maxTilt, Math.min(maxTilt, e.gamma)); 
  
  // Offset beta by -45 so resting tilted phone feels centered
  let tiltY = Math.max(-maxTilt, Math.min(maxTilt, e.beta - 45)); 
  
  // Normalize from -1 to 1
  targetX = tiltX / maxTilt;
  targetY = tiltY / maxTilt;
});

// 3. Smooth Lerp Engine
const lerp = (start, end, factor) => start + (end - start) * factor;

function update() {
  // Interpolate towards the target offset smoothly
  currentX = lerp(currentX, targetX, 0.05);
  currentY = lerp(currentY, targetY, 0.05);

  // Read data attributes and push transforms
  nodes.forEach((node) => {
    const depth = parseFloat(node.getAttribute("data-depth") || "0.1");
    const maxMove = 100; // max pixel distance
    
    const moveX = currentX * depth * maxMove;
    const moveY = currentY * depth * maxMove;

    node.style.transform = `translate3d(${moveX}px, ${moveY}px, 0)`;
  });

  requestAnimationFrame(update);
}

// Start sequence
update();

Enjoy lightweight, infinitely smooth parallax fields without importing massive tweening engines.