Shatter Reveal Card
The is a high-end UI component that acts as a "digital veil." It hides an image behind a grid of flickering binary pixels (0s and 1s). When you hover over it, the pixels shatter and fade away in a random, staggered sequence to reveal the content beneath.
Live Preview
Card for:Peaky Blinders
One minute. That's all it takes. Sometimes, one minute is all you have
Dependencies
npm install
framer-motionCode
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
const ShatterRevealCard = ({
title = "Peaky Blinders",
image = "https://res.cloudinary.com/dmqwpwo6c/image/upload/v1776147234/peaky_vizcxc.jpg",
}) => {
const [isHovered, setIsHovered] = useState(false);
const rows = 20;
const cols = 15;
const totalBoxes = rows * cols;
const [shuffledIndices, setShuffledIndices] = useState<any>([]);
useEffect(() => {
const indices = [...Array(totalBoxes).keys()];
for (let i = indices.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[indices[i], indices[j]] = [indices[j], indices[i]];
}
setShuffledIndices(indices);
}, [totalBoxes]);
return (
<motion.div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className="relative w-80 h-[450px] bg-black border border-white/20 rounded-sm overflow-hidden shadow-2xl group cursor-default"
style={{ fontFamily: "'Space Mono', monospace" }}
>
<div
className="absolute inset-0 z-0 transition-transform duration-700 group-hover:scale-105"
style={{
backgroundImage: `url(${image})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
/>
<div
className="absolute inset-0 z-10 grid"
style={{
gridTemplateColumns: `repeat(${cols}, 1fr)`,
gridTemplateRows: `repeat(${rows}, 1fr)`,
}}
>
{shuffledIndices.map((originalIndex: any, orderIndex: any) => (
<motion.div
key={originalIndex}
initial={false}
animate={{
opacity: isHovered ? 0 : 1,
scale: isHovered ? 0.8 : 1,
}}
transition={{
duration: 0.4,
delay: isHovered ? (orderIndex / totalBoxes) * 1.5 : 0,
ease: "easeOut",
}}
className="w-full h-full bg-black z-20 border-[0.5px] border-white/5"
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{!isHovered && (
<motion.span
animate={{ opacity: [0.1, 0.5, 0.1] }}
transition={{
repeat: Infinity,
duration: Math.random() * 2 + 1,
}}
className="text-[10px] text-white"
>
{Math.random() > 0.5 ? "0" : "1"}
</motion.span>
)}
</motion.div>
))}
</div>
<div className="absolute inset-0 z-0 p-6 flex flex-col justify-between pointer-events-none">
<div className="flex justify-between items-start">
<div className="bg-black/80 px-2 py-1 border border-white/10">
<span className="text-[10px] text-white/40 tracking-[0.3em] italic uppercase block">
Card for:
</span>
<span className="text-[10px] text-white tracking-widest">
{title}
</span>
</div>
<div className="w-2 h-2 bg-red-500 shadow-[0_0_10px_red] animate-pulse" />
</div>
<AnimatePresence>
<motion.div
exit={{ opacity: 0, x: -20 }}
className="bg-black/60 backdrop-blur-sm p-4 border-l-2 border-white"
>
<p className="text-[14px] text-white tracking-tighter leading-tight uppercase">
One minute. That's all it takes. Sometimes, one minute is all you
have
</p>
</motion.div>
</AnimatePresence>
</div>
<div className="absolute inset-0 pointer-events-none bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.5)_50%)] bg-[length:100%_4px] z-30 opacity-20" />
</motion.div>
);
};
export default ShatterRevealCard;
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | Peaky Blinders | The text shown in the top corner badge. |
| image | string | https://res.cloudinary.com/dmqwpwo6c/image/upload/v1776147234/peaky_vizcxc.jpg | The background image revealed on hover. |