Themen

Animierte Icons

glyphfox animiert Icons auf zwei Arten. Lebendige Zustände sind Presets wie draw, pop oder shake, die durch einen Auslöser starten. Morphs verwandeln ein Icon in ein anderes. Beides funktioniert in jedem Paket, und beides schaltet sich von selbst ab, wenn jemand reduzierte Bewegung eingestellt hat.

Einrichtung

Sie müssen nichts zusätzlich installieren: Animation ist Teil der gemeinsamen Props. Die folgenden Beispiele verwenden glyphfox/react; die Props heißen in Vue, Svelte und Angular genauso, ebenso in der Web Component, wo sie Attribute in Kebab-Case sind.

terminal
npm install glyphfox

Ein lebendiger Zustand besteht aus wenigen Zeilen CSS, die im SVG selbst mitgeliefert werden, und zwar in einem <style>-Element, das auf eine eigens für dieses Icon generierte Klasse beschränkt ist. Deshalb funktioniert dieselbe Animation in einer Komponente, im String aus toSvg, in einer Data-URI oder in einer exportierten Datei, ohne dass Sie ein Stylesheet importieren müssen.

Die sieben Presets

Jedes Preset ist auf Icons abgestimmt statt auf beliebige Elemente. draw zeichnet die Striche in der Reihenfolge nach, in der sie gezeichnet wurden, und blendet am Ende die Füllebene ein. shake pendelt vom oberen Ende aus, wie eine Glocke an einem Haken. Die übrigen bewegen das ganze Icon um seinen Mittelpunkt.

PresetBewegungDauerWiederholt sich standardmäßig
drawzeichnet jeden Strich nach, einen nach dem anderen1100 msnein
popwächst von 55 Prozent auf volle Größe, mit leichtem Überschwingen520 msnein
wiggledreht sich nach links und rechts und kommt zur Ruhe720 msnein
spinvolle Umdrehung mit konstanter Geschwindigkeit1000 msja
pulsewird etwas kleiner und blasser und kehrt dann zurück1500 msja
bouncespringt hoch und kommt mit zwei kleineren Sprüngen zur Ruhe900 msnein
shakependelt vom oberen Ende aus mit abnehmender Amplitude950 msnein
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 und delay erwarten Millisekunden. loop akzeptiert true für endlose Wiederholung oder eine Anzahl von Durchläufen; spin und pulse wiederholen sich, sofern Sie nicht loop={false} übergeben. Läuft draw in einer Schleife, wechselt es jedes Mal die Richtung: Das Icon zeichnet sich erst und radiert sich dann wieder aus, statt zurückzuspringen.

Auslöser

Die Prop trigger legt fest, wann ein Preset abgespielt wird:

  • mount, der Standard, startet die Animation, sobald das Icon zum ersten Mal gerendert wird.
  • hover spielt die Animation ab, solange sich der Zeiger über dem Icon oder seinem direkten Elternelement befindet. Ein Icon in einem Button wird also animiert, wenn der Zeiger über dem Button liegt.
  • click spielt die Animation bei jedem Klick auf das Icon erneut ab.
  • state spielt die Animation ab, solange active den Wert true hat. Diesen Auslöser verwenden Sie für alles, worüber Ihre Anwendung entscheidet: eine neue Benachrichtigung, ein fehlgeschlagenes Speichern, ein abgeschlossener Upload.
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>
  );
}

In statischem Markup entsprechen die Auslöser einfachen CSS-Hooks. Eine state-Animation läuft, solange das Element svg die Klasse gf-on trägt. Serverseitig gerendertes HTML und Strings aus toSvg lassen sich daher mit einer einzigen Zeile Skript steuern: svg.classList.toggle('gf-on', unread > 0). hover kommt ganz ohne Skript aus.

Ein Icon in ein anderes morphen

Die Morph-Engine zerlegt beide Icons in Stränge, ordnet sie über eine optimale Zuweisung einander zu, tastet sie auf dieselbe Anzahl von Punkten neu ab und interpoliert. Stränge ohne Gegenstück schrumpfen und werden ausgeblendet. Zwei beliebige Icons lassen sich morphen; zehn Paare sind von Hand abgestimmt, damit sie gestaltet statt berechnet wirken:

Es gibt drei Wege, einen Morph anzufordern. MorphIcon morpht, sobald sich sein name ändert. morphTo an einem beliebigen Icon morpht zu einem Ziel, sobald sein Auslöser eintritt, und wieder zurück, wenn er endet. Hooks und Stores (useMorph in React und Vue, morphStore in Svelte) liefern Ihnen die Frames für Ihr eigenes Rendering.

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>
  );
}

Morphs dauern 450 ms, sofern Sie nicht duration übergeben. Mit trigger="state" folgt das Icon active in beide Richtungen. Das passt zu Umschaltern wie einem Menü, einem Passwortfeld (eye zu eye-off) oder einem Play-Button.

Dieselben Morphs in Vue, Svelte und der 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>

Mit dem Attribut morph animiert <glyph-fox> jede Änderung von name. Der CDN-Build enthält bereits alle Icons, dort ist also keine Registrierung nötig.

Morphs ohne JavaScript

toSvg mit morphTo liefert ein SVG, dessen Pfade die Ausgangsform enthalten und über eine CSS-Transition der Eigenschaft d in die Zielform übergehen. Browser, die d in CSS nicht unterstützen, blenden stattdessen exakt zum Ziel über. Das Ergebnis ist ein einfacher String, bereit für Server-Templates, statische Websites oder ein 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' });

Die CLI schreibt dieselbe Datei: npx glyphfox morph menu x --trigger hover --out menu.svg. Nur toSvg löst ein als Name angegebenes Ziel auf; renderToString aus glyphfox/core erwartet für morphTo Icon-Daten.

Animationsklassen für Tailwind CSS

Icons, die mit dem Tailwind-CSS-Plugin gerendert werden, sind Masken und kein SVG. Sie eignen sich daher für Animationen des ganzen Elements: gf-spin, gf-pulse, gf-pop, gf-wiggle, gf-bounce und gf-shake, mit demselben Timing wie die Presets. Sie funktionieren mit Varianten wie hover:gf-wiggle. Draw und Morphs erfordern Inline-SVG.

Reduzierte Bewegung

Jedes Preset bringt eine Regel für prefers-reduced-motion: reduce mit, die seine Animation abschaltet, der reine CSS-Morph wechselt ohne Übergang, und die JavaScript-Morphs der Komponenten springen direkt zur Endform. Sie müssen die Einstellung nicht selbst prüfen. Wer sie aktiviert hat, sieht trotzdem den Endzustand: das Herz im Solid-Stil, das x eines geöffneten Menüs, das Häkchen nach dem Kopieren.

Reduzierte Bewegung ist nicht der einzige Grund, Animationen kurz zu halten. Bewegung zieht den Blick auf sich. Reservieren Sie Endlosanimationen daher für laufende Vorgänge, etwa ein loader-circle, das sich dreht, während eine Anfrage läuft, und beenden Sie sie, sobald die Arbeit erledigt ist. Mehr zu Bewegung und Bedeutung finden Sie im Leitfaden zur Barrierefreiheit.

Animierte Dateien exportieren

Der Konfigurator im Icon-Browser exportiert jedes Icon mit einem Preset als animiertes SVG, als GIF oder als WebM-Video, mit exakt derselben Bewegung: Die Frames von GIF und WebM werden aus dem Timing des Presets abgetastet. Verwenden Sie das animierte SVG überall, wo SVG akzeptiert wird, und GIF oder WebM für Folien, Dokumentationstools und Chat-Apps, die SVG entfernen.

Performance

  • Alle Presets außer draw animieren nur transform und opacity, was Browser ohne Layout-Berechnung ausführen. draw animiert stroke-dashoffset, wodurch das Icon in jedem Frame neu gezeichnet wird: unproblematisch bei wenigen Icons, in langen Listen besser zu vermeiden.
  • Morphs in Komponenten laufen über requestAnimationFrame und rendern das Icon für die Dauer des Morphs in jedem Frame neu. Eine Handvoll gleichzeitig ist günstig, Hunderte, die zusammen starten, sind es nicht.
  • Die generierten Klassennamen sind Hashes der Einstellungen. Identische Icons erzeugen daher identisches Markup, und die Ausgaben von Server und Client stimmen überein.

Animations-Props

Das sind die Props, die Bewegung steuern. Jede andere gemeinsame Prop, etwa variant oder weight, lässt sich mit ihnen kombinieren.

PropTypStandardBeschreibung
animatedraw, pop, wiggle, spin, pulse, bounce, shakekeinerVoreinstellung für den lebendigen Zustand.
triggermount, hover, click, statemountWann die Animation oder der Morph abläuft.
activebooleanfalseDer Zustand für den Auslöser state.
durationnumber (ms)je nach VoreinstellungDauer der Animation oder des Morphs.
delaynumber (ms)0Verzögerung der Animation oder des Morphs.
loopboolean, numberfalseEndlos oder eine bestimmte Anzahl von Malen wiederholen (spin und pulse laufen standardmäßig in Schleife).
morphToname, datakeinerZiel des Morphs; der Auslöser bestimmt, wann es erscheint.

Stolperfallen

Morph-Ziele, die nichts rendern
Morph-Ziele sowie die Namen für MorphIcon und morphStore werden über den Namen nachgeschlagen. Ein Name, dessen Daten nie importiert oder registriert wurden, wird bei Bedarf nachgeladen, und in einem Vite-Produktionsbuild findet dieses Nachladen nicht statt, weil Vite den variablen dynamischen Import innerhalb von node_modules unverändert lässt. Das Ziel bleibt leer. Registrieren Sie jeden Namen, zu dem Sie morphen, mit registerIcons. Eine Komponente wie CheckIcon nur wegen ihres Seiteneffekts zu importieren, hilft nicht, denn Tree-Shaking entfernt einen Import, der nie verwendet wird.
Hover und Klick außerhalb des Icons
Ein Hover-Preset auf Inline-SVG reagiert per CSS auch auf das direkte Elternelement. Für alles, was über Events gesteuert wird, gilt das nicht: trigger="click" und der Hover-Auslöser eines Morphs hören auf Events am Icon selbst, und glyph-fox reagiert nur auf den Zeiger direkt über dem Element. Ein Klick auf das Padding eines Buttons erreicht sie nicht. Halten Sie den Zustand im Button und geben Sie ihn mit trigger="state" und active nach unten weiter.
Mount-Animationen, die erneut ablaufen
Eine mount-Animation läuft jedes Mal, wenn das Icon gemountet wird: wenn eine Liste mit neuen Keys neu gerendert wird, bei einem Routenwechsel und bei glyph-fox immer dann, wenn sich eines seiner Attribute ändert. Soll ein Icon nur einmal animiert werden, verwenden Sie trigger="state" und entscheiden in Ihrem Code, wann die Animation läuft.
Zustandsanimationen, die nie aufhören
Ein Preset mit loop läuft weiter, solange active true bleibt. Setzen Sie active wieder auf false, sobald das Ereignis behandelt ist, oder geben Sie loop eine Zahl.
Statisches Markup und Klicks
Im HTML aus toSvg wartet nichts auf Klicks. Verwenden Sie trigger: 'state' und schalten Sie die Klasse gf-on in Ihrem eigenen Handler um.

Häufige Fragen

Brauchen animierte Icons JavaScript?

Lebendige Zustände nicht: Das CSS steckt im SVG, daher wird ein serverseitig gerendertes oder statisches Icon beim Mounten und beim Hovern ganz ohne Skript animiert. Auch der reine CSS-Morph aus toSvg läuft ohne Framework. Komponenten-Morphs wie MorphIcon berechnen die Frames mit JavaScript.

Kann ein Icon gleichzeitig morphen und ein Preset abspielen?

Ja. animate und morphTo sind voneinander unabhängige Props. So kann ein Herz beim Klick mit pop aufspringen, während es in den Solid-Stil morpht: <HeartIcon animate="pop" morphTo="heart/solid" trigger="click" />.

Icons in diesem Leitfaden