5 pontos por GN⁺ 2024-04-26 | 1 comentários | Compartilhar no WhatsApp
  • canvas-confetti é uma biblioteca cliente para executar animações de confete baseadas em canvas em páginas web, com suporte tanto à instalação via NPM quanto à inclusão direta por CDN
  • A API básica confetti() permite ajustar número de partículas, ângulo, dispersão, velocidade, gravidade, cores, formas, posição, z-index e mais com um único objeto de opções; em ambientes com suporte a Promise, também é possível receber o momento em que a animação termina
  • Para usuários de Reduced Motion, há a opção disableForReducedMotion; atualmente seu valor padrão é false, mas isso pode mudar em uma futura major release
  • É possível criar formas personalizadas com SVG Path e texto, e implementar efeitos como emoji confetti além das formas padrão square, circle e star
  • confetti.create() cria uma instância em um canvas específico e oferece opções globais como resize e useWorker, mas com useWorker: true o controle do canvas é transferido para um web worker, e manipulá-lo na thread principal causará erro

Instalação e formas de execução

npm install --save canvas-confetti
  • Em builds do projeto, pode ser usada com require('canvas-confetti')
  • Esta biblioteca é um componente cliente e não roda em Node
    • O README orienta que o projeto deve ser compilado com uma ferramenta como webpack
  • Em páginas HTML, também pode ser incluída diretamente com um script de CDN
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/…;
  • Ao usar CDN, recomenda-se usar a versão mais recente disponível no momento da inclusão no projeto; a lista completa pode ser consultada na releases page

Suporte a Reduced Motion

  • Alguns usuários podem não querer movimento em sites ou preferir reduzi-lo, e o navegador pode comunicar isso com prefers-reduced-motion
  • Ao usar a opção disableForReducedMotion, é possível não exibir confete para usuários que tenham dificuldade com animações confusas
  • Atualmente, o valor padrão dessa opção é false
  • Há a possibilidade de mudar o padrão em uma futura major release, e opiniões fortes sobre isso podem ser enviadas em uma issue
  • Se disableForReducedMotion desativar o confete, a Promise de confetti() será resolvida imediatamente

API básica e comportamento de Promise

  • Quando instalada via NPM, pode ser carregada em builds do projeto como componente cliente; na versão via CDN, fica exposta como a função confetti em window
  • confetti([options]) recebe um único objeto opcional de opções
  • Se window.Promise existir, a função retorna uma Promise que informa quando a animação termina
    • Em ambientes sem Promise, como o IE, ela retorna null
    • É possível usar um polyfill de Promise
    • Também é possível fornecer manualmente uma implementação com confetti.Promise = MyPromise
  • Se confetti for chamado várias vezes antes da conclusão, a mesma Promise será retornada em todas elas
  • Internamente, o mesmo elemento canvas é reutilizado, e novos confetes são adicionados enquanto a animação existente continua
  • A Promise retornada por cada chamada será resolvida depois que todas as animações terminarem

Principais opções

  • particleCount: quantidade de confetes lançados, padrão 50
  • angle: ângulo de lançamento, padrão 90, em que 90 significa para cima
  • spread: amplitude de dispersão a partir do centro, padrão 45
  • startVelocity: velocidade inicial, padrão 45
  • decay: taxa de redução da velocidade, padrão 0.9
    • Deve permanecer entre 0 e 1; fora desse intervalo, a velocidade pode aumentar
  • gravity: intensidade com que as partículas são puxadas para baixo, padrão 1
    • 0.5 representa meia gravidade, e não há limite, então também é possível fazê-las subir
  • drift: desvio lateral, padrão 0
    • Negativo significa para a esquerda, positivo para a direita
  • flat: desativa o efeito de inclinação e oscilação típico de um confete 3D realista; padrão false
  • ticks: número de movimentos do confete, padrão 200
  • origin: posição inicial do lançamento
    • origin.x: posição x na página, 0 é esquerda, 1 é direita, padrão 0.5
    • origin.y: posição y na página, 0 é topo, 1 é base, padrão 0.5
  • colors: array de strings de cor em formato HEX
  • shapes: array de formas de confete
    • Os valores embutidos padrão são square, circle, star
    • Por padrão, square e circle são misturados em partes iguais
    • É possível ajustar a proporção com arrays como ['circle', 'circle', 'square']
  • scalar: escala de cada partícula, padrão 1
  • zIndex: camada de exibição do confete, padrão 100
  • disableForReducedMotion: desativa o confete para usuários com preferência por Reduced Motion

Criando formas personalizadas

  • confetti.shapeFromPath({ path, matrix? }) cria uma forma de confete personalizada a partir de uma SVG Path string
  • Há algumas restrições para formas baseadas em Path
    • Todos os paths são tratados como formas preenchidas; path com stroke não é implementado
    • O path é limitado a uma única cor
    • Todo path precisa de uma transform matrix válida
    • Como o cálculo da matrix tem custo, é recomendável calculá-la uma vez por path durante o desenvolvimento e armazená-la em cache
    • A matrix é sempre a mesma para o mesmo valor de path
    • Ao atualizar a biblioteca, é recomendável gerar e armazenar a matrix novamente para manter a forward compatibility
    • Confetes baseados em path ficam limitados a navegadores com suporte a Path2D
  • O valor retornado é um objeto Shape, que pode ser inserido diretamente no array shapes
var triangle = confetti.shapeFromPath({ path: 'M0 10 L5 0 L10 10z' });

confetti({
  shapes: [triangle]
});
  • confetti.shapeFromText({ text, scalar?, color?, fontFamily? }) cria formas de confete baseadas em texto e pode usar emoji Unicode padrão
  • Formas baseadas em texto são ideais para emoji confetti
    • Como o confete oscila, geralmente funciona melhor com um único caractere quase quadrado, especialmente emoji
    • Como o texto é rasterizado em vez de desenhado toda vez, mudar muito a escala depois da criação pode deixá-lo borrado
    • Se você pretende usar scalar nas opções do confete, é melhor usar o mesmo valor de scalar ao criar a forma
  • As opções de texto são text, scalar, color, fontFamily
    • O padrão de fontFamily segue as convenções nativas de renderização de emoji do sistema operacional, com fallback para sans-serif
    • Ao usar web fonts, a fonte deve estar carregada antes da renderização do confete
var scalar = 2;
var pineapple = confetti.shapeFromText({ text: '🍍', scalar });

confetti({
  shapes: [pineapple],
  scalar
});

Canvas personalizado e renderização com worker

  • confetti.create(canvas, [globalOptions]) cria uma instância da função de confete que usa um canvas específico
  • Isso é útil quando se quer limitar o confete a uma área específica da página
  • Por padrão, esse método não modifica o canvas além de desenhar nele
  • O tamanho visual do canvas pode ser alterado com CSS, mas isso não muda o tamanho real da imagem do canvas, o que pode fazer com que ela fique esticada e borrada
    • Ao ativar a opção resize, a biblioteca ajusta o tamanho da imagem do canvas e também responde a mudanças no tamanho da janela ou rotação em dispositivos móveis
  • Não se deve inicializar várias instâncias de confete no mesmo elemento canvas; o ideal é manter a instância personalizada criada
  • Opções globais

    • resize: define se o tamanho da imagem do canvas será ajustado e mantido conforme mudanças na janela; padrão false
    • useWorker: renderiza a animação de confete em um web worker assíncrono quando possível; padrão false
    • No padrão, a animação sempre roda na thread principal
    • Se o navegador oferecer suporte, a animação pode rodar fora da thread principal para não bloqueá-la
    • Em navegadores sem suporte, esse valor é ignorado
    • disableForReducedMotion: faz com que essa instância de confete sempre respeite a preferência de Reduced Motion do usuário
  • Cuidados com useWorker: true

    • Ao usar useWorker: true, o controle do canvas é transferido para o web worker
    • Nesse caso, manipulá-lo na thread principal causará erro, exceto removê-lo do DOM
    • Se for necessário manipular o canvas diretamente, não se deve usar a opção useWorker
    var myCanvas = document.createElement('canvas');
    document.body.appendChild(myCanvas);
    
    var myConfetti = confetti.create(myCanvas, {
      resize: true,
      useWorker: true
    });
    myConfetti({
      particleCount: 100,
      spread: 160
    });
    

Interrompendo a animação e padrões de exemplo

  • confetti.reset() interrompe a animação, remove todo o confete e resolve imediatamente qualquer Promise pendente
  • Instâncias separadas criadas com confetti.create() têm seu próprio método reset
confetti();

setTimeout(() => {
  confetti.reset();
}, 100);
  • A execução básica consiste em chamar confetti() sem argumentos
  • É possível lançar muito confete com particleCount: 150
  • É possível criar um efeito mais espalhado com spread: 180
  • Usar Math.random() em origin permite criar pequenos efeitos de explosão em posições aleatórias da página
  • O exemplo do README mostra um padrão que usa requestAnimationFrame para lançar confete continuamente nas bordas esquerda e direita durante 30 segundos

1 comentários

 
GN⁺ 2024-04-26
Opiniões no Hacker News
  • O truque aqui para criar animações com bom desempenho é desenhar em um canvas e então colocar esse canvas à frente de todos os outros elementos, mas com eventos de ponteiro desativados para que ainda seja possível interagir com a página

    • Sim. Desativar eventos de ponteiro é surpreendentemente útil
    • Isso foi descrito como um truque para animações com bom desempenho, mas não consigo pensar em outro jeito de implementar algo assim. Como seria uma implementação ingênua?
  • Lembra os bons tempos de 2015, quando eu fazia desenvolvimento web no ensino médio. Criei um sitezinho com confete para convidar uma garota para ir comigo ao homecoming; olhando para trás, foi extremamente nerd
    Na época, fazer um site para alguém parecia um superpoder. Pelo período, acho que não era este pacote, mas a animação era bem boa
    Gosto desses pequenos projetos puramente divertidos. Foi por isso que comecei a programar e ainda hoje continua sendo uma grande motivação

    • Deu certo? Ela disse sim?
  • Gostei desta parte da página de demonstração:

    If you happened to get curious and changed the particle count to 400 or so, you saw something disappointing. An even "flattened cone" look to the confetti, making it look way too perfect and ruining the illusion.

    Esse tipo de obsessão por detalhes é raro e, sempre que o encontro, seja em visualização estatística, adereços de cinema ou confete em sites, acho precioso
    Como solução, eu tentaria alterar a própria distribuição aleatória. Eu checaria na prática, mas suspeito que a distribuição real seja mais próxima de uma distribuição gaussiana

  • Adicionei confete no painel administrativo que aparece quando um vendedor fecha uma venda, e é surpreendentemente divertido e motivador

  • Teria sido melhor se a função reset se chamasse confetti.resetti()

    • Como é JavaScript, pelo menos localmente dá para corrigir facilmente com "confetti.resetti = confetti.reset"
      Essa abordagem talvez tenha algum custo de engenharia de software, mas, como qualquer observador atento perceberia claramente, os benefícios superam esmagadoramente os custos, então acho que é só fazer
    • Deem um emprego a essa pessoa. Se ela já tiver um emprego, deem pelo menos um cookie
    • Talvez dê para abrir um PR
  • Além de ser uma biblioteca legal e útil, é um bom exemplo de módulo profundo, como John Ousterhout descreve em Philosophy of Software Design
    A versão mais básica, ou seja, invocar confete, é muito fácil de usar, mas, olhando as opções, dá para obter bastante coisa: neve, cores específicas, vários efeitos de confete etc.

  • Legal e impressionante
    Ao mesmo tempo, não quero ver isso rodando em nenhum site que eu uso. Especialmente não quero confete acompanhando pop-ups de newsletter ou quando adiciono um produto ao carrinho

    • Curiosamente, esse efeito pode ser usado de forma bem eficaz. Não sei quanto a esse formato de tela inteira, mas em um software de gerenciamento de projetos usado por um cliente que visitei recentemente, ao fechar um item o botão ficava verde e esse efeito aparecia
      Era sutil, mas perceptível, e depois da reunião outro desenvolvedor e eu comentamos: “foi um efeito bem legal”. Passava a sensação de “ótimo, há progresso!”
      Só precisa ser opcional

    • Um uso legítimo talvez seja algo como o botão de curtir do YouTube. Tem uma animação legal e, no app móvel, o aparelho também vibra. É uma experiência do usuário muito agradável

    • https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pref...

      É possível configurar o navegador para preferir redução de movimento. Operadores de sites e mantenedores de bibliotecas devem respeitar isso ao implementar coisas como confete. Esta biblioteca, em particular, tem a opção disableForReducedMotion

    • Há lugares em que esse tipo de efeito combina. Por exemplo, ao concluir um jogo

    • Usamos esta biblioteca quando alguém atende a determinada qualificação. Dá um efeito bem bom ao fluxo de onboarding

  • Também existe a biblioteca Party.js: https://party.js.org/

    • Então qual das duas é menor?
      10,4 kB minificado, 4,2 kB minificado + Gzip
      https://bundlephobia.com/package/canvas-confetti@1.9.2

      28,3 kB minificado, 7,4 kB minificado + Gzip
      https://bundlephobia.com/package/party-js@2.2.0

      Dito isso, não sei bem como o bundlephobia funciona. Talvez não mostre da melhor forma o tamanho final do pacote. Provavelmente não reflete code splitting nem importar apenas o necessário. Vejo só como uma visão geral rápida e aproximada

      Pelo Gzip, parece que o confetti vence por alguns KB, então, a menos que você realmente precise espremer esses poucos KB, ambos servem dependendo de qual deles tem os recursos de que você precisa

    • O script do post original parece ter desempenho muito melhor no celular

    • A biblioteca do post original parece ter desempenho muito melhor. No meu computador de trabalho antigo, o Party.js já mostra um pouco de atraso depois de apenas 3 cliques
      O canvas-confetti só começa a atrasar depois de eu clicar sem parar por alguns segundos, provavelmente criando mais de 30 instâncias de confete e muitas partículas

  • Resolvo palavras cruzadas no downforacross.com e, quando completo um quebra-cabeça, aparece confete
    Talvez eles possam usar parte deste código com melhor desempenho para deixar a experiência mais leve
    Dito isso, a menos que seja um site “divertido” ou algo de uso raro, não quero ver esse tipo de animação aparecendo por toda parte

  • Acho que não precisava colocar useful no título

    • Que tal como ferramenta de motivação e como forma de verificar se o código compilou: https://squint-cljs.github.io/squint/
    • Concordo. Ainda assim, essa palavra realmente me deixou curioso, e achei engraçado porque, na prática, não é lá muito útil. Recomendo
    • É tão útil quanto confete de verdade, ou seja, 100% útil