Favicons animados na prática: faça a aba do navegador se mexer (com demo ao vivo)
Você provavelmente já reparou em algo se mexendo no topo desta aba enquanto lia. Isso é um favicon animado — o mesmo truque que o Gmail usa para o contador de novos e-mails e o Discord para os pontinhos de notificação, só que o seu pode fazer praticamente qualquer coisa que você consiga desenhar num canvas.
Este artigo vem com uma demo para você cutucar. Clique num botão e olhe sua aba do navegador mudar de verdade. Sem capturas de tela, sem vídeo embutido — o favicon que você está vendo agora é a própria demo.
Por que animar um favicon?
Falando sério: a maioria dos sites não deveria. Um ícone girando em cada aba enche o saco rápido, e ainda gasta CPU. Mas há alguns casos em que vale a pena de verdade:
- Estados de carregamento ou processamento. Uploads longos, exportações, builds. As pessoas trocam de aba enquanto esperam, e o favicon animado avisa que o trabalho continua.
- Distintivos de notificação. Mensagens novas, menções, alertas. Um pontinho vermelho que pulsa de leve chama mais atenção que um estático.
- Dados ao vivo. Painéis de trading, ferramentas de monitoramento, placares — quando o título da aba não dá conta.
- Momentos de marca. Um spinner festivo, uma celebração no dia do lançamento. Use com parcimônia.
Se seu caso não cai em nenhum desses, pule a animação. Um SVG estático bem-feito já ganha em tamanho de arquivo, modo escuro e bateria.
Demo ao vivo: experimente agora
Escolha uma animação. Depois olhe a aba do navegador — aquele iconezinho lá em cima é o que está sendo redesenhado.
O favicon real tem 16×16 pixels. Nesse tamanho é difícil ver detalhes, então a caixa da esquerda espelha o mesmo canvas em 8× usando vizinho mais próximo.
Status: ocioso
Todo o loop de animação roda agora dentro de um Web Worker, exatamente como favicon_worker.js na biblioteca Aymkdn. A cada 20ms (50fps), o worker desenha no seu OffscreenCanvas, exporta com convertToBlob + FileReader e devolve o data URL para a página. A thread principal só faz uma coisa: atribui essa string em faviconLink.href. É por isso que o ícone agora se move tão suave quanto o demo do GitHub.
Quer ver esse padrão num produto de verdade? Random Picker Wheel usa um favicon animado que acompanha a roleta giratória — uma UI baseada em rotação é um dos poucos casos em que um ícone em movimento combina mesmo com o produto. Abra a página, gire a roda e veja o ícone da aba girar junto.
Como funciona de verdade
São três passos. É essa toda a técnica:
// 1. Pega ou cria o elemento link do favicon
let link = document.querySelector('link[rel~="icon"]');
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.head.appendChild(link);
}
// 2. Desenha um frame num canvas escondido
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');
function drawFrame(t) {
const scale = 0.5 + 0.5 * Math.abs(Math.sin(t / 400));
ctx.clearRect(0, 0, 32, 32);
ctx.fillStyle = '#ef4444';
ctx.beginPath();
ctx.arc(16, 16, 14 * scale, 0, Math.PI * 2);
ctx.fill();
// 3. Exporta o canvas para data URL e atribui ao favicon
link.href = canvas.toDataURL('image/png');
requestAnimationFrame(drawFrame);
}
requestAnimationFrame(drawFrame);
Algo em torno de 15 linhas para um ponto vermelho pulsante. Tudo que for mais sofisticado é só desenhar formas diferentes no canvas.
A realidade de compatibilidade entre navegadores
Aqui é onde a coisa pega. Os navegadores variam bastante na agressividade com que animam o favicon:
- Firefox: anima de forma fluida mesmo com a aba sem foco. É o padrão-ouro.
- Chrome / Edge: animam enquanto a aba está ativa. Quando você sai, o
requestAnimationFrameé limitado a aproximadamente uma vez por segundo, e a animação fica lenta ou trava. - Safari: anima quando em primeiro plano, mas às vezes só atualiza o ícone em intervalos longos. Não conte com fluidez.
Para os usos mais comuns — pontos de notificação, estados de progresso — isso nem chega a ser um problema, porque eles não precisam ser atualizados várias vezes por segundo. Spinners suaves a 60fps são, na maior parte, cosméticos.
O truque do Web Worker (para abas em segundo plano)
O botão "Flip estilo GitHub" lá em cima é uma transposição direta da animação característica da biblioteca Aymkdn/animated-favicon: mantém o ícone A por 3 segundos, comprime a largura via cosseno até zero, troca pelo ícone B, expande de novo. A matemática vem direto do favicon_worker.js da biblioteca: width = canvas.width * Math.abs(Math.cos(progress * Math.PI)), com a segunda imagem assumindo quando progress passa de 0,5.
A biblioteca do Aymkdn vai um passo além do que fazemos aqui: ela executa esse loop dentro de um Web Worker. Workers não são limitados quando a aba vai pra segundo plano, então a animação continua, e o OffscreenCanvas permite ao worker renderizar frames sem encostar no DOM.
O padrão fica mais ou menos assim:
// Na sua página
const worker = new Worker('favicon-worker.js');
worker.onmessage = (e) => {
if (e.data.type === 'updateFavicon') {
document.querySelector('link[rel~="icon"]').href = e.data.dataUrl;
}
};
worker.postMessage({ type: 'init', images: ['icon-a.png', 'icon-b.png'] });
// Em favicon-worker.js
const canvas = new OffscreenCanvas(16, 16);
const ctx = canvas.getContext('2d');
// ...desenhar um frame...
const blob = await canvas.convertToBlob();
const reader = new FileReader();
reader.onloadend = () => self.postMessage({ type: 'updateFavicon', dataUrl: reader.result });
reader.readAsDataURL(blob);
Vale a pena se sua aplicação é o tipo de coisa que o usuário deixa numa aba em segundo plano — clientes de chat, dashboards de build, ferramentas de monitoramento. Caso contrário, a versão na thread principal é mais simples e cumpre.
Algumas coisas que vale saber
Use 16×16 ou 32×32, nada maior. O favicon é renderizado pequeno de qualquer jeito, e canvas maiores significam data URLs maiores e mais CPU por frame. 32×32 com pixels nítidos é o ponto certo.
Defina o favicon como PNG, não ICO. A única coisa que funciona de forma confiável é canvas.toDataURL('image/png'). Não tente codificar ICO na mão.
Restaure o favicon original ao parar. Salve link.href antes de animar e restaure em beforeunload ou quando a operação terminar. Abas com animação meio quebrada depois de uma navegação parecem bugadas.
Não anime para sempre. Mesmo um pulso suave gasta bateria no celular. Pare a animação quando o carregamento terminar, quando o usuário ler a notificação ou quando a aba perder o foco.
Para casos simples, deixe pra lá. Se quiser apenas mostrar "1 mensagem não lida", troque o favicon por uma versão estática com pontinho vermelho. Sem animação. Boa parte do uso de favicons animados é exagero.
Quando recorrer a isso
Favicons animados encaixam quando:
- A animação reflete um estado real que o usuário se importa (upload, processamento, mensagem nova)
- A página é do tipo que costuma ficar numa aba em segundo plano
- Um distintivo estático ou só mudar o título da aba não passaria a mesma informação
Não encaixam quando:
- É só decoração
- A animação roda o tempo inteiro em que a aba está aberta
- Seus usuários estão em Safari ou mobile, onde mal funciona
Pegue o demo aí em cima, leve para o seu projeto, troque cores e formas pelas da sua marca e publique. O código completo está nesta página: ver código fonte, copiar, adaptar.
Referências
- Aymkdn/animated-favicon no GitHub — Biblioteca de favicon animado baseada em Web Worker que continua animando em abas inativas
- The Making of an Animated Favicon — CSS-Tricks — O passo a passo do Chris Coyier sobre a técnica canvas → favicon
- How to animate a favicon? — Stack Overflow — Tópico clássico com várias abordagens e notas de suporte
- OffscreenCanvas — MDN — A API que torna o padrão de animação via Web Worker possível
Use favicon.im to quickly check if your favicon is configured correctly. Our free tool ensures your website's favicon displays properly across all browsers and devices.
Free Public Service
Favicon.im is a completely free public service trusted by developers worldwide.