Atelier UI®

Read the docsGithub
Docs 1.0.0

Getting started

  • Browse Catalog
  • Installation
  • How to contribute
  • Code of conduct
  • Fluid Scene

Components (32)

  • Clip Reveal
  • Stripe Wipe
  • Sweep Exit
  • Dither Flow
    pro
  • Glowing Fog
    pro
  • Halftone Glow
    pro
  • Sphere Gallery
  • Fluid Distortion
  • Image Trail
  • Lens Media
  • Liquid Media
  • Magnetic Dot Grid
  • Pixel Media
  • Pixel Trail
  • Pixelated Text
  • Text Bounce
  • Text Fluid
  • Text Roll
  • Text Scramble
  • Curve Media
  • Elastic Stick
    pro
  • Infinite Gallery
  • Infinite Parallax
  • Infinite Zoom
  • Pixel Scroll
  • Scattered Scroll
  • Text Split
  • WebGL Image
  • WebGL Provider
  • WebGL Scene
  • WebGL Text
  • WebGL Video
Atelier UI 1.0.0 ©2026
Star on githubBuy me a coffeellms.txt
  1. Docs
  2. /
  3. Components
  4. /
  5. Infinite Parallax

Infinite Parallax

Composable infinite parallax column effect with auto-scroll and repeating content.

Motion
Lenis
Tailwind CSS
https://atelier-ui.com/infinite-parallax

Settings

auto-scroll-speed
0.02
parallax-amount
2.0
See the documentation below for more options.

Install

npx atelier-ui add infinite-parallax
npm install motion
infinite-parallax.tsx
import { motion, useAnimationFrame, useMotionValue, useScroll, useVelocity } from "motion/react"
import { type ComponentRef, useEffect, useRef, useState } from "react"

const REVERT_THRESHOLD = 50
const PARALLAX_SCALE = 0.0001

export type InfiniteParallaxProps = {
    reversed?: boolean
    autoScrollSpeed?: number
    parallaxAmount?: number
    children?: React.ReactNode
}

export function InfiniteParallax({
    reversed,
    autoScrollSpeed = 0.02,
    parallaxAmount = 2,
    children,
}: InfiniteParallaxProps) {
    const offsetRef = useRef(0)
    const contentHRef = useRef(0)
    const directionRef = useRef<1 | -1>(-1)
    const containerRef = useRef<ComponentRef<"div">>(null)
    const measureRef = useRef<ComponentRef<"div">>(null)
    const [clones, setClones] = useState(1)

    const y = useMotionValue(0)
    const { scrollY } = useScroll()
    const scrollVelocity = useVelocity(scrollY)

    useEffect(() => {
        const measure = measureRef.current
        const container = containerRef.current
        if (!measure || !container) return

        const calcClones = () => {
            const contentH = measure.getBoundingClientRect().height
            const containerH = container.getBoundingClientRect().height
            if (contentH === 0) return
            contentHRef.current = contentH
            setClones(Math.ceil(containerH / contentH))
        }

        calcClones()
        const ro = new ResizeObserver(calcClones)
        ro.observe(measure)
        ro.observe(container)

        return () => ro.disconnect()
    }, [])

    useAnimationFrame((_, delta) => {
        if (!contentHRef.current) return
        const height = contentHRef.current
        const velocity = scrollVelocity.get()

        if (Math.abs(velocity) > REVERT_THRESHOLD) {
            directionRef.current = velocity > 0 ? -1 : 1
        }

        const parallax = Math.abs(velocity) * parallaxAmount * PARALLAX_SCALE
        const step = delta * (autoScrollSpeed + parallax) * directionRef.current
        let next = offsetRef.current + step

        if (next <= -height) next = 0
        else if (next >= 0) next = -height + 1

        offsetRef.current = next
        y.set(reversed ? next : -next - height)
    })

    return (
        <div ref={containerRef} className="h-full w-full overflow-hidden">
            <motion.div style={{ y }}>
                <div ref={measureRef}>{children}</div>
                {Array.from({ length: clones }, (_, i) => (
                    <div key={i} aria-hidden>
                        {children}
                    </div>
                ))}
            </motion.div>
        </div>
    )
}

Usage

Each column is composable: stack as many as you want, you can change autoScrollSpeed and parallaxAmount per column, and change the direction to reversed to alternate their direction to obtain the desired effect.

<div className="flex h-screen gap-2 overflow-hidden">
  <InfiniteParallax>
    {images.map((src) => (
      <img key={src} src={src} alt="" className="mb-2 w-full" />
    ))}
  </InfiniteParallax>
  <InfiniteParallax reversed>
    {images.map((src) => (
      <img key={src} src={src} alt="" className="mb-2 w-full" />
    ))}
  </InfiniteParallax>
</div>

API

NameTypeDefaultDescription
childrenReactNode—Items to render in the column.
reversedbooleanfalseReverse the column's scroll direction.
autoScrollSpeednumber0.02Auto-scroll speed. 0 to disable.
parallaxAmountnumber2Parallax intensity applied while scrolling.

Credits

Olivier Larose
Where this idea came from.

Motion
React animation library.

Lenis Smooth scroll library used in the demo.

  • Install
  • Usage
  • API
  • Credits
Star on githubBuy me a coffeellms.txt