Grid Dot Background
Dot grid where dots near the cursor glow and enlarge like a radar pulse
backgroundgriddotsinteractivecursor
Preview
Source Code
"use client";
import { useRef, useEffect } from "react";
interface GridDotBackgroundProps {
children?: React.ReactNode;
dotColor?: string;
spacing?: number;
glowRadius?: number;
className?: string;
}
export default function GridDotBackground({
children,
dotColor = "#38bdf8",
spacing = 30,
glowRadius = 120,
className = "",
}: GridDotBackgroundProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const mouseRef = useRef({ x: -1000, y: -1000 });
const animRef = useRef(0);
useEffect(() => {
const canvas = canvasRef.current;
const container = containerRef.current;
if (!canvas || !container) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
let cssW = 0, cssH = 0;
const resize = () => {
const dpr = window.devicePixelRatio || 1;
cssW = container.clientWidth;
cssH = container.clientHeight;
canvas.width = cssW * dpr;
canvas.height = cssH * dpr;
canvas.style.width = `${cssW}px`;
canvas.style.height = `${cssH}px`;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
};
resize();
window.addEventListener("resize", resize);
const handleMouseMove = (e: MouseEvent) => {
const rect = container.getBoundingClientRect();
mouseRef.current = { x: e.clientX - rect.left, y: e.clientY - rect.top };
};
const handleMouseLeave = () => {
mouseRef.current = { x: -1000, y: -1000 };
};
container.addEventListener("mousemove", handleMouseMove);
container.addEventListener("mouseleave", handleMouseLeave);
// Parse color to RGB
const tempEl = document.createElement("div");
tempEl.style.color = dotColor;
document.body.appendChild(tempEl);
const computed = getComputedStyle(tempEl).color;
document.body.removeChild(tempEl);
const rgb = computed.match(/\d+/g)?.map(Number) || [56, 189, 248];
const animate = () => {
ctx.clearRect(0, 0, cssW, cssH);
const mouse = mouseRef.current;
for (let x = spacing; x < cssW; x += spacing) {
for (let y = spacing; y < cssH; y += spacing) {
const dx = x - mouse.x;
const dy = y - mouse.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const influence = Math.max(0, 1 - dist / glowRadius);
const baseSize = 1.5;
const size = baseSize + influence * 4;
const alpha = 0.15 + influence * 0.8;
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${alpha})`;
ctx.fill();
// Glow for nearby dots
if (influence > 0.2) {
ctx.beginPath();
ctx.arc(x, y, size * 3, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${influence * 0.1})`;
ctx.fill();
}
}
}
animRef.current = requestAnimationFrame(animate);
};
animate();
return () => {
cancelAnimationFrame(animRef.current);
window.removeEventListener("resize", resize);
container.removeEventListener("mousemove", handleMouseMove);
container.removeEventListener("mouseleave", handleMouseLeave);
};
}, [dotColor, spacing, glowRadius]);
return (
<div ref={containerRef} className={`relative overflow-hidden bg-[#050510] ${className}`}>
<canvas ref={canvasRef} className="absolute inset-0" />
<div className="pointer-events-none relative z-10">{children}</div>
</div>
);
}