Shutter Effect
Original · freeA camera-shutter style reveal on hover.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/imgfx-shutter.json"use client";
import { useId } from "react";
export default function ImgfxShutter() {
// Unique id so multiple instances on a page never share the same @property/animation names.
const raw = useId();
const uid = `imgfx-shutter-${raw.replace(/[^a-zA-Z0-9]/g, "")}`;
const bladeCount = 6;
const bladeStep = 360 / bladeCount;
// Six aperture blades built from hard-stopped conic sectors with thin dividers.
const blades = `repeating-conic-gradient(from -20deg, #0b0f16 0deg ${
bladeStep - 2
}deg, #1b2430 ${bladeStep - 2}deg ${bladeStep}deg)`;
// Layered: soft center shadow for depth, metallic sheen streak, then the blades.
const shutterBackground = [
"radial-gradient(circle at 50% 50%, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0) 58%)",
"conic-gradient(from 40deg at 50% 50%, rgba(255,255,255,0.10), rgba(255,255,255,0) 22%, rgba(255,255,255,0.06) 55%, rgba(255,255,255,0) 78%)",
blades,
].join(", ");
// A ring of transparent (the open aperture) growing out of an otherwise opaque plate.
const aperture = `radial-gradient(circle at 50% 50%, transparent var(--${uid}-r), #000 calc(var(--${uid}-r) + 0.6%))`;
return (
<section className="relative w-full overflow-hidden bg-gradient-to-b from-slate-50 to-slate-100 px-6 py-20 sm:py-28 dark:from-neutral-950 dark:to-neutral-900">
<style>{`
@property --${uid}-r {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}
.${uid}-plate {
--${uid}-r: 0%;
transition:
--${uid}-r 820ms cubic-bezier(0.16, 1, 0.3, 1),
transform 820ms cubic-bezier(0.16, 1, 0.3, 1),
opacity 820ms cubic-bezier(0.16, 1, 0.3, 1);
}
.${uid}-trigger:hover .${uid}-plate,
.${uid}-trigger:focus-visible .${uid}-plate {
--${uid}-r: 74%;
transform: rotate(26deg) scale(1.7);
opacity: 0.98;
}
.${uid}-ring {
transition:
transform 820ms cubic-bezier(0.16, 1, 0.3, 1),
opacity 620ms ease;
}
.${uid}-trigger:hover .${uid}-ring,
.${uid}-trigger:focus-visible .${uid}-ring {
transform: scale(1.9);
opacity: 0;
}
.${uid}-zoom {
transition: transform 900ms cubic-bezier(0.16, 1, 0.3, 1);
}
.${uid}-trigger:hover .${uid}-zoom,
.${uid}-trigger:focus-visible .${uid}-zoom {
transform: scale(1.06);
}
@media (prefers-reduced-motion: reduce) {
.${uid}-plate,
.${uid}-ring,
.${uid}-zoom {
transition: none;
}
.${uid}-trigger:hover .${uid}-plate,
.${uid}-trigger:focus-visible .${uid}-plate {
--${uid}-r: 74%;
transform: rotate(26deg) scale(1.7);
}
}
`}</style>
<div className="mx-auto max-w-2xl">
<div className="mb-8 text-center">
<p className="text-xs font-semibold uppercase tracking-[0.28em] text-indigo-600 dark:text-indigo-400">
Image Effect
</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-slate-900 sm:text-4xl dark:text-slate-50">
Camera Shutter
</h2>
<p className="mt-3 text-sm text-slate-500 dark:text-neutral-400">
Hover or focus to open the aperture and let the frame through.
</p>
</div>
<figure
tabIndex={0}
aria-label="A photograph revealed by an opening camera aperture on hover or focus."
className={`${uid}-trigger group relative mx-auto aspect-[4/3] w-full max-w-xl cursor-pointer overflow-hidden rounded-2xl bg-slate-900 shadow-2xl shadow-slate-900/30 ring-1 ring-slate-900/10 outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:shadow-black/50 dark:ring-white/10`}
>
{/* The photograph beneath the shutter, gently zooming as the aperture opens. */}
<div className={`${uid}-zoom absolute inset-0`}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/img/gallery/g07.webp"
alt="Sunlit mountain ridgeline fading into layered morning haze"
loading="lazy"
draggable={false}
className="h-full w-full object-cover"
/>
</div>
{/* The six-blade shutter plate. Fully opaque when closed; a circular hole grows from the centre on reveal. */}
<div
aria-hidden="true"
className={`${uid}-plate absolute inset-0 origin-center`}
style={{
background: shutterBackground,
WebkitMaskImage: aperture,
maskImage: aperture,
}}
/>
{/* A crisp aperture ring that flares outward as the blades retract. */}
<div
aria-hidden="true"
className={`${uid}-ring pointer-events-none absolute left-1/2 top-1/2 h-24 w-24 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white/70 dark:border-white/60`}
/>
<figcaption className="pointer-events-none absolute bottom-3 left-4 z-10 text-xs font-medium text-white/85 opacity-0 transition-opacity duration-500 group-hover:opacity-100 group-focus-visible:opacity-100">
f/1.8 · 1/250s
</figcaption>
</figure>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Hover Zoom Effect
OriginalImage zooms in smoothly inside a fixed rounded frame on hover.

Hover Zoom Out Effect
OriginalImage starts zoomed and settles to normal on hover.

Zoom Rotate Effect
OriginalImage zooms and rotates slightly on hover.

Grayscale Color Effect
OriginalGreyscale image turns to full colour on hover.

Color Grayscale Effect
OriginalFull-colour image fades to greyscale on hover.

Blur Sharp Effect
OriginalBlurred image sharpens on hover.

Sharp Blur Effect
OriginalSharp image blurs with a caption appearing on hover.

Sepia Clear Effect
OriginalSepia-toned image clears to full colour on hover.

Saturate Pop Effect
OriginalMuted image saturates and pops on hover.

Contrast Pop Effect
OriginalLow-contrast image gains punchy contrast on hover.

Brightness Lift Effect
OriginalDim image brightens on hover.

Hue Rotate Effect
OriginalImage hue-shifts through colours on hover.

