Atelier UI®

DocsCatalogShader StudioPricingGithub
Docs 1.0.0

tools

  • Browse Catalog
  • Shader Studio
    pro
  • Collage
    new

Documentation

  • How it works
  • License
  • MCP

Page Transition (04)

  • Clip Transition
  • Stripe Transition
  • Pixel Transition
  • Band Transition

Components (36)

  • Orbit Gallery
  • Sphere Gallery
  • Spiral Gallery
  • Glowing Fog
  • Gradient Flow
  • Halftone Glow
  • Scattered Grid
  • Tag Cloud
  • Edge Bounce
  • Fluid Distortion
  • Image Trail
  • Lens Media
  • Liquid Media
  • Magnetic Dot Grid
  • Pixel Media
  • Pixel Trail
  • Dither Cursor
  • Hover Burst
  • Image Bloom
  • Curve Media
  • Infinite Gallery
  • Infinite Parallax
  • Infinite Zoom
  • Pixel Scroll
  • Scattered Scroll
  • Elastic Stick
  • Letter Swarm
  • Magnify Trail
  • Stacking Grid
  • Wavy Scroll
  • Pixelated Text
  • Text Bounce
  • Text Fluid
  • Text Scramble
  • Falling Text
  • Text Roll

Foundation Blocks (07)

  • Smooth 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. Edge Bounce

Edge Bounce

A wrapper that bounces away from the cursor on hover and springs back into place.

https://atelier-ui.com/edge-bounce
pause
0.00
out-duration
0.35
in-duration
1.00
bounce
0.30
distance
35
rotation
25
pause
0.00
out-duration
0.35
in-duration
1.00
bounce
0.30
distance
35
rotation
25
Prompt
Add Atelier's Edge Bounce to my app.

If there is no components.json, run: npx shadcn@latest init -d
Then run: npx shadcn@latest add @atelier/edge-bounce
That writes the atelier-ui skill under .agents/skills and .claude/skills. Follow it.

Props: pause={0} outDuration={0.35} inDuration={1} bounce={0.3} distance={35} rotation={25}

Just into your favorite coding agent and let Atelier do its magic. Live props are included.


This command will install all the dependencies this component uses.

npx shadcn@latest add @atelier/edge-bounce

Install the dependencies first, then feel free to copy the files into your project as you see fit.

npm install motion
edge-bounce.tsx
"use client"

import { useAnimate, useMotionValue, useVelocity } from "motion/react"
import type React from "react"
import { type ComponentRef, type ReactNode, useEffect, useRef } from "react"

const MAX_BOUNCE_SPEED = 1500

export type EdgeBounceProps = {
    children: ReactNode
    className?: string
    pause?: number
    outDuration?: number
    inDuration?: number
    bounce?: number
    distance?: number
    rotation?: number
}

export function EdgeBounce({
    children,
    className,
    pause = 0,
    outDuration = 0.35,
    inDuration = 1,
    bounce = 0.3,
    distance = 35,
    rotation = 25,
}: EdgeBounceProps) {
    const [scope, animate] = useAnimate<ComponentRef<"div">>()
    const isBouncing = useRef(false)
    const touchInside = useRef(false)
    const pointerX = useMotionValue(0)
    const pointerY = useMotionValue(0)
    const velocityX = useVelocity(pointerX)
    const velocityY = useVelocity(pointerY)

    const bounceFrom = async (fromX: number, fromY: number) => {
        const element = scope.current
        if (!element || isBouncing.current) return

        const velX = velocityX.get()
        const velY = velocityY.get()
        const speed = Math.hypot(velX, velY)
        const speedRatio = Math.min(speed / MAX_BOUNCE_SPEED, 1)

        const rect = element.getBoundingClientRect()
        const deltaX = rect.left + rect.width / 2 - fromX
        const deltaY = rect.top + rect.height / 2 - fromY
        const distanceFromCenter = Math.hypot(deltaX, deltaY)

        if (distanceFromCenter === 0 || speed === 0) return

        const tilt = (deltaY * velX - deltaX * velY) / (distanceFromCenter * speed)

        isBouncing.current = true
        await animate(
            element,
            {
                x: (deltaX / distanceFromCenter) * distance * speedRatio,
                y: (deltaY / distanceFromCenter) * distance * speedRatio,
                rotate: tilt * rotation * speedRatio,
            },
            { duration: outDuration, ease: "circOut" },
        )
        isBouncing.current = false

        animate(
            element,
            { x: 0, y: 0, rotate: 0 },
            { type: "spring", duration: inDuration, bounce, delay: pause },
        )
    }

    useEffect(() => {
        const isOver = (event: PointerEvent) => {
            const rect = scope.current?.getBoundingClientRect()
            if (!rect) return false

            return (
                event.clientX >= rect.left &&
                event.clientX <= rect.right &&
                event.clientY >= rect.top &&
                event.clientY <= rect.bottom
            )
        }

        const handlePointerDown = (event: PointerEvent) => {
            if (event.pointerType === "touch") touchInside.current = isOver(event)
        }

        const handlePointerMove = (event: PointerEvent) => {
            pointerX.set(event.clientX)
            pointerY.set(event.clientY)

            if (event.pointerType !== "touch") return

            const inside = isOver(event)
            if (inside && !touchInside.current) bounceFrom(event.clientX, event.clientY)
            touchInside.current = inside
        }

        window.addEventListener("pointerdown", handlePointerDown)
        window.addEventListener("pointermove", handlePointerMove)

        return () => {
            window.removeEventListener("pointerdown", handlePointerDown)
            window.removeEventListener("pointermove", handlePointerMove)
        }
    })

    const handlePointerEnter = (event: React.PointerEvent<HTMLDivElement>) => {
        if (event.pointerType !== "touch") bounceFrom(event.clientX, event.clientY)
    }

    return (
        <div
            ref={scope}
            className={`table will-change-transform ${className ?? ""}`}
            onPointerEnter={handlePointerEnter}
        >
            {children}
        </div>
    )
}

Wrap the content that should bounce away from the cursor.

<EdgeBounce>
    <img src="https://picsum.photos/seed/atelier-1/1200/800" alt="Photo" />
</EdgeBounce>

NameTypeDefaultDescription
childrenReactNode—Content to render inside the wrapper. Required.
classNamestring—Class names applied to the wrapper.
pausenumber0Delay in seconds before springing back.
outDurationnumber0.35Duration in seconds for the wrapper moving away.
inDurationnumber1Duration in seconds for the spring-back animation.
bouncenumber0.3Bounciness of the spring-back. 0 = no overshoot, 1 = maximum oscillation.
distancenumber35Maximum pixels the wrapper moves from its origin, reached at full pointer speed.
rotationnumber25Maximum rotation in degrees, following the pointer's travel direction. 0 disables it.

Motion
React animation library.

Star on githubBuy me a coffeellms.txt