Todos os componentes

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.

Abrir no catálogo

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

components/ui/wave-field.tsx
'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;

Componentes parecidos