Wave Field
Wave Field é um componente de fundo para React e Tailwind CSS, escrito para a NedDev e teu para usares. Copia e cola no teu projeto.
NedDev
backgrounds
O que é
Wave Field é um componente de fundo para React e Tailwind CSS, escrito para a NedDev e teu para usares. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Precisa de
npm i lucide-react
Licença
Este componente foi escrito para este catálogo. É teu para usares em trabalho de cliente e em produtos que vendas, nos termos de uso.
Wave Field
'use client';
import React, { useEffect, useRef, useState } from 'react';
import { Play, Pause } from 'lucide-react';
import { cn } from '@/lib/utils';
export interface WaveFieldProps {
headline?: string;
className?: string;
}
export function WaveField({
headline = "HARMONIC",
className = "",
}: WaveFieldProps) {
const containerRef = useRef<HTMLDivElement | null>(null);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [isRunning, setIsRunning] = useState(true);
const pointerRef = useRef({ x: -2000, y: -2000, targetX: -2000, targetY: -2000 });
const isRunningRef = useRef(isRunning);
isRunningRef.current = isRunning;
useEffect(() => {
const container = containerRef.current;
const canvas = canvasRef.current;
if (!container || !canvas) return;
const ctx = canvas.getContext('2d', { alpha: false });
if (!ctx) return;
let animId = 0;
let time = 0;
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const rect = entry.contentRect;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.floor(rect.width * dpr);
canvas.height = Math.floor(rect.height * dpr);
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.scale(dpr, dpr);
}
});
resizeObserver.observe(container);
const render = () => {
if (isRunningRef.current) {
time += 0.015;
}
const width = container.clientWidth;
const height = container.clientHeight;
const pointer = pointerRef.current;
pointer.x += (pointer.targetX - pointer.x) * 0.1;
pointer.y += (pointer.targetY - pointer.y) * 0.1;
const isDark = document.documentElement.classList.contains('dark');
ctx.fillStyle = isDark ? '#040507' : '#ffffff';
ctx.fillRect(0, 0, width, height);
const lines = 32;
const stepY = height / (lines + 1);
for (let i = 0; i < lines; i++) {
const yBase = stepY * (i + 1);
ctx.beginPath();
const points = 100;
const stepX = width / points;
for (let p = 0; p <= points; p++) {
const x = p * stepX;
const dx = x - pointer.x;
const dy = yBase - pointer.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const influence = dist < 250 ? (1 - dist / 250) * 35 : 0;
const wave = Math.sin(p * 0.1 + time + i * 0.2) * 18 + Math.cos(p * 0.05 - time * 0.8) * 12;
const y = yBase + wave - influence;
if (p === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
const alpha = 0.12 + (i / lines) * 0.35;
ctx.strokeStyle = isDark ? `rgba(255, 255, 255, ${alpha})` : `rgba(0, 0, 0, ${alpha})`;
ctx.lineWidth = 1;
ctx.stroke();
}
animId = requestAnimationFrame(render);
};
animId = requestAnimationFrame(render);
return () => {
cancelAnimationFrame(animId);
resizeObserver.disconnect();
};
}, []);
return (
<div
ref={containerRef}
onMouseMove={(e) => {
const rect = containerRef.current?.getBoundingClientRect();
if (rect) {
pointerRef.current.targetX = e.clientX - rect.left;
pointerRef.current.targetY = e.clientY - rect.top;
}
}}
onMouseLeave={() => {
pointerRef.current.targetX = -2000;
pointerRef.current.targetY = -2000;
}}
className={cn("relative flex h-[400px] w-full select-none flex-col justify-between overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-xl dark:border-zinc-800 dark:bg-[#040507]", className)}
>
<canvas ref={canvasRef} className="absolute inset-0 block h-full w-full cursor-crosshair" />
<div className="relative z-20 flex h-full w-full flex-col justify-between p-6 md:p-8">
<header className="flex w-full items-center justify-between font-mono text-[11px] text-zinc-600 dark:text-zinc-400">
<button
onClick={() => setIsRunning(!isRunning)}
className="flex items-center gap-1.5 rounded-lg border border-zinc-300 bg-zinc-100/80 px-2.5 py-1.5 backdrop-blur-md transition-colors hover:bg-zinc-200 dark:border-zinc-800 dark:bg-zinc-900/70 dark:hover:bg-zinc-800"
>
{isRunning ? <Pause className="size-3" /> : <Play className="size-3" />}
<span>{isRunning ? "FREEZE" : "RUN"}</span>
</button>
</header>
<main className="pointer-events-none flex flex-col items-center justify-center text-center text-zinc-900 mix-blend-difference dark:text-white">
<h1 className="font-mono text-5xl font-black tracking-tighter uppercase sm:text-6xl md:text-7xl">{headline}</h1>
</main>
<div />
</div>
</div>
);
}
export default WaveField;