lightswind
Version:
A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.
171 lines (170 loc) • 7.81 kB
JavaScript
// @ts-nocheck
"use client";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useRef, useState } from "react";
import { useInView } from "framer-motion";
export const InteractiveGridBackground = ({ gridSize = 50, gridColor = "transparent", darkGridColor = "transparent", effectColor = "rgba(0, 0, 0, 0.5)", darkEffectColor = "rgba(255, 255, 255, 0.5)", trailLength = 3, width, height, idleSpeed = 0.2, glow = true, glowRadius = 20, children, showFade = true, fadeIntensity = 20, idleRandomCount = 5, className, ...props }) => {
const canvasRef = useRef(null);
const containerRef = useRef(null);
const isInView = useInView(containerRef);
const visibilityRef = useRef(true);
useEffect(() => {
visibilityRef.current = isInView;
}, [isInView]);
const [isDarkMode, setIsDarkMode] = useState(false);
const trailRef = useRef([]);
const idleTargetsRef = useRef([]);
const idlePositionsRef = useRef([]);
const mouseActiveRef = useRef(false);
const lastMouseTimeRef = useRef(Date.now());
// Detect dark mode
useEffect(() => {
const updateDarkMode = () => {
const prefersDark = window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
setIsDarkMode(document.documentElement.classList.contains("dark") || prefersDark);
};
updateDarkMode();
const observer = new MutationObserver(() => updateDarkMode());
observer.observe(document.documentElement, { attributes: true });
return () => observer.disconnect();
}, []);
// Mouse tracking
useEffect(() => {
const handleMouseMove = (e) => {
const container = containerRef.current;
if (!container)
return;
const rect = container.getBoundingClientRect();
const rawX = e.clientX - rect.left;
const rawY = e.clientY - rect.top;
if (rawX < 0 || rawY < 0 || rawX > rect.width || rawY > rect.height)
return;
mouseActiveRef.current = true;
lastMouseTimeRef.current = Date.now();
const snappedX = Math.floor(rawX / gridSize);
const snappedY = Math.floor(rawY / gridSize);
const last = trailRef.current[0];
if (!last || last.x !== snappedX || last.y !== snappedY) {
trailRef.current.unshift({ x: snappedX, y: snappedY });
if (trailRef.current.length > trailLength)
trailRef.current.pop();
}
};
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, [gridSize, trailLength]);
// Drawing logic
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas)
return;
const ctx = canvas.getContext("2d");
if (!ctx)
return;
const canvasWidth = width || window.innerWidth;
const canvasHeight = height || window.innerHeight;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const cols = Math.floor(canvasWidth / gridSize);
const rows = Math.floor(canvasHeight / gridSize);
const lineColor = isDarkMode ? darkGridColor : gridColor;
const glowColor = isDarkMode ? darkEffectColor : effectColor;
// Initialize idle positions
idleTargetsRef.current = Array.from({ length: idleRandomCount }, () => ({
x: Math.floor(Math.random() * cols),
y: Math.floor(Math.random() * rows),
}));
idlePositionsRef.current = idleTargetsRef.current.map((p) => ({ ...p }));
const draw = () => {
// ✅ Performance: Skip drawing completely on mobile to save CPU/Battery
if (typeof window !== "undefined" && window.innerWidth < 768) {
return;
}
if (!visibilityRef.current) {
requestAnimationFrame(draw);
return;
}
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// Draw grid lines
ctx.strokeStyle = lineColor;
ctx.lineWidth = 1;
for (let x = 0; x <= canvasWidth; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvasHeight);
ctx.stroke();
}
for (let y = 0; y <= canvasHeight; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvasWidth, y);
ctx.stroke();
}
// Idle animation logic
const idleThreshold = 2000;
if (Date.now() - lastMouseTimeRef.current > idleThreshold) {
mouseActiveRef.current = false;
idlePositionsRef.current.forEach((pos, i) => {
const target = idleTargetsRef.current[i];
const dx = target.x - pos.x;
const dy = target.y - pos.y;
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01) {
// new random target when reached
idleTargetsRef.current[i] = {
x: Math.floor(Math.random() * cols),
y: Math.floor(Math.random() * rows),
};
}
else {
pos.x += dx * idleSpeed;
pos.y += dy * idleSpeed;
}
const roundedX = Math.round(pos.x);
const roundedY = Math.round(pos.y);
const last = trailRef.current[0];
if (!last || last.x !== roundedX || last.y !== roundedY) {
trailRef.current.unshift({ x: roundedX, y: roundedY });
if (trailRef.current.length > trailLength * idleRandomCount)
trailRef.current.pop();
}
});
}
// Draw trail glow
trailRef.current.forEach((cell, idx) => {
const alpha = 1 - idx * (1 / (trailLength + 1));
const rgbaColor = glowColor.replace(/[\d.]+\)$/g, `${alpha})`);
ctx.fillStyle = rgbaColor;
if (glow) {
ctx.shadowColor = rgbaColor;
ctx.shadowBlur = glowRadius;
}
else {
ctx.shadowBlur = 0;
}
ctx.fillRect(cell.x * gridSize, cell.y * gridSize, gridSize, gridSize);
});
requestAnimationFrame(draw);
};
draw();
}, [
gridSize,
width,
height,
gridColor,
darkGridColor,
effectColor,
darkEffectColor,
isDarkMode,
trailLength,
idleSpeed,
glow,
glowRadius,
idleRandomCount,
]);
return (_jsxs("div", { ref: containerRef, className: `relative ${className}`, style: { width: width || "99vw", height: height || "100vh" }, ...props, children: [_jsx("canvas", { ref: canvasRef, className: "absolute top-0 left-0 z-0 pointer-events-none" }), showFade && (_jsx("div", { className: "pointer-events-none absolute inset-0 bg-white dark:bg-black", style: {
maskImage: `radial-gradient(ellipse at center, transparent ${fadeIntensity}%, black)`,
WebkitMaskImage: `radial-gradient(ellipse at center, transparent ${fadeIntensity}%, black)`,
} })), _jsx("div", { className: "relative z-0 w-full h-full", children: children })] }));
};
export default InteractiveGridBackground;