Contador animado
Un número que cuenta hasta su valor al entrar en pantalla.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"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.
npx shadcn@latest add https://www.gassierconsulting.com/r/contador-animadoEjemplo de uso de Contador animado
Después de copiar count-up.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { CountUp } from "./count-up";
export default function Ejemplo() {
return <CountUp prefix="Ejemplo" />;
}Propiedades de CountUp
CountUp acepta 9 propiedades; 1 es obligatoria.
| Propiedad | Tipo | Descripción |
|---|---|---|
to | number | Obligatoria. |
from? | number | Opcional. |
duration? | number | Duración en milisegundos. |
decimals? | number | Opcional. |
prefix? | string | Opcional. |
suffix? | string | Opcional. |
locale? | string | Formato numérico, por ejemplo "es-MX" o "en-US". |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
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".
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.