Frameworks

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.

terminal
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.

src/components/icon/icon.tsx
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é.

src/routes/layout.tsx
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.

src/components/status.tsx
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.

src/components/menu-toggle.tsx
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.

src/components/like-button.tsx
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 :

src/global.css
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.

src/routes/features/index.tsx
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.

OptionTypePar défautDescription
sizenumber, string24Largeur et hauteur ; les nombres sont en pixels.
colorstringcurrentColorCouleur principale.
secondaryColorstringcolorCouleur du calque duotone et du remplissage plein.
secondaryOpacitynumber0.3 / 1Opacité du calque duotone ou du remplissage plein.
weightnumber2Trait de 1 à 3 sur la grille de 24, avec compensation optique en dessous de 24 px.
absoluteWeightbooleanfalseGarde le trait à weight pixels quelle que soit la taille.
cornersround, sharproundJointures et extrémités arrondies ou vives.
variantoutline, duotone, solidoutlineAxe de style ; solid vient des formes fermées.
rotatenumber0Rotation en degrés.
flipXbooleanfalseMiroir horizontal.
flipYbooleanfalseMiroir vertical.
mirrorRtlbooleanfalseMiroir dans les mises en page de droite à gauche.
animatedraw, pop, wiggle, spin, pulse, bounce, shakeaucunPréréglage d'état vivant.
triggermount, hover, click, statemountMoment où l'animation ou le morph se joue.
activebooleanfalseL'état pour le déclencheur state.
durationnumber (ms)selon le préréglageDurée de l'animation ou du morph.
delaynumber (ms)0Délai de l'animation ou du morph.
loopboolean, numberfalseRépète à l'infini ou un certain nombre de fois (spin et pulse bouclent par défaut).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotaucunBadge avec espace de découpe automatique.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightCoin du badge.
morphToname, dataaucunCible du morph ; le déclencheur décide quand elle s'affiche.
titlestringaucunNom accessible : title, role img et aria-label.
decorativebooleantrue sans titleMasque l'icône aux technologies d'assistance.

Pièges courants

dangerouslySetInnerHTML prend une chaîne
Passez directement le balisage. La forme React avec __html ne 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-on avec trigger: '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, comme routeLoader$ ; 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 composant Icon les 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.

Icônes de ce guide