Frameworks

Icons in SvelteKit

glyphfox/svelte rendert auf dem Server, daher liefern SvelteKit-Seiten echtes SVG im HTML aus, ganz gleich, ob eine Seite pro Anfrage gerendert oder zur Build-Zeit vorgerendert wird.

Installation

terminal
npm install glyphfox

Es gibt nichts zu konfigurieren. glyphfox/svelte wird unter der Export-Condition svelte bereitgestellt. Daher erkennt vite-plugin-svelte, das jedes SvelteKit-Projekt verwendet, das Paket als Svelte-Bibliothek und kompiliert seine Komponenten für Server und Browser. Die Komponenten setzen Svelte 5 voraus; die Svelte-Anleitung behandelt Komponenten und Props im Detail.

Icon-Namen im Root-Layout registrieren

Komponenten wie HomeIcon registrieren ihre Daten selbst. Namen, die nur in Strings vorkommen, also in Icon, Fusion, MorphIcon oder morphTo, benötigen registerIcons. Der beste Ort dafür ist ein Modul, das das Root-Layout importiert: Es läuft auf dem Server vor dem ersten Rendering und im Browser vor der Hydration.

src/lib/icons.js
import { registerIcons } from 'glyphfox/svelte';
import menu from 'glyphfox/icons/menu';
import x from 'glyphfox/icons/x';
import moon from 'glyphfox/icons/moon';
import file from 'glyphfox/icons/file';

registerIcons([menu, x, moon, file]);
src/routes/+layout.svelte
<script>
  import '$lib/icons.js';
  import { MorphIcon } from 'glyphfox/svelte';

  let { children } = $props();
  let open = $state(false);
</script>

<header>
  <button type="button" aria-label="Menu" aria-expanded={open} onclick={() => (open = !open)}>
    <MorphIcon name={open ? 'x' : 'menu'} />
  </button>
</header>

{@render children()}

Die Registry ist Modulzustand. Auf dem Server wird sie von allen Anfragen geteilt, was unbedenklich ist, weil sich Icon-Daten nie ändern, und sie wird einmalig befüllt, wenn das Modul zum ersten Mal geladen wird.

Was der Server rendert

Icon-Komponenten erzeugen beim serverseitigen Rendering ihr vollständiges SVG. Die Animationen lebendiger Zustände sind ein Stylesheet im SVG, daher dreht sich ein Spinner bereits im Server-HTML, es sei denn, Besucher bevorzugen reduzierte Bewegung. Morphs sowie die Auslöser für Klick und Hover starten erst nach der Hydration, weil $effect nicht auf dem Server läuft.

Für einen Morph, bei dem Daten bestimmen, an welchem Ende er steht, verwenden Sie trigger="state" mit active. Der Server rendert das passende Ende des Morphs, und die Animation läuft nur, wenn sich der Wert im Browser ändert. Mit dem Standardauslöser (mount) rendert der Server das Ausgangs-Icon, und das Morphing läuft bei jedem Seitenaufruf ab.

src/routes/+layout.server.js
export function load({ cookies }) {
  return { theme: cookies.get('theme') ?? 'light' };
}
src/lib/ThemeToggle.svelte
<script>
  import { SunIcon } from 'glyphfox/svelte';

  let { theme } = $props();
  let dark = $state(theme === 'dark');

  function toggle() {
    dark = !dark;
    document.cookie = `theme=${dark ? 'dark' : 'light'}; path=/; max-age=31536000`;
  }
</script>

<button type="button" aria-label="Dark theme" aria-pressed={dark} onclick={toggle}>
  <SunIcon morphTo="moon" trigger="state" active={dark} />
</button>

Wenn Sie das Theme in einer serverseitigen Load-Funktion aus einem Cookie lesen, starten Server und Browser mit demselben Wert. Rendern Sie den Umschalter im Root-Layout mit <ThemeToggle theme={data.theme} />; moon ist in src/lib/icons.js registriert.

SVG-Dateien aus einem Endpunkt

toSvg aus dem Haupteinstiegspunkt glyphfox rendert jede Spezifikation zu einem String. Damit lässt sich eine +server.js-Route anlegen, die Icons als Dateien ausliefert: ein Favicon, ein Bild für ein E-Mail-Template oder eine Linkvorschau. Mit entries werden die Dateien zur Build-Zeit vorgerendert.

src/routes/icons/[name].svg/+server.js
import { error } from '@sveltejs/kit';
import { iconNames, toSvg } from 'glyphfox';

export const prerender = true;

export function entries() {
  return ['fox', 'home', 'search'].map((name) => ({ name }));
}

export function GET({ params }) {
  if (!iconNames.includes(params.name)) error(404, 'Unknown icon');
  const svg = toSvg(params.name, { size: 32, color: '#ff7a1a', variant: 'duotone' });
  return new Response(svg, { headers: { 'content-type': 'image/svg+xml' } });
}
src/app.html
<link rel="icon" href="/icons/fox.svg" type="image/svg+xml" />

Eine eigenständige SVG-Datei hat kein Elternelement, von dem sie eine Farbe erben könnte. Übergeben Sie daher color: Der Wert wird am svg-Element gesetzt, und die Striche mit currentColor übernehmen ihn. Damit jedes Icon vorgerendert wird, lassen Sie entries den Ausdruck iconNames.map((name) => ({ name })) zurückgeben.

Der Haupteinstiegspunkt enthält die Daten aller Icons. In +server.js- und +page.server.js-Dateien, die nie den Browser erreichen, ist das unproblematisch; in eine Komponente importiert, würde toSvg das gesamte Set an den Client ausliefern.

Rein serverseitiges Markup mit {@html}

Eine Load-Funktion in +page.server.js kann gerenderte Strings zurückgeben, die die Seite mit {@html} einfügt. Der Browser erhält das Markup im HTML und in den serialisierten Seitendaten, aber keinen Icon-Code.

src/routes/+page.server.js
import { toSvg } from 'glyphfox';

const features = [
  { icon: 'zap', title: 'Fast' },
  { icon: 'shield-check', title: 'Safe' },
  { icon: 'accessibility', title: 'Accessible' },
];

export function load() {
  return {
    features: features.map((f) => ({ ...f, svg: toSvg(f.icon, { size: 28, weight: 1.5 }) })),
  };
}
src/routes/+page.svelte
<script>
  let { data } = $props();
</script>

<ul class="features">
  {#each data.features as feature (feature.icon)}
    <li>{@html feature.svg} {feature.title}</li>
  {/each}
</ul>

{@html} ist hier sicher, weil die Strings aus Ihrem eigenen Servercode stammen und nie aus Benutzereingaben. Ohne title werden die Icons mit aria-hidden gerendert, was neben einer sichtbaren Beschriftung genau richtig ist.

adapter-static und Prerendering

Mit adapter-static oder mit export const prerender = true in einer beliebigen Route werden Seiten zur Build-Zeit gerendert und die Icons in die HTML-Dateien geschrieben. Die Regeln des serverseitigen Renderings gelten auch zur Build-Zeit: Ein Name, den der Build nicht auflösen kann, wird als leeres SVG geschrieben.

svelte.config.js
import adapter from '@sveltejs/adapter-static';

export default {
  kit: { adapter: adapter() },
};
src/routes/+layout.js
export const prerender = true;

Für Seiten, die kein JavaScript benötigen, ergänzen Sie export const csr = false: Die Icons bleiben statisches SVG, und es wird kein Komponentencode übertragen. CSS-Animationen laufen weiterhin, Morphs und die Auslöser für Klick oder Hover hingegen, die JavaScript benötigen, bewirken nichts.

Namen in Produktions-Builds

In der Entwicklung bündelt Vite das Paket vorab, daher scheint jeder Name zu funktionieren. Im Produktions-Build bündelt Vite den On-Demand-Loader nicht, den glyphfox in node_modules mitbringt, weil Vite dynamische Importe mit Variablen in Abhängigkeiten überspringt, und nicht registrierte Namen bleiben leer. Registrieren Sie diese Namen, oder lassen Sie Vite den Loader bündeln, der dann pro Icon einen kleinen Chunk erzeugt:

vite.config.js
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [sveltekit()],
  build: {
    dynamicImportVarsOptions: { exclude: [/node_modules\/(?!glyphfox\/)/] },
  },
});

Diese Chunks helfen nur im Browser nach der Hydration. Das serverseitige Rendering wartet nicht auf sie, daher bleibt die Registrierung der Weg, ein Icon ins HTML zu bringen.

Props

SvelteKit verwendet die Komponenten aus glyphfox/svelte, daher gelten die gemeinsamen Props in camelCase.

PropTypStandardBeschreibung
sizenumber, string24Breite und Höhe; Zahlen sind Pixel.
colorstringcurrentColorHauptfarbe.
secondaryColorstringcolorFarbe der Duotone-Ebene und der Solid-Füllung.
secondaryOpacitynumber0.3 / 1Deckkraft der Duotone-Ebene oder der Solid-Füllung.
weightnumber2Strichstärke von 1 bis 3 im 24er-Raster, unter 24 px optisch ausgeglichen.
absoluteWeightbooleanfalseHält den Strich bei jeder Größe auf weight Pixeln.
cornersround, sharproundAbgerundete oder spitze Verbindungen und Enden.
variantoutline, duotone, solidoutlineStilachse; solid entsteht aus den geschlossenen Formen.
rotatenumber0Drehung in Grad.
flipXbooleanfalseHorizontal spiegeln.
flipYbooleanfalseVertikal spiegeln.
mirrorRtlbooleanfalseIn Layouts von rechts nach links spiegeln.
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).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotkeinerBadge mit automatischer Aussparung.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightEcke des Badges.
morphToname, datakeinerZiel des Morphs; der Auslöser bestimmt, wann es erscheint.
titlestringkeinerZugänglicher Name: title, role img und aria-label.
decorativebooleantrue ohne titleVerbirgt das Icon vor assistiven Technologien.

Stolperfallen

Registrierung in onMount oder in reinem Browser-Code
Code in onMount, in $effect oder hinter if (browser) läuft erst, nachdem der Server gerendert hat. Das HTML enthält dann leere Icons, die erst nach der Hydration auftauchen. Registrieren Sie in einem Modul, das das Root-Layout importiert.
Ein anderes Icon auf dem Server als im Browser
Der Inhalt des svg-Elements wird mit {@html} eingefügt, und Svelte behält das Server-Markup eines {@html}-Blocks bei, wenn das erste Rendering im Client davon abweicht (in der Entwicklung warnt Svelte dann mit hydration_html_changed). Ein Icon, das anhand von localStorage oder window gewählt wird, kann daher bis zur nächsten Änderung falsch bleiben. Leiten Sie den ersten Wert aus Daten ab, die auch der Server hat, etwa aus einem Cookie, das in einer Load-Funktion gelesen wird.
toSvg in einer universellen Komponente
Wenn Sie toSvg aus glyphfox in einer .svelte-Datei importieren, gelangen die Daten aller Icons ins Client-Bundle. Verwenden Sie es nur in Server-Dateien, oder greifen Sie auf die Komponenten zurück.
Ein vorgerenderter Morph im falschen Zustand
Mit dem Auslöser mount zeigt das vorgerenderte HTML das Ausgangs-Icon, und das Morphing läuft nach der Hydration ab. Verwenden Sie trigger="state" für alles, was in seinem Endzustand vorgerendert werden soll.

Häufige Fragen

Kann ich jedes Icon als SVG-Datei vorrendern?

Ja. Lassen Sie entries im Endpunkt oben iconNames.map((name) => ({ name })) zurückgeben, dann schreibt der Build pro Icon eine Datei unter /icons/. Jede Datei ist einige hundert Byte groß.

Funktionieren Icons auf Seiten mit csr = false?

Ja. Der Server schreibt das SVG ins HTML, und die Animationen lebendiger Zustände laufen weiterhin, weil sie CSS im SVG sind. Morphs und die Auslöser für Klick und Hover benötigen JavaScript und bleiben daher auf ihrem ersten Frame stehen.

Icons in diesem Leitfaden