Icônes dans Qwik
La plupart des icônes ne changent plus une fois la page chargée. Dans Qwik, elles peuvent être du SVG simple écrit sur le serveur, sans aucun code de composant envoyé au navigateur. Pour les rares icônes qui se transforment ou réagissent, l'élément <glyph-fox> gère lui-même l'interaction.
Installation
Installez le paquet principal. L'approche côté serveur utilise glyphfox/core et un module par icône issu de glyphfox/icons/NAME ; l'élément provient de glyphfox/web. Il n'y a rien à configurer dans Vite ni dans Qwik.
npm install glyphfox
Les exemples importent depuis @builder.io/qwik. Qwik 2 déplace ces imports vers @qwik.dev/core ; côté glyphfox, rien ne change.
Icônes rendues sur le serveur
renderToString renvoie le balisage SVG correspondant aux données d'une icône et aux props communes. Qwik insère du balisage brut avec dangerouslySetInnerHTML, qui, dans Qwik, prend directement la chaîne, et non un objet comme dans React.
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 est une simple fonction, un composant inline dans la terminologie de Qwik. Il est rendu au sein de son parent et ne possède pas sa propre frontière de chargement différé. Sur le serveur, le parent l'écrit dans le HTML, et tant que ce parent n'est pas rendu à nouveau dans le navigateur, aucun code le concernant n'est jamais téléchargé.
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> ));
Si un composant est rendu à nouveau côté client, par exemple parce qu'il lit un signal qui change, Qwik télécharge sa fonction de rendu et les modules qu'il importe, y compris le moteur de rendu de glyphfox/core. Placez les icônes dans des composants rendus une seule fois, et isolez les parties qui changent dans leurs propres composants.
Animations sans JavaScript
Le balisage produit par renderToString embarque son propre CSS. Avec animate, il joue un preset, et avec morphTo et trigger: 'hover', il se transforme tant que le pointeur survole le svg ou son parent direct. Aucun des deux ne nécessite de script, et tous deux s'arrêtent sous 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" />;
Avec le core, morphTo attend des données d'icône, pas un nom : importez l'icône cible comme ci-dessus. Une chaîne est ignorée et l'icône reste immobile.
Pour un état comme ouvert ou sélectionné, faites le rendu avec trigger: 'state' : le svg se transforme alors lorsqu'il reçoit la classe gf-on. Basculer une classe ne nécessite aucun nouveau rendu, si bien que le seul code que le navigateur télécharge est le gestionnaire de clic, et seulement au premier clic.
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> ));
Icônes interactives avec l'élément
Lorsqu'une icône doit passer d'un nom à l'autre, se transformer à chaque changement ou réagir elle-même aux clics, utilisez l'élément <glyph-fox>. Chargez-le depuis une tâche visible (visible task) dans le composant qui l'utilise, afin que le navigateur ne le récupère que lorsqu'une telle icône est à l'écran.
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> ); });
Écrivez les props de l'élément sous forme d'attributs de type chaîne, comme en HTML : name, size, morph, morph-to, trigger. Avec un déclencheur de clic ou de survol, l'élément gère son propre état : le composant n'est donc jamais rendu à nouveau et Qwik n'a rien à reprendre pour lui.
glyphfox/web embarque toutes les icônes. Pour charger moins, importez depuis cette tâche votre propre module, qui enregistre un sous-ensemble avec defineGlyphFox et registerIcons, comme dans le guide des web components. Si votre configuration TypeScript rejette la balise inconnue, déclarez glyph-fox parmi les éléments intrinsèques JSX dans un fichier de déclaration.
L'élément se dessine après sa mise à niveau : le HTML du serveur contient donc une balise vide. Réservez sa taille avec CSS pour que la mise en page ne bouge pas :
glyph-fox:not(:defined) { display: inline-block; width: 24px; height: 24px; }
Qwik City
Les layouts et les routes sont rendus sur le serveur : le composant Icon fonctionne donc dans src/routes/layout.tsx, dans les pages et dans tous les composants qu'ils rendent. La génération de site statique écrit le même balisage dans des fichiers HTML.
Lorsque les noms d'icônes proviennent de données, résolvez-les sur le serveur. Dans un routeLoader$, toSvg de glyphfox accepte n'importe quel nom ainsi que la syntaxe complète des noms ; il embarque toutes les icônes, ce qui ne pose pas de problème dans du code exécuté uniquement sur le serveur.
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> ); });
Pour disposer plutôt de l'élément sur toutes les pages, ajoutez le build CDN en un seul fichier dans le head de src/root.tsx avec defer, comme décrit dans HTML et CDN. Ce choix coûte un téléchargement de 62 Ko gzip sur chaque page, mais vous dispense de suivre les endroits où l'élément est utilisé.
Props
Le composant Icon transmet ses props à renderToString, qui accepte les options communes avec leurs noms en camelCase, ainsi que les chaînes class et style pour le svg. Sur l'élément, ces mêmes props sont des attributs en kebab-case.
| Option | Type | Par défaut | Description |
|---|---|---|---|
size | number, string | 24 | Largeur et hauteur ; les nombres sont en pixels. |
color | string | currentColor | Couleur principale. |
secondaryColor | string | color | Couleur du calque duotone et du remplissage plein. |
secondaryOpacity | number | 0.3 / 1 | Opacité du calque duotone ou du remplissage plein. |
weight | number | 2 | Trait de 1 à 3 sur la grille de 24, avec compensation optique en dessous de 24 px. |
absoluteWeight | boolean | false | Garde le trait à weight pixels quelle que soit la taille. |
corners | round, sharp | round | Jointures et extrémités arrondies ou vives. |
variant | outline, duotone, solid | outline | Axe de style ; solid vient des formes fermées. |
rotate | number | 0 | Rotation en degrés. |
flipX | boolean | false | Miroir horizontal. |
flipY | boolean | false | Miroir vertical. |
mirrorRtl | boolean | false | Miroir dans les mises en page de droite à gauche. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | aucun | Préréglage d'état vivant. |
trigger | mount, hover, click, state | mount | Moment où l'animation ou le morph se joue. |
active | boolean | false | L'état pour le déclencheur state. |
duration | number (ms) | selon le préréglage | Durée de l'animation ou du morph. |
delay | number (ms) | 0 | Délai de l'animation ou du morph. |
loop | boolean, number | false | Répète à l'infini ou un certain nombre de fois (spin et pulse bouclent par défaut). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | aucun | Badge avec espace de découpe automatique. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Coin du badge. |
morphTo | name, data | aucun | Cible du morph ; le déclencheur décide quand elle s'affiche. |
title | string | aucun | Nom accessible : title, role img et aria-label. |
decorative | boolean | true sans title | Masque l'icône aux technologies d'assistance. |
Pièges courants
- dangerouslySetInnerHTML prend une chaîne
- Passez directement le balisage. La forme React avec
__htmlne fonctionne pas dans Qwik. - Des parents rendus à nouveau
- Une icône placée dans un composant rendu à nouveau dans le navigateur entraîne avec elle
glyphfox/core, soit environ 37 Ko minifiés. Isolez les parties avec état dans de petits composants dédiés. - Des noms dans morphTo
- Le core attend des données d'icône pour
morphTo. Une chaîne est ignorée, et l'icône ne se transforme pas. - Le survol n'atteint que le parent direct
- Le morph CSS au survol réagit au svg et à son parent direct, c'est-à-dire le span conteneur. Survoler un bouton plus grand qui l'entoure ne déclenche pas le morph ; pour cela, basculez
gf-onavectrigger: 'state'. - toSvg dans le code client
import { toSvg } from 'glyphfox'embarque toutes les icônes. Sa place est dans du code exécuté uniquement sur le serveur, commerouteLoader$; dans les composants susceptibles de s'exécuter dans le navigateur, utilisez le core avec un module par icône.- L'élément avant sa mise à niveau
- Le HTML du serveur contient un
<glyph-fox>vide. Réservez sa place avec CSS, ou rendez avec le composantIconles icônes qui doivent être visibles immédiatement.
Questions fréquentes
Les icônes rendues côté serveur n'envoient-elles vraiment aucun JavaScript ?
Oui, tant que le composant qui les rend n'est pas rendu à nouveau dans le navigateur. Le HTML contient du SVG simple, et les presets comme les morphs au survol sont du CSS intégré à ce SVG. Seuls les composants rendus à nouveau, ainsi que l'élément <glyph-fox>, apportent du code au navigateur.
Puis-je utiliser les composants React via qwikify$ ?
Ils se comportent comme n'importe quel composant React dans qwikify$, mais cette île embarque alors React et le runtime React de glyphfox. Pour des icônes, le balisage rendu côté serveur ou l'élément revient bien moins cher.