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.
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.
- Positive depths move the element with the cursor.
- Negative depths move the element against the cursor.
- Higher absolute values represent elements “closer” to the camera that move faster.
<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.