Effect
Hover Reveal Override for Framer
This is a hover reveal effect, originally created by Matt Parry. I've turned it into a code override that works seamlessly in Framer, making it simple to apply to any element on your Framer website.
About the resource
You can either copy the component and use the hover reveal effect with an image, or just copy the code override to create the override yourself in your Framer project and apply it to any element.
In this demo, I employed the hover reveal override in such a way that there's a separate image layer beneath the hover reveal image. It's set to 0.1 opacity, so you can still see the image even if it isn't fully revealed.
Code override for hover reveal effect
Feel free to copy the override code from below and create the override yourself in your Framer project.
About the resource
You can either copy the component and use the hover reveal effect with an image, or just copy the code override to create the override yourself in your Framer project and apply it to any element.
In this demo, I employed the hover reveal override in such a way that there's a separate image layer beneath the hover reveal image. It's set to 0.1 opacity, so you can still see the image even if it isn't fully revealed.
Code override for hover reveal effect
Feel free to copy the override code from below and create the override yourself in your Framer project.
About the resource
You can either copy the component and use the hover reveal effect with an image, or just copy the code override to create the override yourself in your Framer project and apply it to any element.
In this demo, I employed the hover reveal override in such a way that there's a separate image layer beneath the hover reveal image. It's set to 0.1 opacity, so you can still see the image even if it isn't fully revealed.
Code override for hover reveal effect
Feel free to copy the override code from below and create the override yourself in your Framer project.
import type { ComponentType } from "react"
import { useEffect, useState, useRef } from "react"
import {
animate,
motion,
useMotionValue,
useMotionTemplate,
} from "framer-motion"
export function withImageMask(Component): ComponentType {
return (props) => {
const [size, setSize] = useState<
{ width: number; height: number } | undefined
>(undefined)
const maskX = useMotionValue(0)
const maskY = useMotionValue(0)
const maskSize = useMotionValue(0)
const maskImage = useMotionTemplate`radial-gradient(circle at ${maskX}px ${maskY}px, black ${maskSize}px, transparent ${maskSize}px)`
const ref = useRef(null)
useEffect(() => {
if (!size || !ref.current) return
const transition = {
type: "spring",
stiffness: 200,
damping: 10,
}
const { width, height } = size
animate(maskSize, Math.sqrt(width * width + height * height), {
duration: 0.7,
})
animate(maskX, width / 2, transition)
animate(maskY, height / 2, transition)
}, [size])
return (
<motion.div
ref={ref}
onHoverStart={() => !size && animate(maskSize, 50)}
onHoverEnd={() => !size && animate(maskSize, 0)}
onPointerDown={() => !size && animate(maskSize, 40)}
onClick={() => setSize(ref.current.getBoundingClientRect())}
onPointerMove={(e) => {
if (size) return
const { top, left } = ref.current.getBoundingClientRect()
maskX.set(e.clientX - left)
maskY.set(e.clientY - top)
}}
style={{ WebkitMaskImage: maskImage, maskImage }}
{...props}
>
<Component {...props} />
</motion.div>
)
}
}
import type { ComponentType } from "react"
import { useEffect, useState, useRef } from "react"
import {
animate,
motion,
useMotionValue,
useMotionTemplate,
} from "framer-motion"
export function withImageMask(Component): ComponentType {
return (props) => {
const [size, setSize] = useState<
{ width: number; height: number } | undefined
>(undefined)
const maskX = useMotionValue(0)
const maskY = useMotionValue(0)
const maskSize = useMotionValue(0)
const maskImage = useMotionTemplate`radial-gradient(circle at ${maskX}px ${maskY}px, black ${maskSize}px, transparent ${maskSize}px)`
const ref = useRef(null)
useEffect(() => {
if (!size || !ref.current) return
const transition = {
type: "spring",
stiffness: 200,
damping: 10,
}
const { width, height } = size
animate(maskSize, Math.sqrt(width * width + height * height), {
duration: 0.7,
})
animate(maskX, width / 2, transition)
animate(maskY, height / 2, transition)
}, [size])
return (
<motion.div
ref={ref}
onHoverStart={() => !size && animate(maskSize, 50)}
onHoverEnd={() => !size && animate(maskSize, 0)}
onPointerDown={() => !size && animate(maskSize, 40)}
onClick={() => setSize(ref.current.getBoundingClientRect())}
onPointerMove={(e) => {
if (size) return
const { top, left } = ref.current.getBoundingClientRect()
maskX.set(e.clientX - left)
maskY.set(e.clientY - top)
}}
style={{ WebkitMaskImage: maskImage, maskImage }}
{...props}
>
<Component {...props} />
</motion.div>
)
}
}
import type { ComponentType } from "react"
import { useEffect, useState, useRef } from "react"
import {
animate,
motion,
useMotionValue,
useMotionTemplate,
} from "framer-motion"
export function withImageMask(Component): ComponentType {
return (props) => {
const [size, setSize] = useState<
{ width: number; height: number } | undefined
>(undefined)
const maskX = useMotionValue(0)
const maskY = useMotionValue(0)
const maskSize = useMotionValue(0)
const maskImage = useMotionTemplate`radial-gradient(circle at ${maskX}px ${maskY}px, black ${maskSize}px, transparent ${maskSize}px)`
const ref = useRef(null)
useEffect(() => {
if (!size || !ref.current) return
const transition = {
type: "spring",
stiffness: 200,
damping: 10,
}
const { width, height } = size
animate(maskSize, Math.sqrt(width * width + height * height), {
duration: 0.7,
})
animate(maskX, width / 2, transition)
animate(maskY, height / 2, transition)
}, [size])
return (
<motion.div
ref={ref}
onHoverStart={() => !size && animate(maskSize, 50)}
onHoverEnd={() => !size && animate(maskSize, 0)}
onPointerDown={() => !size && animate(maskSize, 40)}
onClick={() => setSize(ref.current.getBoundingClientRect())}
onPointerMove={(e) => {
if (size) return
const { top, left } = ref.current.getBoundingClientRect()
maskX.set(e.clientX - left)
maskY.set(e.clientY - top)
}}
style={{ WebkitMaskImage: maskImage, maskImage }}
{...props}
>
<Component {...props} />
</motion.div>
)
}
}