Icons in Qwik
Die meisten Icons ändern sich nach dem Laden der Seite nie. In Qwik können sie reines SVG sein, das der Server schreibt, ohne dass Komponentencode an den Browser geht. Für die wenigen Icons, die morphen oder reagieren, übernimmt das Element <glyph-fox> die Interaktion selbst.
Installation
Installieren Sie das Kernpaket. Der serverseitige Ansatz nutzt glyphfox/core und ein Modul pro Icon aus glyphfox/icons/NAME; das Element stammt aus glyphfox/web. Weder in Vite noch in Qwik muss etwas konfiguriert werden.
npm install glyphfox
Die Beispiele importieren aus @builder.io/qwik. In Qwik 2 wandern diese Importe nach @qwik.dev/core; auf der Seite von glyphfox bleibt alles gleich.
Auf dem Server gerenderte Icons
renderToString liefert das SVG-Markup für Icon-Daten und die gemeinsamen Props. Qwik fügt rohes Markup mit dangerouslySetInnerHTML ein, das in Qwik direkt den String erwartet und nicht wie in React ein Objekt.
import type { IconData, SvgOptions } from 'glyphfox'; import { renderToString } from 'glyphfox/core'; type Props = SvgOptions & { icon: IconData; class?: string; }; export const Icon = ({ icon, class: className, ...options }: Props) => ( <span class={className} style="display:inline-flex;line-height:0" dangerouslySetInnerHTML={renderToString(icon, options)} /> );
Icon ist eine einfache Funktion, in Qwik-Terminologie eine Inline-Komponente. Sie rendert als Teil ihrer Elternkomponente und hat keine eigene Lazy-Loading-Grenze. Auf dem Server schreibt die Elternkomponente sie ins HTML, und solange die Elternkomponente im Browser nicht erneut rendert, wird für sie nie Code heruntergeladen.
import { component$, Slot } from '@builder.io/qwik'; import home from 'glyphfox/icons/home'; import settings from 'glyphfox/icons/settings'; import bookOpen from 'glyphfox/icons/book-open'; import { Icon } from '~/components/icon/icon'; export default component$(() => ( <div class="shell"> <nav class="sidebar"> <a href="/"><Icon icon={home} size={20} /> Home</a> <a href="/docs/"><Icon icon={bookOpen} size={20} /> Docs</a> <a href="/settings/"><Icon icon={settings} size={20} weight={1.5} /> Settings</a> </nav> <main> <Slot /> </main> </div> ));
Rendert eine Komponente im Client erneut, etwa weil sie ein Signal liest, das sich ändert, lädt Qwik ihre Render-Funktion und die von ihr importierten Module herunter, einschließlich des Renderers aus glyphfox/core. Platzieren Sie Icons in Komponenten, die nur einmal rendern, und lagern Sie die veränderlichen Teile in eigene Komponenten aus.
Animationen ohne JavaScript
Das Markup aus renderToString bringt sein eigenes CSS mit. Mit animate spielt es ein Preset ab, und mit morphTo und trigger: 'hover' morpht es, solange sich der Zeiger über dem svg-Element oder seinem direkten Elternelement befindet. Beides kommt ohne Skript aus und stoppt bei prefers-reduced-motion.
import arrowRight from 'glyphfox/icons/arrow-right'; import chevronRight from 'glyphfox/icons/chevron-right'; import loader from 'glyphfox/icons/loader'; import { Icon } from '~/components/icon/icon'; export const Next = () => ( <a href="/next/" class="next"> Next <Icon icon={arrowRight} morphTo={chevronRight} trigger="hover" /> </a> ); export const Saving = () => <Icon icon={loader} animate="spin" title="Saving" />;
Mit dem Core erwartet morphTo Icon-Daten, keinen Namen: Importieren Sie das Ziel-Icon wie oben. Ein String wird ignoriert, und das Icon bleibt unbewegt.
Für einen Zustand wie geöffnet oder ausgewählt rendern Sie mit trigger: 'state': Das svg-Element morpht dann, sobald es die Klasse gf-on erhält. Das Umschalten einer Klasse erfordert kein erneutes Rendern, daher ist der einzige Code, den der Browser herunterlädt, der Klick-Handler, und auch der erst beim ersten Klick.
import { component$ } from '@builder.io/qwik'; import menu from 'glyphfox/icons/menu'; import x from 'glyphfox/icons/x'; import { Icon } from '~/components/icon/icon'; export const MenuToggle = component$(() => ( <button type="button" aria-label="Menu" aria-expanded="false" onClick$={(_, button) => { const open = button.getAttribute('aria-expanded') !== 'true'; button.setAttribute('aria-expanded', String(open)); button.querySelector('svg')?.classList.toggle('gf-on', open); }} > <Icon icon={menu} morphTo={x} trigger="state" /> </button> ));
Interaktive Icons mit dem Element
Wenn ein Icon zwischen Namen wechseln, bei jeder Änderung morphen oder selbstständig auf Klicks reagieren soll, verwenden Sie das Element <glyph-fox>. Laden Sie es aus einem Visible Task in der Komponente, die es verwendet, damit der Browser es nur abruft, wenn ein solches Icon auf dem Bildschirm ist.
import { component$, useVisibleTask$ } from '@builder.io/qwik'; export const LikeButton = component$(() => { // eslint-disable-next-line qwik/no-use-visible-task useVisibleTask$(() => { import('glyphfox/web'); }); return ( <button type="button" class="like" aria-label="Like"> <glyph-fox name="heart" morph-to="heart/solid" trigger="click"></glyph-fox> </button> ); });
Schreiben Sie die Props des Elements wie in HTML als String-Attribute: name, size, morph, morph-to, trigger. Mit einem Klick- oder Hover-Auslöser verwaltet das Element seinen Zustand selbst, daher rendert die Komponente nie erneut, und für sie fällt in Qwik kein Resuming an.
glyphfox/web enthält alle Icons. Um weniger zu laden, importieren Sie aus dem Task ein eigenes Modul, das mit defineGlyphFox und registerIcons eine Teilmenge registriert, wie in der Anleitung zu Web Components. Falls Ihr TypeScript-Setup das unbekannte Tag ablehnt, deklarieren Sie glyph-fox in einer Deklarationsdatei unter den intrinsischen JSX-Elementen.
Das Element zeichnet erst nach seinem Upgrade, daher enthält das Server-HTML ein leeres Tag. Reservieren Sie seine Größe per CSS, damit sich das Layout nicht verschiebt:
glyph-fox:not(:defined) { display: inline-block; width: 24px; height: 24px; }
Qwik City
Layouts und Routen rendern auf dem Server, daher funktioniert die Komponente Icon in src/routes/layout.tsx, in Seiten und in jeder Komponente, die diese rendern. Die statische Seitengenerierung schreibt dasselbe Markup in HTML-Dateien.
Wenn Icon-Namen aus Daten stammen, lösen Sie sie auf dem Server auf. In einem routeLoader$ akzeptiert toSvg aus glyphfox jeden Namen und die vollständige Namenssyntax; es enthält alle Icons, was in reinem Servercode unproblematisch ist.
import { component$ } from '@builder.io/qwik'; import { routeLoader$ } from '@builder.io/qwik-city'; import { toSvg } from 'glyphfox'; const FEATURES = [ { icon: 'shield-check', label: 'Audited' }, { icon: 'cloud&lock:overlap', label: 'Encrypted backups' }, { icon: 'file+check@tr', label: 'Signed releases' }, ]; export const useFeatures = routeLoader$(() => FEATURES.map((f) => ({ ...f, svg: toSvg(f.icon, { size: 28, weight: 1.75 }) })), ); export default component$(() => { const features = useFeatures(); return ( <ul class="features"> {features.value.map((f) => ( <li key={f.label}> <span dangerouslySetInnerHTML={f.svg} /> {f.label} </li> ))} </ul> ); });
Soll das Element stattdessen auf jeder Seite verfügbar sein, binden Sie den CDN-Build als Einzeldatei mit defer in den Head von src/root.tsx ein, wie unter HTML und CDN beschrieben. Damit laden Sie auf jeder Seite 62 KB gzip, müssen dafür aber nicht nachhalten, wo das Element verwendet wird.
Props
Die Komponente Icon reicht ihre Props an renderToString weiter, das die gemeinsamen Optionen mit ihren camelCase-Namen akzeptiert, dazu class und style als Strings für das svg-Element. Am Element sind dieselben Props Attribute in kebab-case.
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
size | number, string | 24 | Breite und Höhe; Zahlen sind Pixel. |
color | string | currentColor | Hauptfarbe. |
secondaryColor | string | color | Farbe der Duotone-Ebene und der Solid-Füllung. |
secondaryOpacity | number | 0.3 / 1 | Deckkraft der Duotone-Ebene oder der Solid-Füllung. |
weight | number | 2 | Strichstärke von 1 bis 3 im 24er-Raster, unter 24 px optisch ausgeglichen. |
absoluteWeight | boolean | false | Hält den Strich bei jeder Größe auf weight Pixeln. |
corners | round, sharp | round | Abgerundete oder spitze Verbindungen und Enden. |
variant | outline, duotone, solid | outline | Stilachse; solid entsteht aus den geschlossenen Formen. |
rotate | number | 0 | Drehung in Grad. |
flipX | boolean | false | Horizontal spiegeln. |
flipY | boolean | false | Vertikal spiegeln. |
mirrorRtl | boolean | false | In Layouts von rechts nach links spiegeln. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | keiner | Voreinstellung für den lebendigen Zustand. |
trigger | mount, hover, click, state | mount | Wann die Animation oder der Morph abläuft. |
active | boolean | false | Der Zustand für den Auslöser state. |
duration | number (ms) | je nach Voreinstellung | Dauer der Animation oder des Morphs. |
delay | number (ms) | 0 | Verzögerung der Animation oder des Morphs. |
loop | boolean, number | false | Endlos oder eine bestimmte Anzahl von Malen wiederholen (spin und pulse laufen standardmäßig in Schleife). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | keiner | Badge mit automatischer Aussparung. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Ecke des Badges. |
morphTo | name, data | keiner | Ziel des Morphs; der Auslöser bestimmt, wann es erscheint. |
title | string | keiner | Zugänglicher Name: title, role img und aria-label. |
decorative | boolean | true ohne title | Verbirgt das Icon vor assistiven Technologien. |
Stolperfallen
- dangerouslySetInnerHTML erwartet einen String
- Übergeben Sie das Markup direkt. Die React-Schreibweise mit
__htmlfunktioniert in Qwik nicht. - Elternkomponenten, die erneut rendern
- Ein Icon in einer Komponente, die im Browser erneut rendert, zieht
glyphfox/corenach sich, etwa 37 KB minifiziert. Lagern Sie zustandsbehaftete Teile in eigene kleine Komponenten aus. - Namen in morphTo
- Der Core benötigt für
morphToIcon-Daten. Ein String wird ignoriert, und das Icon morpht nicht. - Hover wirkt nur über das direkte Elternelement
- Der CSS-Hover-Morph reagiert auf das svg-Element und sein direktes Elternelement, also das Wrapper-Span. Ein Hover über einen größeren umgebenden Button startet den Morph nicht; schalten Sie dafür
gf-onmittrigger: 'state'um. - toSvg in Client-Code
import { toSvg } from 'glyphfox'bringt alle Icons mit. Das gehört in reinen Servercode wierouteLoader$; in Komponenten, die im Browser laufen können, verwenden Sie den Core mit Modulen pro Icon.- Das Element vor seinem Upgrade
- Das Server-HTML enthält ein leeres
<glyph-fox>. Reservieren Sie seine Box per CSS, oder rendern Sie Icons, die sofort sichtbar sein müssen, mit der KomponenteIcon.
Häufige Fragen
Liefern serverseitig gerenderte Icons wirklich kein JavaScript aus?
Ja, solange die Komponente, die sie rendert, im Browser nicht erneut rendert. Das HTML enthält reines SVG, und Presets sowie Hover-Morphs sind CSS innerhalb dieses SVG. Nur Komponenten, die erneut rendern, und das Element <glyph-fox> bringen Code in den Browser.
Kann ich die React-Komponenten über qwikify$ verwenden?
In qwikify$ verhalten sie sich wie jede andere React-Komponente, allerdings liefert diese Island dann React und die React-Runtime von glyphfox mit aus. Für Icons sind serverseitig gerendertes Markup oder das Element deutlich günstiger.