Iconos en Qwik
La mayoría de los iconos no cambian nunca una vez cargada la página. En Qwik pueden ser SVG puro escrito en el servidor, sin enviar código de componentes al navegador. Para los pocos que se transforman o reaccionan, el elemento <glyph-fox> se encarga él solo de la interacción.
Instalación
Instala el paquete base. El enfoque del lado del servidor usa glyphfox/core y un módulo por icono de glyphfox/icons/NAME; el elemento viene de glyphfox/web. No hace falta configurar nada en Vite ni en Qwik.
npm install glyphfox
Los ejemplos importan desde @builder.io/qwik. Qwik 2 traslada estas importaciones a @qwik.dev/core; la parte de glyphfox no cambia.
Iconos renderizados en el servidor
renderToString devuelve el marcado SVG a partir de los datos del icono y las props compartidas. Qwik inserta marcado en bruto con dangerouslySetInnerHTML, que en Qwik recibe la propia cadena, no un objeto como en 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 es una función normal, un componente inline en la terminología de Qwik. Se renderiza como parte de su padre y no tiene un límite de carga diferida propio. En el servidor, el padre lo escribe en el HTML y, mientras el padre no se vuelva a renderizar en el navegador, nunca se descarga código para él.
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 componente se vuelve a renderizar en el cliente, por ejemplo porque lee una señal que cambia, Qwik descarga su función de renderizado y los módulos que importa, incluido el renderizador de glyphfox/core. Mantén los iconos en componentes que se renderizan una sola vez y mueve las partes que cambian a sus propios componentes.
Movimiento sin JavaScript
El marcado de renderToString lleva su propio CSS. Con animate reproduce un preset, y con morphTo y trigger: 'hover' se transforma mientras el puntero está sobre el svg o su padre directo. Ninguno de los dos necesita un script, y ambos se detienen con 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" />;
Con el core, morphTo acepta datos de iconos, no un nombre: importa el icono de destino como arriba. Una cadena se ignora y el icono se queda quieto.
Para un estado como abierto o seleccionado, renderiza con trigger: 'state': así el svg se transforma cuando recibe la clase gf-on. Alternar una clase no requiere volver a renderizar, así que el único código que descarga el navegador es el manejador de clics, y solo con el primer 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> ));
Iconos interactivos con el elemento
Cuando un icono tiene que cambiar entre nombres, transformarse en cada cambio o reaccionar a los clics por sí solo, usa el elemento <glyph-fox>. Cárgalo desde una tarea visible en el componente que lo usa, para que el navegador lo descargue solo cuando un icono así esté en pantalla.
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> ); });
Escribe las props del elemento como atributos de cadena, igual que en HTML: name, size, morph, morph-to, trigger. Con un disparador de clic o hover, el elemento mantiene su propio estado, así que el componente nunca se vuelve a renderizar y Qwik no tiene nada que reanudar para él.
glyphfox/web incluye todos los iconos. Para cargar menos, importa desde la tarea un módulo propio que registre un subconjunto con defineGlyphFox y registerIcons, como en la guía de web components. Si tu configuración de TypeScript rechaza la etiqueta desconocida, declara glyph-fox entre los elementos intrínsecos de JSX en un archivo de declaraciones.
El elemento se dibuja después de actualizarse, así que el HTML del servidor contiene una etiqueta vacía. Reserva su tamaño con CSS para que el diseño no se mueva:
glyph-fox:not(:defined) { display: inline-block; width: 24px; height: 24px; }
Qwik City
Los layouts y las rutas se renderizan en el servidor, así que el componente Icon funciona en src/routes/layout.tsx, en las páginas y en todos los componentes que estas renderizan. La generación de sitios estáticos escribe ese mismo marcado en archivos HTML.
Cuando los nombres de los iconos vienen de datos, resuélvelos en el servidor. En un routeLoader$, toSvg de glyphfox acepta cualquier nombre y la sintaxis completa de nombres; incluye todos los iconos, lo cual no es problema en código que solo se ejecuta en el servidor.
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> ); });
Si prefieres tener el elemento en todas las páginas, añade al head de src/root.tsx la versión de un solo archivo para la CDN con defer, como se explica en HTML y CDN. A cambio de una descarga de 62 KB con gzip en cada página, no tienes que controlar dónde se usa el elemento.
Props
El componente Icon pasa sus props a renderToString, que acepta las opciones compartidas con sus nombres en camelCase, además de las cadenas class y style para el svg. En el elemento, esas mismas props son atributos en kebab-case.
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
size | number, string | 24 | Ancho y alto; los números son píxeles. |
color | string | currentColor | Color principal. |
secondaryColor | string | color | Color de la capa duotone y del relleno sólido. |
secondaryOpacity | number | 0.3 / 1 | Opacidad de la capa duotone o del relleno sólido. |
weight | number | 2 | Trazo de 1 a 3 en la cuadrícula de 24, con compensación óptica por debajo de 24 px. |
absoluteWeight | boolean | false | Mantiene el trazo en weight píxeles a cualquier tamaño. |
corners | round, sharp | round | Uniones y extremos redondeados o rectos. |
variant | outline, duotone, solid | outline | Eje de estilo; solid se obtiene de las formas cerradas. |
rotate | number | 0 | Rotación en grados. |
flipX | boolean | false | Refleja en horizontal. |
flipY | boolean | false | Refleja en vertical. |
mirrorRtl | boolean | false | Refleja en layouts de derecha a izquierda. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | ninguno | Preset de estado vivo. |
trigger | mount, hover, click, state | mount | Cuándo se reproduce la animación o el morph. |
active | boolean | false | El estado para el disparador state. |
duration | number (ms) | según el preset | Duración de la animación o del morph. |
delay | number (ms) | 0 | Retraso de la animación o del morph. |
loop | boolean, number | false | Repite sin fin o un número de veces (spin y pulse se repiten por defecto). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | ninguno | Insignia con hueco de recorte automático. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Esquina de la insignia. |
morphTo | name, data | ninguno | Destino del morph; el disparador decide cuándo se muestra. |
title | string | ninguno | Nombre accesible: title, role img y aria-label. |
decorative | boolean | true sin title | Oculta el icono a las tecnologías de apoyo. |
Errores comunes
- dangerouslySetInnerHTML recibe una cadena
- Pasa el marcado directamente. La forma de React con
__htmlno funciona en Qwik. - Padres que se vuelven a renderizar
- Un icono dentro de un componente que se vuelve a renderizar en el navegador arrastra consigo
glyphfox/core, unos 37 KB minificados. Mantén las partes con estado en sus propios componentes pequeños. - Nombres en morphTo
- El core necesita datos de iconos para
morphTo. Una cadena se ignora, y el icono no se transforma. - El hover solo llega al padre directo
- El morph CSS al pasar el cursor reacciona al svg y a su padre directo, que es el span contenedor. Pasar el cursor por un botón más grande que lo rodee no inicia el morph; para eso, alterna
gf-oncontrigger: 'state'. - toSvg en código de cliente
import { toSvg } from 'glyphfox'incluye todos los iconos. Su sitio es el código que solo se ejecuta en el servidor, comorouteLoader$; en los componentes que pueden ejecutarse en el navegador, usa el core con un módulo por icono.- El elemento antes de actualizarse
- El HTML del servidor contiene un
<glyph-fox>vacío. Reserva su caja con CSS o renderiza con el componenteIconlos iconos que deban verse de inmediato.
Preguntas frecuentes
¿De verdad los iconos renderizados en el servidor no envían nada de JavaScript?
Sí, siempre que el componente que los renderiza no se vuelva a renderizar en el navegador. El HTML contiene SVG puro, y los presets y los morphs al pasar el cursor son CSS dentro de ese SVG. Solo los componentes que se vuelven a renderizar, y el elemento <glyph-fox>, llevan código al navegador.
¿Puedo usar los componentes de React mediante qwikify$?
Se comportan como cualquier componente de React dentro de qwikify$, pero entonces esa isla envía React y el runtime de React de glyphfox. Para iconos, el marcado renderizado en el servidor o el elemento cuestan mucho menos.