Ir al contenido
Evaluar mi proyecto
Componentes · Cursor y efectosDisponible

Superficie de agua con ondas

Cada clic lanza anillos concéntricos que se expanden y se desvanecen; Enter o Espacio la activan desde el teclado.

Toca el aguaHaz clic donde quieras

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

efecto-ondas.tsx60 líneas
Descargar
"use client";

import { useRef, useState, type CSSProperties, type KeyboardEvent, type PointerEvent } from "react";

/** Superficie de agua: cada clic o toque lanza anillos concéntricos que se expanden y se desvanecen, y el cuerpo ondula. Con Enter o Espacio se lanza una onda desde el centro. Solo SVG y CSS. React + TypeScript, sin dependencias. */
export type EfectoOndasProps = {
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gceon-root { box-sizing: border-box; position: relative; width: 100%; max-width: 640px; height: 320px; overflow: hidden; border-radius: 20px; background: radial-gradient(120% 120% at 30% 20%, #0f5f8f, #072a4a 70%); color: #d7f0ff; font: 500 15px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; display: grid; place-items: center; cursor: pointer; user-select: none; outline: 0; touch-action: manipulation; }
.gceon-root *, .gceon-root *::before, .gceon-root *::after { box-sizing: border-box; }
.gceon-root:focus-visible { box-shadow: inset 0 0 0 3px #7fd0ff; }
.gceon-ring { position: absolute; width: 20px; height: 20px; margin: -10px 0 0 -10px; border: 2px solid rgba(190, 235, 255, .9); border-radius: 50%; pointer-events: none; animation: gceon-grow 1.8s cubic-bezier(.16, .6, .3, 1) forwards; animation-delay: var(--d, 0s); opacity: 0; }
.gceon-hint { position: relative; pointer-events: none; text-align: center; opacity: .8; }
.gceon-hint b { display: block; font: 600 20px/1.2 ui-sans-serif, sans-serif; color: #fff; }
.gceon-shine { position: absolute; inset: 0; pointer-events: none; background: repeating-radial-gradient(circle at 70% 120%, transparent 0 14px, rgba(255, 255, 255, .03) 14px 16px); }
@keyframes gceon-grow { 0% { transform: scale(.4); opacity: 0; } 10% { opacity: 1; } 100% { transform: scale(26); opacity: 0; border-width: .4px; } }
@media (prefers-reduced-motion: reduce) { .gceon-ring { animation: gceon-fade 1.2s ease-out forwards; } @keyframes gceon-fade { 0% { opacity: .9; transform: scale(3); } 100% { opacity: 0; transform: scale(3); } } }`;

type Ring = { id: number; x: number; y: number };

export function EfectoOndas({ className = "", style }: EfectoOndasProps) {
  const [rings, setRings] = useState<Ring[]>([]);
  const id = useRef(0);
  const root = useRef<HTMLDivElement>(null);

  function drop(x: number, y: number) {
    const batch: Ring[] = [0, 1, 2].map(() => ({ id: ++id.current, x, y }));
    setRings((list) => [...list.slice(-12), ...batch]);
    window.setTimeout(() => setRings((list) => list.filter((r) => !batch.some((b) => b.id === r.id))), 2400);
  }
  function click(event: PointerEvent<HTMLDivElement>) {
    const rect = event.currentTarget.getBoundingClientRect();
    drop(event.clientX - rect.left, event.clientY - rect.top);
  }
  function key(event: KeyboardEvent) {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      const rect = root.current!.getBoundingClientRect();
      drop(rect.width / 2, rect.height / 2);
    }
  }

  return (
    <div ref={root} className={`gceon-root ${className}`} style={style} role="button" tabIndex={0} aria-label="Superficie de agua. Pulsa para crear ondas" onPointerDown={click} onKeyDown={key}>
      <style>{CSS}</style>
      <div className="gceon-shine" aria-hidden="true" />
      {rings.map((ring, i) => (
        <span key={ring.id} className="gceon-ring" aria-hidden="true" style={{ left: ring.x, top: ring.y, "--d": `${(i % 3) * 0.16}s` } as CSSProperties} />
      ))}
      <p className="gceon-hint">
        <b>Toca el agua</b>
        Haz clic donde quieras
      </p>
    </div>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/efecto-ondas

Ejemplo de uso de Superficie de agua con ondas

Después de copiar efecto-ondas.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { EfectoOndas } from "./efecto-ondas";

export default function Ejemplo() {
  return <EfectoOndas />;
}

Propiedades de EfectoOndas

EfectoOndas acepta 2 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Superficie de agua con ondas con variables CSS

Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.

VariableValor por defecto
--d0s

Accesibilidad y rendimiento

El archivo tiene 60 líneas (4 KB) y define 2 animaciones con @keyframes. Esto es lo que hace Superficie de agua con ondas por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
  • Declara roles ARIA para lectores de pantalla: button.
  • Responde al teclado, no solo al ratón.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)