Ícones no Qwik
A maioria dos ícones nunca muda depois que a página carrega. No Qwik, eles podem ser SVG puro escrito no servidor, sem nenhum código de componente enviado ao navegador. Para os poucos que se transformam ou reagem, o elemento <glyph-fox> cuida da interação sozinho.
Instalação
Instale o pacote principal. A abordagem no servidor usa glyphfox/core e um módulo por ícone de glyphfox/icons/NAME; o elemento vem de glyphfox/web. Não é preciso configurar nada no Vite nem no Qwik.
npm install glyphfox
Os exemplos importam de @builder.io/qwik. No Qwik 2, esses imports passam para @qwik.dev/core; a parte do glyphfox continua igual.
Ícones renderizados no servidor
O renderToString retorna a marcação SVG a partir dos dados do ícone e das props compartilhadas. O Qwik insere marcação bruta com dangerouslySetInnerHTML, que no Qwik recebe a própria string, e não um objeto como no 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 é uma função comum, um componente inline na terminologia do Qwik. Ele renderiza como parte do pai e não tem uma fronteira de lazy loading própria. No servidor, o pai o escreve no HTML e, desde que o pai não renderize de novo no navegador, nenhum código dele chega a ser baixado.
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> ));
Se um componente renderiza de novo no cliente, por exemplo porque lê um signal que muda, o Qwik baixa a função de renderização dele e os módulos que ele importa, incluindo o renderizador de glyphfox/core. Mantenha os ícones em componentes que renderizam uma vez só e leve as partes que mudam para componentes próprios.
Movimento sem JavaScript
A marcação do renderToString traz o próprio CSS. Com animate, ela toca um preset, e com morphTo e trigger: 'hover', ela se transforma enquanto o ponteiro está sobre o svg ou sobre o pai direto dele. Nenhum dos dois precisa de script, e ambos param com 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" />;
Com o core, morphTo recebe dados de ícone, não um nome: importe o ícone de destino como acima. Uma string é ignorada, e o ícone fica parado.
Para um estado como aberto ou selecionado, renderize com trigger: 'state': o svg então se transforma quando recebe a classe gf-on. Alternar uma classe não exige nova renderização, então o único código que o navegador baixa é o manipulador de clique, e só no primeiro clique.
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> ));
Ícones interativos com o elemento
Quando um ícone precisa alternar entre nomes, se transformar a cada mudança ou reagir sozinho a cliques, use o elemento <glyph-fox>. Carregue-o a partir de uma visible task no componente que o usa, para que o navegador só o baixe quando um ícone desses estiver na tela.
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> ); });
Escreva as props do elemento como atributos de string, como no HTML: name, size, morph, morph-to, trigger. Com um gatilho de clique ou hover, o elemento guarda o próprio estado, então o componente nunca renderiza de novo e o Qwik não tem nada para retomar nele.
O glyphfox/web traz todos os ícones. Para carregar menos, importe, a partir da task, um módulo seu que registre um subconjunto com defineGlyphFox e registerIcons, como no guia de web components. Se a sua configuração de TypeScript rejeitar a tag desconhecida, declare glyph-fox entre os elementos intrínsecos do JSX em um arquivo de declaração.
O elemento só desenha depois do upgrade, então o HTML do servidor fica com uma tag vazia. Reserve o tamanho dele com CSS para o layout não se mexer:
glyph-fox:not(:defined) { display: inline-block; width: 24px; height: 24px; }
Qwik City
Layouts e rotas renderizam no servidor, então o componente Icon funciona em src/routes/layout.tsx, nas páginas e em todo componente que elas renderizam. A geração de site estático grava a mesma marcação em arquivos HTML.
Quando os nomes dos ícones vêm de dados, resolva-os no servidor. Em um routeLoader$, o toSvg de glyphfox aceita qualquer nome e a sintaxe de nomes completa; ele traz todos os ícones, o que não é problema em código só de 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> ); });
Se preferir ter o elemento em todas as páginas, adicione o build de arquivo único da CDN ao head em src/root.tsx com defer, como descrito em HTML e CDN. Com isso, você paga um download de 62 KB gzip em cada página para não precisar controlar onde o elemento é usado.
Props
O componente Icon repassa as props para o renderToString, que aceita as opções compartilhadas com os nomes em camelCase, além das strings class e style para o svg. No elemento, as mesmas props são atributos em kebab-case.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
size | number, string | 24 | Largura e altura; números são pixels. |
color | string | currentColor | Cor principal. |
secondaryColor | string | color | Cor da camada duotone e do preenchimento sólido. |
secondaryOpacity | number | 0.3 / 1 | Opacidade da camada duotone ou do preenchimento sólido. |
weight | number | 2 | Traço de 1 a 3 na grade de 24, com compensação óptica abaixo de 24 px. |
absoluteWeight | boolean | false | Mantém o traço com weight pixels em qualquer tamanho. |
corners | round, sharp | round | Junções e pontas redondas ou retas. |
variant | outline, duotone, solid | outline | Eixo de estilo; o sólido vem das formas fechadas. |
rotate | number | 0 | Rotação em graus. |
flipX | boolean | false | Espelha na horizontal. |
flipY | boolean | false | Espelha na vertical. |
mirrorRtl | boolean | false | Espelha em layouts da direita para a esquerda. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | nenhum | Animação de estado vivo. |
trigger | mount, hover, click, state | mount | Quando a animação ou o morph acontece. |
active | boolean | false | O estado para o gatilho state. |
duration | number (ms) | por animação | Duração da animação ou do morph. |
delay | number (ms) | 0 | Atraso da animação ou do morph. |
loop | boolean, number | false | Repete para sempre ou um número de vezes (spin e pulse repetem por padrão). |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | nenhum | Selo com espaço de recorte automático. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Canto do selo. |
morphTo | name, data | nenhum | Destino do morph; o gatilho decide quando aparece. |
title | string | nenhum | Nome acessível: title, role img e aria-label. |
decorative | boolean | true sem title | Esconde o ícone das tecnologias assistivas. |
Armadilhas comuns
- dangerouslySetInnerHTML recebe uma string
- Passe a própria marcação. A forma do React, com
__html, não funciona no Qwik. - Componentes pais que renderizam de novo
- Um ícone dentro de um componente que renderiza de novo no navegador leva junto o
glyphfox/core, cerca de 37 KB minificados. Mantenha as partes com estado em componentes pequenos e separados. - Nomes em morphTo
- O core precisa de dados de ícone em
morphTo. Uma string é ignorada, e o ícone não se transforma. - O hover só alcança o pai direto
- O morph CSS no hover reage ao svg e ao pai direto dele, que é o span que o envolve. Passar o mouse sobre um botão maior em volta dele não inicia o morph; para isso, alterne
gf-oncomtrigger: 'state'. - toSvg em código do cliente
import { toSvg } from 'glyphfox'traz todos os ícones. O lugar dele é em código só de servidor, comorouteLoader$; em componentes que podem rodar no navegador, use o core com um módulo por ícone.- O elemento antes do upgrade
- O HTML do servidor fica com um
<glyph-fox>vazio. Reserve o espaço dele com CSS ou renderize com o componenteIconos ícones que precisam aparecer de imediato.
Perguntas frequentes
Os ícones renderizados no servidor realmente não enviam JavaScript?
Sim, desde que o componente que os renderiza não renderize de novo no navegador. O HTML traz SVG puro, e presets e morphs no hover são CSS dentro desse SVG. Só os componentes que renderizam de novo e o elemento <glyph-fox> levam código para o navegador.
Posso usar os componentes React com qwikify$?
Eles se comportam como qualquer componente React dentro de qwikify$, mas essa ilha passa a enviar o React e o runtime React do glyphfox. Para ícones, a marcação renderizada no servidor ou o elemento custam bem menos.