Animación de Criatura Marina con SVG y JavaScript

Los gráficos vectoriales escalables (SVG) representan un formato fundamental para el diseño web interactivo, al permitir la generación y manipulación de imágenes mediante código. Al integrarlos con JavaScript, se desbloquea la posibilidad de crear efectos dinámicos y atractivos para el usuario.

Principios de los Gráficos SVG

SVG es un dialecto de XML diseñado para describir gráficos en dos dimensiones con formas como círculos, rectángulos y curvas complejas. Su ventaja principal reside en la independencia de la resolución, ya que la imagen se calcula en el navegador, permitiendo un escalado sin pérdida de nitidez. Esta característica, sumada a su integración directa con el Document Object Model (DOM), lo convierte en la base ideal para gráficos interactivos.

Anatomía Básica de un Elemento SVG

Un documento SVG se inicia con la etiqueta contenedora <svg>, que establece el espacio de trabajo. Los elementos gráficos se anidan dentro. Por ejemplo, un rectángulo básico se define así:

<svg width="200" height="150" style="border: 1px solid #ccc;">
  <rect x="20" y="20" width="100" height="80" rx="10" fill="#64b5f6" />
</svg>

Control Dinámico Mediante JavaScript

JavaScript permite modificar en tiempo real las propiedades de los elementos SVG, como posición, color o escala. Esto habilita la creación de animaciones fluidas basadas en eventos del usuario, tales como el movimiento del puntero.

Implementación de una Criatura Interactiva

El siguiente ejemplo desarrolla una composición gráfica compuesta por múltiples segmentos que persiguen suavemente la posición del ratón, generando un efecto fluido y orgánico.

Estructura HTML

Se define un contenedor principal que albergará la animación.

<div id="canvas-container"></div>

Lógica de Animación en JavaScript

La animación se construye alrededor de un bucle principal que actualiza periódicamente la posición de cada componente visual en relación al cursor.

class FollowCreature {
  constructor(containerId) {
    this.container = document.getElementById(containerId);
    this.cursorPos = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
    this.segments = [];
    this.maxVelocity = 8;
    this.smoothingFactor = 10;
    this.frameInterval = 1000 / 30; // 30 FPS

    this._initializeSegments();
    this._bindEvents();
    this._startAnimationLoop();
  }

  _initializeSegments() {
    // Definición de segmentos con sus propiedades iniciales y retardos
    const segmentData = [
      { delay: 0, color: '#1565c0', radius: 15 },
      { delay: 1, color: '#1976d2', radius: 12 },
      { delay: 2, color: '#2196f3', radius: 9 },
      { delay: 3, color: '#64b5f6', radius: 6 },
      { delay: 4, color: '#90caf9', radius: 4 },
    ];

    this.segments = segmentData.map(data => ({
      current: { x: this.cursorPos.x, y: this.cursorPos.y },
      target: { ...this.cursorPos },
      ...data
    }));
  }

  _bindEvents() {
    document.addEventListener('mousemove', (event) => {
      this.cursorPos.x = event.clientX;
      this.cursorPos.y = event.clientY;
    });
  }

  _startAnimationLoop() {
    setInterval(() => this._updatePositions(), this.frameInterval);
  }

  _updatePositions() {
    // Se calcula un objetivo intermedio para cada segmento, basado en el segmento anterior
    for (let i = 0; i < this.segments.length; i++) {
      const segment = this.segments[i];
      const targetPosition = i === 0 ? this.cursorPos : this.segments[i - 1].current;

      const deltaX = targetPosition.x - segment.current.x;
      const deltaY = targetPosition.y - segment.current.y;

      // Aplicar suavizado y límite de velocidad
      segment.current.x += this._clampVelocity(deltaX) / this.smoothingFactor;
      segment.current.y += this._clampVelocity(deltaY) / this.smoothingFactor;
    }
    this._render();
  }

  _clampVelocity(value) {
    return Math.max(-this.maxVelocity, Math.min(this.maxVelocity, value));
  }

  _render() {
    let svgContent = `<svg width="100%" height="100%" style="pointer-events: none; position: fixed; top: 0; left: 0;">`;
    
    this.segments.forEach(segment => {
      svgContent += `<circle cx="${segment.current.x}" cy="${segment.current.y}" r="${segment.radius}" fill="${segment.color}" opacity="0.85" />`;
    });

    svgContent += `</svg>`;
    this.container.innerHTML = svgContent;
  }
}

// Iniciar la animación
const creature = new FollowCreature('canvas-container');

Explicación del Código

  1. Clase FollowCreature: Encapsula toda la lógica. El constructor inicializa las propiedades, crea los segmentos y arranca el ciclo.
  2. Inicialización de Segmentos: Cada segmento tiene una posición actual, un color, un radio y un valor de delay que se usará para crear el efecto de "cola".
  3. Ciclo de Animación: _updatePositions itera sobre los segmentos. El objetivo de cada segmento (excepto el primero) es la posición actual del segmento que le precede en el array, no la posición del cursor directamente. Esto genera el movimiento en cadena.
  4. Suavizado y Límite: La posición se actualiza fraccionando la diferencia (deltaX/Y) por un factor de suavizado, y se restringe la velocidda máxima con _clampVelocity para evitar movimientos bruscos.
  5. Renderizado SVG: El método _render construye dinámicamente un documento SVG con círculos (<circle>) que representan cada segmento y lo inyecta en el contenedor.

Código Completo y Funcional


<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Criatura SVG Interactiva</title>
  <style>
    body, html {
      margin: 0;
      padding: 0;
      overflow: hidden;
      height: 100%;
      background-color: #0a1929;
    }
    #canvas-container {
      position: absolute;
      width: 100%;
      height: 100%;
    }
  </style>
</head>
<body>
  <div id="canvas-container"></div>

  <script>
    class FollowCreature {
      // ... (El código completo de la clase definido anteriormente)
    }
    const creature = new FollowCreature('canvas-container');
  </script>
</body>
</html>

Principios de Diseño y Optimización

La arquitectura anterior ilustra cómo los datos (posición de segmentos), la lógica (cálculos de movimiento) y la presentación (generación del SVG) están claramente separados. Para optimizar el rendimiento, en aplicaciones complejas se podría utilizar requestAnimationFrame en lugar de setInterval, y manejar los elementos SVG como nodos DOM en lugar de regenerar todo el contenido como un string en cada fotograma.

Etiquetas: SVG JavaScript animación-web gráficos-vectoriales desarrollo-web

Publicado el 6-3 17:21