pnpm add motion clsx tailwind-merge motionCreate components/pixel-reveal.tsx and paste the code below:
"use client";
import { cn } from "@/lib/utils";
import { motion } from "motion/react";
import React, { useEffect, useRef, useState, useMemo } from "react";
interface SquarePixelRevealProps {
children: React.ReactNode;
pixelSize?: number;
className?: string;
delay?: number;
pixelColor?: string;
}
export const PixelReveal = ({
children,
pixelSize = 8,
className,
delay = 0,
pixelColor = "bg-black dark:bg-white",
}: SquarePixelRevealProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const [grid, setGrid] = useState({ cols: 0, rows: 0 });
const [isInView, setIsInView] = useState(false);
const isTailwindClass = pixelColor.startsWith("bg-");
useEffect(() => {
const updateGrid = () => {
if (!containerRef.current) return;
const { width, height } = containerRef.current.getBoundingClientRect();
const cols = Math.ceil(width / pixelSize);
const rows = Math.ceil(height / pixelSize);
setGrid({ cols, rows });
};
updateGrid();
window.addEventListener("resize", updateGrid);
return () => window.removeEventListener("resize", updateGrid);
}, [pixelSize]);
const totalPixels = grid.cols * grid.rows;
const pixelDelays = useMemo(() => {
if (totalPixels === 0) return [];
return Array.from({ length: totalPixels }, () => Math.random());
}, [totalPixels]);
return (
<motion.div
ref={containerRef}
className={cn("relative inline-block", className)}
onViewportEnter={() => setIsInView(true)}
viewport={{ once: true, amount: 0.2 }}
>
<motion.div
initial={{ opacity: 0 }}
animate={isInView ? { opacity: 1 } : {}}
transition={{ duration: 0.4, delay: delay + 0.2 }}
>
{children}
</motion.div>
{grid.cols > 0 && (
<div
className="pointer-events-none absolute inset-0 z-10"
style={{
display: "grid",
gridTemplateColumns: `repeat(${grid.cols}, 1fr)`,
gridTemplateRows: `repeat(${grid.rows}, 1fr)`,
}}
>
{pixelDelays.map((randomFactor, i) => {
const staggerDelay = delay + randomFactor * 0.8;
return (
<div
key={i}
className={cn(
"h-full w-full transition-opacity duration-0 will-change-opacity",
isTailwindClass ? pixelColor : "",
isInView ? "opacity-0" : "opacity-100"
)}
style={{
backgroundColor: !isTailwindClass ? pixelColor : undefined,
transitionDelay: `${staggerDelay}s`,
}}
/>
);
})}
</div>
)}
</motion.div>
);
};
import { PixelReveal } from "@/components/pixel-reveal";
export default function MyComponent() {
return (
<PixelReveal>
<h1 className="text-4xl font-bold">Hello World</h1>
</PixelReveal>
);
}