Docs
Parallax Floating

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:

  1. Element Registration: Using React Context, each FloatingElement child registers itself with the parent Floating component, providing its DOM reference and depth value.

  2. Mouse Position Tracking: The component tracks mouse movement across the screen using a custom hook that provides normalized coordinates relative to the container.

  3. 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
  4. Strength: The floating effect is customized through two main factors:

    • Individual depth values on each FloatingElement 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
  5. 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:

  1. Instead of creating individual FloatingElement components for each item, group related items under a single FloatingElement
  2. All children of a FloatingElement will move together with the same depth value
  3. 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 as depth={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

PropTypeDefaultDescription
children*React.ReactNode-The content to be displayed
sensitivitynumber0.1The sensitivity of the movement
easingFactornumber0.05The easing factor of the movement
classNamestring-Additional CSS classes for styling

FloatingElement

PropTypeDefaultDescription
children*React.ReactNode-The content to be displayed
depthnumber1The depth of the element
classNamestring-Additional CSS classes for styling