Flash UI

Floating Parallax

Build stunning floating parallax section.

Smooth Scroll Hero

Installation

Floating Hero

Installation

1

Add @/components/parallax.tsx
import {
    createContext,
    ReactNode,
    useCallback,
    useContext,
    useEffect,
    useRef,
  } from "react"
  import { useAnimationFrame } from "framer-motion"
  
  import { cn } from "@/app/utils/cn"
//   import { useMousePosition } from "@/hook/mousePosition"
  import { useMousePositionRef } from "@/hook/mousePosition"
  
  interface FloatingContextType {
    registerElement: (id: string, element: HTMLDivElement, depth: number) => void
    unregisterElement: (id: string) => void
  }
  
  const FloatingContext = createContext<FloatingContextType | null>(null)
  
  interface FloatingProps {
    children: ReactNode
    className?: string
    sensitivity?: number
    easingFactor?: number
  }
  
  const Floating = ({
    children,
    className,
    sensitivity = 1,
    easingFactor = 0.05,
    ...props
  }: FloatingProps) => {
    const containerRef = useRef<HTMLDivElement>(null)
    const elementsMap = useRef(
      new Map<
        string,
        {
          element: HTMLDivElement
          depth: number
          currentPosition: { x: number; y: number }
        }
      >()
    )
    const mousePositionRef = useMousePositionRef(containerRef)
  
    const registerElement = useCallback(
      (id: string, element: HTMLDivElement, depth: number) => {
        elementsMap.current.set(id, {
          element,
          depth,
          currentPosition: { x: 0, y: 0 },
        })
      },
      []
    )
  
    const unregisterElement = useCallback((id: string) => {
      elementsMap.current.delete(id)
    }, [])
  
    useAnimationFrame(() => {
      if (!containerRef.current) return
  
      elementsMap.current.forEach((data) => {
        const strength = (data.depth * sensitivity) / 20
  
        // Calculate new target position
        const newTargetX = mousePositionRef.current.x * strength
        const newTargetY = mousePositionRef.current.y * strength
  
        // Check if we need to update
        const dx = newTargetX - data.currentPosition.x
        const dy = newTargetY - data.currentPosition.y
  
        // Update position only if we're still moving
        data.currentPosition.x += dx * easingFactor
        data.currentPosition.y += dy * easingFactor
  
        data.element.style.transform = `translate3d(${data.currentPosition.x}px, ${data.currentPosition.y}px, 0)`
      })
    })
  
    return (
      <FloatingContext.Provider value={{ registerElement, unregisterElement }}>
        <div
          ref={containerRef}
          className={cn("absolute top-0 left-0 w-full h-full", className)}
          {...props}
        >
          {children}
        </div>
      </FloatingContext.Provider>
    )
  }
  
  export default Floating
  
  interface FloatingElementProps {
    children: ReactNode
    className?: string
    depth?: number
  }
  
  export const FloatingElement = ({
    children,
    className,
    depth = 1,
  }: FloatingElementProps) => {
    const elementRef = useRef<HTMLDivElement>(null)
    const idRef = useRef(Math.random().toString(36).substring(7))
    const context = useContext(FloatingContext)
  
    useEffect(() => {
      if (!elementRef.current || !context) return
  
      const nonNullDepth = depth ?? 0.01
  
      context.registerElement(idRef.current, elementRef.current, nonNullDepth)
      return () => context.unregisterElement(idRef.current)
    }, [depth])
  
    return (
      <div
        ref={elementRef}
        className={cn("absolute will-change-transform", className)}
      >
        {children}
      </div>
    )
  }

2

Add @/hook/mousePosition.tsx
import { useRef, useEffect } from "react";
export const useMousePositionRef = (
  containerRef?: React.RefObject<HTMLElement | SVGElement>
) => {
  const mousePositionRef = useRef({ x: 0, y: 0 });
  useEffect(() => {
    const updatePosition = (x: number, y: number) => {
      if (containerRef?.current) {
        const rect = containerRef.current.getBoundingClientRect();
        mousePositionRef.current = {
          x: x - rect.left,
          y: y - rect.top,
        };
      } else {
        mousePositionRef.current = { x, y };
      }
    };
    const handleMouseMove = (event: MouseEvent) => {
      updatePosition(event.clientX, event.clientY);
    };
    const handleTouchMove = (event: TouchEvent) => {
      const touch = event.touches[0];
      updatePosition(touch.clientX, touch.clientY);
    };
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("touchmove", handleTouchMove);
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("touchmove", handleTouchMove);
    };
  }, [containerRef]);
  return mousePositionRef;
};

3

Add the @/app/utils/cn to your project
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

4

Install the required packages
npm install framer-motion
Flash UICopyright © 2025 Flash UI. All Rights Reserved.