Temas

Iconos animados

glyphfox anima los iconos de dos maneras. Los estados vivos son presets como draw, pop o shake que se reproducen cuando se activa un disparador. Los morphs dan a un icono la forma de otro. Ambos funcionan en todos los paquetes y ambos se desactivan solos para quienes piden movimiento reducido.

Configuración

No hay nada extra que instalar: la animación forma parte de las props compartidas. Los ejemplos siguientes usan glyphfox/react; las props se llaman igual en Vue, Svelte, Angular y el web component, donde son atributos en kebab-case.

terminal
npm install glyphfox

Un estado vivo son unas pocas líneas de CSS que viajan dentro del propio SVG, en un elemento <style> acotado a una clase generada para ese icono. Por eso la misma animación funciona en un componente, en la cadena que devuelve toSvg, en un data URI o en un archivo exportado, sin ninguna hoja de estilos que importar.

Los siete presets

Cada preset está ajustado para iconos, no para elementos genéricos. draw recorre los trazos en el orden en que se dibujaron y, al final, hace aparecer la capa de relleno con un fundido. shake oscila desde arriba, como una campana colgada de un gancho. Los demás mueven el icono entero alrededor de su centro.

PresetMovimientoDuraciónEn bucle por defecto
drawdibuja cada trazo, uno tras otro1100 msno
popcrece desde el 55 por ciento con un ligero rebote520 msno
wigglegira a izquierda y derecha hasta asentarse720 msno
spinuna vuelta completa a velocidad constante1000 mssí
pulsese encoge y se atenúa un poco, y luego vuelve1500 mssí
bouncesalta hacia arriba y se asienta con dos rebotes más pequeños900 msno
shakeoscila desde arriba con una amplitud decreciente950 msno
Presets.jsx
import { LoaderCircleIcon, SparklesIcon, BellIcon, HeartIcon } from 'glyphfox/react';

export function Presets({ hasNews, liked }) {
  return (
    <>
      <LoaderCircleIcon animate="spin" />
      <SparklesIcon animate="draw" />
      <BellIcon animate="shake" trigger="state" active={hasNews} />
      <HeartIcon animate="pop" trigger="click" variant={liked ? 'solid' : 'outline'} />
    </>
  );
}

duration y delay se expresan en milisegundos. loop acepta true para repetir sin fin o un número de repeticiones; spin y pulse se repiten en bucle salvo que pases loop={false}. Un draw en bucle alterna la dirección, de modo que el icono se dibuja y luego se borra, en lugar de volver de golpe al principio.

Disparadores

La prop trigger decide cuándo se reproduce un preset:

  • mount, el valor predeterminado, lo reproduce cuando el icono se renderiza por primera vez.
  • hover lo reproduce mientras el puntero está sobre el icono o sobre su padre directo, así que un icono dentro de un botón se anima al pasar el puntero por el botón.
  • click lo reproduce de nuevo con cada clic en el icono.
  • state lo reproduce mientras active sea verdadero. Es el disparador que debes usar para todo lo que decide tu aplicación: una notificación nueva, un guardado fallido, una subida terminada.
Notifications.jsx
import { BellIcon } from 'glyphfox/react';

export function NotificationsButton({ unread }) {
  return (
    <button type="button" aria-label={`Notifications, ${unread} unread`}>
      <BellIcon animate="shake" trigger="state" active={unread > 0} loop={2} />
    </button>
  );
}

En el marcado estático, los disparadores se corresponden con simples ganchos de CSS. Una animación state se reproduce mientras el elemento svg tiene la clase gf-on, así que el HTML renderizado en el servidor y las cadenas de toSvg se pueden controlar con una sola línea de script: svg.classList.toggle('gf-on', unread > 0). hover no necesita ningún script.

Transforma un icono en otro

El motor de morph aplana ambos iconos en hebras, las empareja mediante una asignación óptima, las remuestrea con el mismo número de puntos e interpola. Las hebras sin pareja se encogen y se desvanecen. Cualquier par de iconos puede transformarse; diez pares están ajustados a mano para que parezcan diseñados y no calculados:

Hay tres formas de pedir un morph. MorphIcon se transforma cada vez que cambia su name. morphTo, en cualquier icono, lo transforma hacia un destino cuando se activa su disparador y lo devuelve a su forma original cuando este se desactiva. Los hooks y stores (useMorph en React y Vue, morphStore en Svelte) te entregan los fotogramas para que los renderices tú mismo.

CopyButton.jsx
import { useState } from 'react';
import { MorphIcon, MenuIcon, registerIcons } from 'glyphfox/react';
import { iconCopy, iconCheck, iconX } from 'glyphfox';

// Morph targets are looked up by name: register their data once.
registerIcons([iconCopy, iconCheck, iconX]);

export function CopyButton({ text }) {
  const [copied, setCopied] = useState(false);
  const copy = async () => {
    await navigator.clipboard.writeText(text);
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };
  return (
    <button type="button" onClick={copy}>
      <MorphIcon name={copied ? 'check' : 'copy'} duration={300} />
      {copied ? 'Copied' : 'Copy'}
    </button>
  );
}

export function MenuButton({ open, onToggle }) {
  return (
    <button type="button" aria-expanded={open} aria-label="Menu" onClick={onToggle}>
      <MenuIcon morphTo="x" trigger="state" active={open} />
    </button>
  );
}

Los morphs duran 450 ms salvo que pases duration. Con trigger="state", el icono sigue a active en ambas direcciones, lo que encaja con controles de dos estados como un menú, un campo de contraseña (de eye a eye-off) o un botón de reproducción.

Los mismos morphs en Vue, Svelte y el web component

PlayButton.vue
<script setup>
import { ref } from 'vue';
import { MorphIcon, registerIcons } from 'glyphfox/vue';
import { iconPlay, iconPause } from 'glyphfox';

registerIcons([iconPlay, iconPause]);
const playing = ref(false);
</script>

<template>
  <button type="button" :aria-label="playing ? 'Pause' : 'Play'" @click="playing = !playing">
    <MorphIcon :name="playing ? 'pause' : 'play'" />
  </button>
</template>
ThemeToggle.svelte
<script>
  import { Icon, morphStore, registerIcons } from 'glyphfox/svelte';
  import { iconSun, iconMoon } from 'glyphfox';

  registerIcons([iconSun, iconMoon]);
  const icon = morphStore('sun', { duration: 400 });
  let dark = $state(false);
</script>

<button type="button" aria-label="Dark theme" aria-pressed={dark}
  onclick={() => { dark = !dark; icon.set(dark ? 'moon' : 'sun'); }}>
  {#if $icon}<Icon icon={$icon.icon} variant={$icon.variant} />{/if}
</button>
index.html
<script src="https://cdn.jsdelivr.net/npm/glyphfox@1"></script>

<button type="button" id="menu" aria-expanded="false" aria-label="Menu">
  <glyph-fox name="menu" morph duration="300"></glyph-fox>
</button>

<script>
  const button = document.getElementById('menu');
  const icon = button.querySelector('glyph-fox');
  button.addEventListener('click', () => {
    const open = button.getAttribute('aria-expanded') !== 'true';
    button.setAttribute('aria-expanded', String(open));
    icon.setAttribute('name', open ? 'x' : 'menu');
  });
</script>

El atributo morph hace que <glyph-fox> anime cada cambio de name. La versión de la CDN ya incluye todos los iconos, así que ahí no hace falta registrar nada.

Morphs sin JavaScript

toSvg con morphTo devuelve un SVG cuyos trazados contienen la forma inicial y pasan a la de destino mediante una transición CSS de d. Los navegadores que no admiten d en CSS hacen, en su lugar, un fundido cruzado hacia el destino exacto. El resultado es una cadena normal, lista para plantillas de servidor, sitios estáticos o un CMS.

build-icons.js
import { toSvg } from 'glyphfox';

// Morphs while the pointer is over the icon or its direct parent.
const menu = toSvg('menu', { morphTo: 'x', trigger: 'hover', duration: 300 });

// Morphs while the svg has the class gf-on: toggle it from any script.
const play = toSvg('play', { morphTo: 'pause', trigger: 'state' });

La CLI genera el mismo archivo: npx glyphfox morph menu x --trigger hover --out menu.svg. Solo toSvg resuelve un destino indicado por su nombre; renderToString de glyphfox/core espera datos de icono en morphTo.

Clases de animación de Tailwind CSS

Los iconos renderizados con el plugin de Tailwind CSS son máscaras, no SVG, así que admiten animaciones del elemento completo: gf-spin, gf-pulse, gf-pop, gf-wiggle, gf-bounce y gf-shake, con los mismos tiempos que los presets. Funcionan con variantes como hover:gf-wiggle. Draw y los morphs necesitan SVG en línea.

Movimiento reducido

Cada preset incluye una regla prefers-reduced-motion: reduce que desactiva su animación, el morph solo con CSS cambia sin transición y los morphs con JavaScript de los componentes saltan directamente a la forma final. No necesitas comprobar el ajuste tú mismo. Quienes lo activan siguen viendo el estado final: el corazón sólido, la x de un menú abierto, la marca de verificación tras copiar.

El movimiento reducido no es la única razón para que las animaciones sean breves. El movimiento atrae la mirada, así que reserva las animaciones en bucle para lo que está en curso, como un loader-circle que gira mientras se ejecuta una solicitud, y detenlas cuando termine el trabajo. La guía de accesibilidad profundiza en el movimiento y el significado.

Exporta archivos animados

El personalizador del explorador de iconos exporta cualquier icono con un preset como SVG animado, GIF o video WebM, con exactamente el mismo movimiento: los fotogramas del GIF y del WebM se muestrean a partir de los tiempos del preset. Usa el SVG animado allí donde se acepte SVG, y GIF o WebM para diapositivas, herramientas de documentación y aplicaciones de chat que eliminan el SVG.

Rendimiento

  • Los presets distintos de draw solo animan transform y opacity, que los navegadores ejecutan sin recalcular el layout. draw anima stroke-dashoffset, lo que vuelve a pintar el icono en cada fotograma: está bien para unos pocos iconos, pero conviene evitarlo en listas largas.
  • Los morphs de los componentes se ejecutan con requestAnimationFrame y vuelven a renderizar el icono en cada fotograma mientras dura el morph. Unos pocos a la vez salen baratos; cientos que empiezan juntos, no.
  • Los nombres de clase generados son hashes de la configuración, así que iconos idénticos producen un marcado idéntico y la salida del servidor coincide con la del cliente.

Props de animación

Estas son las props que controlan el movimiento. Cualquier otra prop compartida, como variant o weight, se puede combinar con ellas.

PropTipoPor defectoDescripción
animatedraw, pop, wiggle, spin, pulse, bounce, shakeningunoPreset de estado vivo.
triggermount, hover, click, statemountCuándo se reproduce la animación o el morph.
activebooleanfalseEl estado para el disparador state.
durationnumber (ms)según el presetDuración de la animación o del morph.
delaynumber (ms)0Retraso de la animación o del morph.
loopboolean, numberfalseRepite sin fin o un número de veces (spin y pulse se repiten por defecto).
morphToname, dataningunoDestino del morph; el disparador decide cuándo se muestra.

Errores comunes

Destinos de morph que no renderizan nada
Los destinos de morph, los nombres de MorphIcon y los de morphStore se buscan por nombre. Un nombre cuyos datos nunca se importaron ni se registraron se carga bajo demanda, y en un build de producción de Vite esa carga no ocurre, porque Vite deja intacta la importación dinámica con variable que hay dentro de node_modules. El destino queda vacío. Registra con registerIcons todos los nombres que uses como destino de un morph. Importar un componente como CheckIcon solo por su efecto secundario no sirve, porque el tree shaking elimina un import que nunca se usa.
Hover y clic fuera del icono
Un preset hover en SVG en línea también responde al padre directo, gracias a CSS. No ocurre lo mismo con todo lo que funciona mediante eventos: trigger="click" y el disparador hover de un morph escuchan en el propio icono, y glyph-fox solo reacciona al puntero sobre el propio elemento. Un clic en el padding de un botón no les llega. Guarda el estado en el botón y pásalo al icono con trigger="state" y active.
Animaciones de montaje que se repiten
Una animación mount se reproduce cada vez que el icono se monta: cuando una lista se vuelve a renderizar con keys nuevas, cuando cambia la ruta y, en glyph-fox, cada vez que cambia uno de sus atributos. Si un icono debe animarse una sola vez, usa trigger="state" y decide en tu código cuándo se reproduce.
Animaciones de estado que nunca se detienen
Un preset con loop sigue reproduciéndose mientras active siga siendo verdadero. Vuelve a poner active en falso cuando se haya gestionado el evento, o asigna un número a loop.
Marcado estático y clics
En el HTML generado con toSvg, nada escucha los clics. Usa trigger: 'state' y alterna la clase gf-on desde tu propio manejador.

Preguntas frecuentes

¿Los iconos animados necesitan JavaScript?

Los estados vivos no: el CSS está dentro del SVG, así que un icono estático o renderizado en el servidor se anima al montarse y al pasar el puntero sin ningún script. El morph solo con CSS de toSvg también funciona sin framework. Los morphs de componentes como MorphIcon usan JavaScript para calcular los fotogramas.

¿Un icono puede transformarse y reproducir un preset a la vez?

Sí. animate y morphTo son props independientes, así que un corazón puede reproducir pop con un clic mientras se transforma a su estilo sólido: <HeartIcon animate="pop" morphTo="heart/solid" trigger="click" />.

Iconos de esta guía