Parallax Floating
A component that creates a parallax floating effect on cursor/touch movement. Works also with videos, svgs, or any type of html elements.
fancy.
Download
Source code
Create a hook for querying the cursor position:
import { useState, useEffect, RefObject } from "react";
export const useMousePosition = (containerRef?: RefObject<HTMLElement | SVGElement>) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const updatePosition = (x: number, y: number) => {
if (containerRef && containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
const relativeX = x - rect.left;
const relativeY = y - rect.top;
// Calculate relative position even when outside the container
setPosition({ x: relativeX, y: relativeY });
} else {
setPosition({ x, y });
}
};
const handleMouseMove = (ev: MouseEvent) => {
updatePosition(ev.clientX, ev.clientY);
};
const handleTouchMove = (ev: TouchEvent) => {
const touch = ev.touches[0];
updatePosition(touch.clientX, touch.clientY);
};
// Listen for both mouse and touch events
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("touchmove", handleTouchMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("touchmove", handleTouchMove);
};
}, [containerRef]);
return position;
};
Then copy and paste the following code into your project:
Usage
There are two components exported from the source file: Floating
and FloatingElement
. The first one is a wrapper component that takes care of the animation, mouse position tracking and other logic. The second one is a component that you must use to wrap any elements you want to float.
<Floating>
<FloatingElement depth={0.5}>
<div className="absolute top-1/2 left-1/4 bg-red-500" />
</FloatingElement>
<FloatingElement depth={1}>
<div className="absolute top-1/2 left-2/4 bg-green-500" />
</FloatingElement>
<FloatingElement depth={2}>
<div className="absolute top-1/2 left-3/4 bg-blue-500" />
</FloatingElement>
</Floating>
The advantage of this setup is that you can style and position your elements however you want using Tailwind classes or custom CSS directly on the FloatingElement
component, while the Floating
wrapper component handles all the complex animation logic. Simply wrap your positioned elements with FloatingElement
, set their depth
value, and the floating effect will be applied while maintaining your original styling and positioning.
Understanding the component
If you're curious how it works, here's a quick overview of the component's internals:
-
Element Registration: Using React Context, each
FloatingElement
child registers itself with the parentFloating
component, providing its DOM reference and depth value. -
Mouse Position Tracking: The component tracks mouse movement across the screen using a custom hook that provides normalized coordinates relative to the container.
-
Animation Loop: Using Framer Motion's
useAnimationFrame
, the component runs a continuous animation loop that:- Calculates the target position for each element based on the mouse coordinates
- Applies linear interpolation (lerp) to smoothly transition elements to their new positions
- Updates the transform property of each element using CSS transforms
-
Strength: The floating effect is customized through two main factors:
- Individual
depth
values on eachFloatingElement
determine how far that element moves. The higher the depth, the farther the element will move. - The global
sensitivity
prop controls the overall intensity of the movement
- Individual
-
Lerp: The
easingFactor
prop determines how quickly elements move toward their target positions - lower values create smoother, more gradual movements while higher values create snappier responses.
Notes
Z-Index Management
The Floating
component focuses solely on movement animation and does not handle z-index stacking. You'll need to manually set appropriate z-index values on your FloatingElement
components to achieve the desired layering effect. The depth
prop only controls the intensity of the floating movement, not the visual stacking order.
Performance Optimization
For better performance when dealing with multiple floating elements, you can use a grouping strategy:
- Instead of creating individual
FloatingElement
components for each item, group related items under a singleFloatingElement
- All children of a
FloatingElement
will move together with the same depth value - This reduces the number of elements being calculated and transformed
For example, if you have 6 floating images, instead of creating 6 separate FloatingElement
components, you could group them into 3 pairs. This reduces the animation calculations from 6 to 3.
Directional Control
With the depth
and sensitivity
props, you can control the direction, and strength of the floating effect:
-
Positive Values: Elements move toward the mouse cursor
- Higher values create stronger movement
- Example:
depth={2}
moves twice as far asdepth={1}
-
Negative Values: Elements move away from the mouse cursor
- Creates an inverse floating effect
- Example:
depth={-1}
moves in the opposite direction of the mouse
Props
Floating
Prop | Type | Default | Description |
---|---|---|---|
children* | React.ReactNode | - | The content to be displayed |
sensitivity | number | 0.1 | The sensitivity of the movement |
easingFactor | number | 0.05 | The easing factor of the movement |
className | string | - | Additional CSS classes for styling |
FloatingElement
Prop | Type | Default | Description |
---|---|---|---|
children* | React.ReactNode | - | The content to be displayed |
depth | number | 1 | The depth of the element |
className | string | - | Additional CSS classes for styling |