Docs
Interactive Grid
An interactive grid background component.
requires interactionhover
Installation
Run the following command
It will create a new file interactive-grid.tsx
inside the components/animata/background
directory.
mkdir -p components/animata/background && touch components/animata/background/interactive-grid.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { cn } from "@/lib/utils";
function useGridLayout() {
const containerRef = useRef<HTMLDivElement>(null);
const [layout, setLayout] = useState({ vertical: 0, horizontal: 0 });
useEffect(() => {
const updateLayout = () => {
const rect = containerRef.current?.getBoundingClientRect();
if (!rect) {
return;
}
setLayout({
vertical: Math.floor(rect.height),
horizontal: Math.floor(rect.width),
});
};
updateLayout();
// Can be debounced if needed
window.addEventListener("resize", updateLayout);
return () => window.removeEventListener("resize", updateLayout);
}, []);
return {
layout,
containerRef,
};
}
function plotSquares(width: number, height: number, size: number): { x: number; y: number }[] {
const squares: { x: number; y: number }[] = [];
const boundary = size * 2;
const used = new Set<number>();
for (let x = boundary; x < width / 2 - boundary; x += size) {
for (let y = boundary; y < height - boundary; y += size) {
// Custom logic to reduce the number of squares
if (
used.has(x + y + size) ||
used.has(x + y - size) ||
used.has(x + y + size * 4) ||
used.has(x + y - size * 4)
) {
continue;
}
used.add(x + y);
squares.push({ x, y });
}
}
return squares;
}
const size = 24; // h-6
const boxClassName =
"absolute h-6 w-6 rounded-md bg-transparent p-px border border-gray-400/30 border-box group ";
function Grid() {
const {
layout: { vertical, horizontal },
containerRef,
} = useGridLayout();
const squares = useMemo(() => plotSquares(horizontal, vertical, size), [horizontal, vertical]);
const [active, setActive] = useState(0);
const timerRef = useRef<NodeJS.Timeout>();
const onMouseEnter = useCallback(() => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
const set = () => {
const next = squares[Math.floor(Math.random() * squares.length) % squares.length];
setActive(next.x + next.y);
};
set();
const cycleCount = 5;
let count = 0;
timerRef.current = setInterval(() => {
if (count === cycleCount) {
clearInterval(timerRef.current);
setActive(0);
return;
}
set();
count++;
}, 1000);
}, [squares]);
const cells = useMemo(() => {
return squares.map(({ x, y }, i) => {
let xPos = x;
if (i % 2) {
// Mirror the grid horizontally (50% of the time)
xPos = horizontal - x - size;
}
const shouldHighlight = active - x === x || active - y === y;
return (
<div
key={`${x}-${y}`}
style={{
transform: `translate(${xPos}px, ${y}px)`,
}}
onMouseEnter={onMouseEnter}
onClick={onMouseEnter}
className={boxClassName}
>
<div
style={{
transitionDelay: active ? `${x + y}ms` : "0ms",
}}
className={cn(
"h-full w-full scale-90 rounded bg-gray-400/30 opacity-0 transition-all duration-700",
{
"scale-100 opacity-100": shouldHighlight,
"group-hover:scale-100 group-hover:opacity-100": !shouldHighlight,
},
)}
/>
</div>
);
});
}, [squares, horizontal, active, onMouseEnter]);
return (
<div
ref={containerRef}
onClick={onMouseEnter}
className={cn("absolute inset-0 h-full max-h-96 w-full", {
"top-1/4": vertical > 96 * 4, // 96 * 4 is the height of the grid
})}
>
{cells}
</div>
);
}
export default function InteractiveGrid({
children,
className,
contentClassName,
}: {
children: React.ReactNode;
className?: string;
contentClassName?: string;
}) {
return (
<div
className={cn("storybook-fix relative h-full w-full overflow-hidden rounded-3xl", className)}
style={{
backgroundImage:
"linear-gradient(123deg, transparent 0%, transparent 36%,rgba(17, 17, 57,0.02) 36%, rgba(17, 17, 87,0.02) 56%,transparent 56%, transparent 100%),linear-gradient(251deg, transparent 0%, transparent 68%,rgba(3, 3, 3,0.02) 68%, rgba(3, 3, 93,0.02) 99%,transparent 99%, transparent 100%),linear-gradient(135deg, rgb(200,215,255),rgb(205,215,255))",
}}
>
<Grid />
<div className={cn("relative mx-auto h-full w-fit", contentClassName)}>{children}</div>
</div>
);
}
Credits
Built by hari