Ir al contenido
Evaluar mi proyecto
Componentes · Texto y animacionesDisponible

Contador animado

Un número que cuenta hasta su valor al entrar en pantalla.

Usuarios activos
Satisfacción
Ingresos al mes
Demostración interactiva

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

count-up.tsx73 líneas
Descargar
"use client";

/**
 * Número que cuenta hasta su valor cuando entra en pantalla, con frenado suave al final y cifras de ancho fijo para que no baile.
 * Con movimiento reducido muestra el valor final de una vez.
 *
 *   <CountUp to={12400} suffix="+" />
 *   <CountUp to={98.6} decimals={1} suffix="%" duration={2200} />
 *   <CountUp to={1.2} decimals={1} prefix="$" suffix="M" />
 */

import { useEffect, useRef, useState, type CSSProperties } from "react";

export type CountUpProps = {
  to: number;
  from?: number;
  /** Duración en milisegundos. */
  duration?: number;
  decimals?: number;
  prefix?: string;
  suffix?: string;
  /** Formato numérico, por ejemplo "es-MX" o "en-US". */
  locale?: string;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gccu-value { display: inline-block; font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
`;

const easeOutExpo = (t: number) => (t >= 1 ? 1 : 1 - 2 ** (-10 * t));

export function CountUp({ to, from = 0, duration = 1600, decimals = 0, prefix = "", suffix = "", locale = "es-MX", className, style }: CountUpProps) {
  const ref = useRef<HTMLSpanElement>(null);
  const [value, setValue] = useState(from);
  const format = (number: number) => `${prefix}${number.toLocaleString(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}${suffix}`;

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      const frame = requestAnimationFrame(() => setValue(to));
      return () => cancelAnimationFrame(frame);
    }

    let frame = 0;
    const observer = new IntersectionObserver(([entry]) => {
      if (!entry.isIntersecting) return;
      observer.disconnect();
      const start = performance.now();
      const tick = (now: number) => {
        const progress = Math.min(1, (now - start) / duration);
        setValue(from + (to - from) * easeOutExpo(progress));
        if (progress < 1) frame = requestAnimationFrame(tick);
      };
      frame = requestAnimationFrame(tick);
    }, { threshold: 0.4 });
    observer.observe(element);

    return () => { observer.disconnect(); cancelAnimationFrame(frame); };
  }, [from, to, duration]);

  return (
    <>
      <style>{CSS}</style>
      <span ref={ref} className={["gccu-value", className].filter(Boolean).join(" ")} style={style} aria-label={format(to)} role="text">
        <span aria-hidden="true">{format(value)}</span>
      </span>
    </>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/contador-animado

Ejemplo de uso de Contador animado

Después de copiar count-up.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { CountUp } from "./count-up";

export default function Ejemplo() {
  return <CountUp prefix="Ejemplo" />;
}

Propiedades de CountUp

CountUp acepta 9 propiedades; 1 es obligatoria.

PropiedadTipoDescripción
tonumberObligatoria.
from?numberOpcional.
duration?numberDuración en milisegundos.
decimals?numberOpcional.
prefix?stringOpcional.
suffix?stringOpcional.
locale?stringFormato numérico, por ejemplo "es-MX" o "en-US".
className?stringOpcional.
style?CSSPropertiesOpcional.

Accesibilidad y rendimiento

El archivo tiene 73 líneas (2 KB) y no necesita @keyframes. Esto es lo que hace Contador animado por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Declara roles ARIA para lectores de pantalla: text.
  • Se pausa cuando sale de la pantalla para no gastar batería ni CPU.
  • Anima con requestAnimationFrame, sincronizado con el refresco de pantalla.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React · Next.js (quita la directiva use client fuera de Next.js) · Sin Tailwind

Acerca del producto

Un número que cuenta desde un valor inicial hasta el final cuando entra en pantalla, con un frenado suave al final y cifras de ancho fijo para que el texto no baile. Admite prefijo, sufijo, decimales y formato regional.

Incluye

Un solo archivo React + TypeScript, con los estilos dentro. No usa Tailwind ni bibliotecas externas.

  • Prefijo, sufijo y decimales
  • Formato regional con Intl (es-MX, en-US…)
  • Valor final disponible para lectores de pantalla
  • Con movimiento reducido muestra el valor final

Cómo usarlo

Pasa to y, si hace falta, from, duration, decimals, prefix y suffix. Tamaño y color por style o className.