npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nullsablex/counter-up

v1.0.0

Published

Biblioteca JS pura para animação de contadores numéricos

Readme

Counter Up

Biblioteca JavaScript pura para animação de contadores numéricos — funciona no DOM e fora dele (Node.js, SSR, testes).

Recursos

  • JavaScript puro, sem dependências externas
  • Funciona com DOM e em modo headless (Node.js, SSR, Vitest sem jsdom)
  • Suporte a seletor CSS, elemento DOM, NodeList e array de elementos
  • Formatação via Intl.NumberFormat com suporte a locale, prefixo e sufixo
  • Easing embutido (linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeOutQuart, easeOutExpo) ou função personalizada
  • Acessibilidade: respeita prefers-reduced-motion por padrão
  • Inicialização automática ao entrar na viewport (startOnView)
  • Controles completos: start, pause, resume, stop, reset, set, update, destroy
  • Tipos TypeScript nativos incluídos — sem @types/* externo
  • Saídas ESM e UMD (normal e minificada)

Instalação

npm install @nullsablex/counter-up

O pacote já inclui os arquivos prontos de dist/. Não é necessário rodar build para usar.

Demo

Acesse a demonstração online: https://counter-up.nullsablex.com


Uso

ESM — elemento único

import { counterUp } from "@nullsablex/counter-up";

counterUp("#total", {
  start: 0,
  end: 12500.5,
  duration: 1800,
  decimals: 2,
  prefix: "R$ ",
});

ESM — múltiplos elementos

import { counterUp } from "@nullsablex/counter-up";

const counters = counterUp(".metric", {
  start: 0,
  end: 1200,
  duration: 1400,
});

// Atualiza cada elemento com um valor diferente
counters.update([100, 250, 999]);

ESM — iniciar ao entrar na viewport

import { counterUp } from "@nullsablex/counter-up";

counterUp(".stat", {
  end: 1500,
  startOnView: true, // aguarda o elemento aparecer na tela
  once: true,        // anima apenas uma vez
  threshold: 0.2,    // dispara quando 20% do elemento estiver visível
});

Modo headless — sem DOM (Node.js, SSR, testes)

Passe null como target. O valor é entregue exclusivamente via onUpdate.

import { counterUp } from "@nullsablex/counter-up";

const counter = counterUp(null, {
  start: 0,
  end: 1000,
  duration: 2000,
  onUpdate: (value) => {
    // use o valor como quiser: atualizar estado, renderizar no servidor, etc.
    console.log(Math.round(value));
  },
  onComplete: (value) => {
    console.log("Fim:", value); // → 1000
  },
});

Navegador — UMD via script tag

<script src="./dist/counterup.umd.min.js"></script>
<script>
  counterUp(".metric", { end: 5000, duration: 1500 });
</script>

API

counterUp(target, options)

target — o que animar:

| Tipo | Exemplo | Comportamento | |---|---|---| | string | "#id", ".classe" | Seleciona via document.querySelectorAll | | Element | document.getElementById("x") | Usa o elemento diretamente | | NodeList / HTMLCollection | document.querySelectorAll(".x") | Anima todos os elementos | | Element[] | [el1, el2] | Anima todos os elementos do array | | null / undefined | null | Modo headless — sem DOM, use onUpdate |


Opções

Valores

| Opção | Tipo | Padrão | Descrição | |---|---|---|---| | start | number | 0 | Número de onde a animação parte. O contador começa exibindo este valor. | | end | number | auto | Número até onde a animação conta. Quando omitido, a biblioteca lê o textContent do elemento e usa esse valor como destino — ou seja, o valor já renderizado no HTML é suficiente. Necessário em modo headless. | | duration | number | 2000 | Tempo total da animação em milissegundos. 0 pula diretamente para o valor de end. |

Formatação

| Opção | Tipo | Padrão | Descrição | |---|---|---|---| | decimals | number | auto | Quantidade de casas decimais exibidas. Quando omitido junto com end, é inferido automaticamente do textContent do elemento (ex.: "15.50"2). | | prefix | string | "" | Texto adicionado antes do número (ex.: "R$ ", "$"). | | suffix | string | "" | Texto adicionado depois do número (ex.: "%", " pts"). | | locale | string | "pt-BR" | Locale para Intl.NumberFormat. Controla separadores decimais e de milhar (ex.: "en-US", "de-DE"). | | useGrouping | boolean | true | Exibe separador de milhar conforme o locale (1.000 vs 1000). | | formatter | function \| null | null | Função de formatação personalizada. Substitui toda a lógica de formatação padrão. Recebe (value, element, index) e deve retornar uma string. |

Animação

| Opção | Tipo | Padrão | Descrição | |---|---|---|---| | easing | string \| function | "easeOutCubic" | Curva de aceleração da animação. Strings aceitas: "linear", "easeInQuad", "easeOutQuad", "easeInOutQuad", "easeInCubic", "easeOutCubic", "easeInOutCubic", "easeOutQuart", "easeOutExpo". Também aceita uma função (t: number) => number onde t vai de 0 a 1. |

Comportamento

| Opção | Tipo | Padrão | Descrição | |---|---|---|---| | sleep | number | 0 | Tempo de espera em milissegundos antes de a animação começar. 0 inicia imediatamente. Útil para escalonar múltiplos contadores ou aguardar após um elemento entrar na viewport. O sleep é cancelado se .stop(), .pause() ou .destroy() for chamado antes de ele disparar. | | respectReducedMotion | boolean | true | Quando true, respeita prefers-reduced-motion: reduce do sistema e força duration: 0 (transição instantânea). Veja a seção Acessibilidade. | | autostart | boolean | true | Inicia a animação automaticamente ao criar a instância. Se false, a animação fica aguardando uma chamada manual a .start(). | | startOnView | boolean | false | Usa IntersectionObserver para iniciar a animação somente quando o elemento entra na viewport. Ignorado em modo headless (sem DOM). | | once | boolean | true | Usado com startOnView: se true, a animação dispara apenas na primeira vez que o elemento aparecer. Se false, reinicia toda vez que o elemento entrar na viewport. |

IntersectionObserver (usado com startOnView)

| Opção | Tipo | Padrão | Descrição | |---|---|---|---| | root | Element \| null | null | Elemento raiz do IntersectionObserver. null usa o viewport da janela. | | rootMargin | string | "0px" | Margem ao redor do root, no formato CSS (ex.: "0px 0px -100px 0px"). Permite disparar antes ou depois do elemento estar completamente visível. | | threshold | number \| number[] | 0.1 | Fração do elemento que precisa estar visível para disparar. 0.1 = 10%, 1 = 100% visível. |

Callbacks

| Opção | Tipo | Descrição | |---|---|---| | onUpdate | function \| null | Chamado a cada frame da animação com (value, element, index). element é null em modo headless. | | onComplete | function \| null | Chamado uma vez quando a animação termina com (value, element, index). element é null em modo headless. |


Instância — elemento único

Métodos

| Método | Descrição | |---|---| | .start() | Inicia a animação. Se estiver pausada, retoma do ponto onde parou. Se já estiver rodando, não faz nada. | | .pause() | Pausa a animação preservando o progresso atual. | | .resume() | Retoma a animação do ponto em que foi pausada. | | .stop() | Para a animação e reseta o progresso interno (mas não o valor exibido). | | .reset() | Para a animação e volta o valor exibido para start. | | .set(value) | Define o valor exibido diretamente, sem animação. Para qualquer animação em curso. | | .update(nextEnd, nextOptions?) | Muda o valor final (e opcionalmente outras opções) e reinicia a animação do valor atual. | | .destroy() | Para a animação, desconecta o observer e marca a instância como destruída. Chamadas subsequentes são ignoradas. |

Getters

| Getter | Tipo | Descrição | |---|---|---| | .value | number | Valor numérico atual (sem formatação). | | .running | boolean | true se a animação estiver em execução. | | .paused | boolean | true se a animação estiver pausada. | | .waiting | boolean | true se a animação estiver aguardando o sleep disparar. |


Instância de grupo — múltiplos elementos

Retornada quando target resolve para mais de um elemento.

Métodos

Os mesmos da instância única, aplicados a todos os elementos: start(), pause(), resume(), stop(), reset(), destroy()

set(value | value[]) — aceita um único valor (aplicado a todos) ou um array (um valor por elemento).

update(nextEnd | nextEnd[], nextOptions?) — aceita um único valor final ou um array de valores finais.

Getters

| Getter | Tipo | Descrição | |---|---|---| | .values | number[] | Array com o valor atual de cada elemento. | | .running | boolean | true se ao menos um elemento estiver animando. | | .paused | boolean | true se ao menos um elemento estiver pausado. | | .waiting | boolean | true se ao menos um elemento estiver aguardando o sleep disparar. | | .count | number | Quantidade de elementos no grupo. |


Exemplos de controle manual

const counter = counterUp("#score", { end: 500, autostart: false });

// Inicia manualmente
counter.start();

// Pausa e retoma
counter.pause();
counter.resume();

// Muda o valor exibido sem animação
counter.set(250);

// Muda o alvo e reinicia a animação
counter.update(1000, { duration: 800 });

// Lê o valor atual em qualquer momento
console.log(counter.value);

// Libera recursos ao remover o componente
counter.destroy();

Acessibilidade — prefers-reduced-motion

Por padrão a biblioteca respeita a preferência do sistema do usuário. Quando prefers-reduced-motion: reduce está ativo, a duração é forçada a 0 e o contador vai direto ao valor final — sem animação.

A leitura da preferência acontece no momento da criação da instância (e em cada chamada de update()). Mudanças posteriores na configuração do sistema operacional não afetam contadores já em execução; se sua aplicação precisa reagir a essa mudança em tempo real, escute o MediaQueryList e chame instance.update(end) manualmente:

const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
mql.addEventListener("change", () => counter.update(counter.value));

Para desabilitar o comportamento (animar sempre, mesmo com a preferência ativa), passe respectReducedMotion: false.


TypeScript

O pacote inclui declarações nativas em dist/counterup.d.ts. Nenhuma instalação extra é necessária.

import { counterUp } from "@nullsablex/counter-up";
import type {
  CounterUpOptions,
  CounterUpInstance,
  CounterUpGroupInstance,
} from "@nullsablex/counter-up";

// Instância única — tipo inferido automaticamente
const counter: CounterUpInstance = counterUp("#total", {
  end: 1000,
  duration: 1500,
  prefix: "R$ ",
  decimals: 2,
  onComplete: (value) => console.log("Fim:", value),
});

// Modo headless — target null → CounterUpInstance garantido
const headless: CounterUpInstance = counterUp(null, {
  start: 0,
  end: 100,
  duration: 2000,
  onUpdate: (value) => updateProgressBar(value),
});

// Opções reutilizáveis com tipagem
const opts: CounterUpOptions = {
  duration: 1800,
  easing: "easeOutCubic",
  locale: "en-US",
};

counterUp(".metric", opts);

Tipos exportados

| Tipo | Descrição | |---|---| | CounterUpOptions | Interface completa de opções | | CounterUpInstance | Instância retornada para elemento único ou headless | | CounterUpGroupInstance | Instância retornada para múltiplos elementos | | CounterUpTarget | União de todos os tipos aceitos como target | | EasingFunction | (t: number) => number | | FormatterFunction | (value, element, index) => string | | CounterUpCallback | Assinatura de onUpdate e onComplete |


Build (desenvolvimento da biblioteca)

npm run build

Arquivos gerados em dist/ (a partir de src/counterup.js via scripts/build.mjs):


Testes

npm test           # roda toda a suíte uma vez
npm run test:watch # modo watch durante desenvolvimento

A suíte (tests/counterup.test.js) usa Vitest + jsdom e cobre DOM, modo headless, pause/resume/destroy/reset, auto-detecção locale-aware, prefers-reduced-motion, startOnView + update() e a API de grupo.


CI/CD

Workflows configurados em .github/workflows/:


Projeto

Licença

MIT. Consulte LICENSE.

Contribuição

Veja CONTRIBUTING.md.

Código de Conduta

Veja CODE_OF_CONDUCT.md.

Histórico de versões

Veja CHANGELOG.md.