Framer Override

Framer Override

Parallax Tilt any Element on Hover

Hover and move your cursor

Installation

1

Go to Assets, then in the panel, go to Code section and click the plus "+" icon and choose New Code File.

2

Give your file a name and select New Override.

3

Copy and paste the code below in your file and save (CMD/CTRL + S).

Usage

1

Select the element you want to apply the override to.

2

In the right sidebar, go to Code Overrides and click the plus "+" icon.

3

Choose your file (name you gave earlier).

4

Choose your override (it will be called ParallaxTiltOnHover)

import { Override, useAnimation } from "framer"
import { useRef, useEffect, useState } from "react"

export function ParallaxTiltOnHover(): Override {
    const controls = useAnimation()
    const ref = useRef<HTMLDivElement>(null)
    const rafRef = useRef<number | null>(null)

    const state = useRef({
        isHovering: false,
        mouseX: 0,
        mouseY: 0,
    })

    const update = () => {
        if (!ref.current || !state.current.isHovering) return

        const bounds = ref.current.getBoundingClientRect()
        const x = state.current.mouseX - bounds.left
        const y = state.current.mouseY - bounds.top

        const centerX = bounds.width / 2
        const centerY = bounds.height / 2

        // Position (opposite direction)
        const offsetX = (centerX - x) / 20
        const offsetY = (centerY - y) / 20

        // Tilt (toward cursor)
        const rotateY = (x - centerX) / 25
        const rotateX = (centerY - y) / 25

        controls.start({
            x: offsetX,
            y: offsetY,
            rotateX,
            rotateY,
            transition: { ease: "ease-in-out", duration: 0.1 },
        })

        rafRef.current = requestAnimationFrame(update)
    }

    const onPointerMove = (e: React.PointerEvent) => {
        state.current.mouseX = e.clientX
        state.current.mouseY = e.clientY

        if (!state.current.isHovering) {
            state.current.isHovering = true
            rafRef.current = requestAnimationFrame(update)
        }
    }

    const onPointerLeave = () => {
        state.current.isHovering = false
        if (rafRef.current) cancelAnimationFrame(rafRef.current)

        controls.start({
            x: 0,
            y: 0,
            rotateX: 0,
            rotateY: 0,
            transition: { ease: "linear", duration: 0.2 },
        })
    }

    useEffect(() => {
        return () => {
            if (rafRef.current) cancelAnimationFrame(rafRef.current)
        }
    }, [])

    return {
        ref,
        animate: controls,
        onPointerMove,
        onPointerLeave,
        style: {
            transformPerspective: 800, // Add depth
        },
    }
}

Cosmin Negoita

Versatile digital designer from Craiova, RO, with over 15 years of experience.

Worked both in agencies and startups, doing work for clients from various industries.

© 2025 Cosmin Negoita