7.2 KiB
Eidos CountUp
<CountUp> es un service component — anima un número de from → to
con un settle de muelle (spring) al entrar en viewport, y renderiza CADA
frame a través de uix.format.numbers (locale reactivo + preferencias de
separadores del runtime).
Hermano de <FormatNumber> (diseño de familia:
docs/decisions/design-text-effects.md, D-T2): formatear es de FormatNumber,
contar es de CountUp; ambos comparten el mismo formatter del ecosistema.
Superficie
El caso de uso real es una estadística animada de titular — un número grande que cuenta al entrar en vista, con afijos y formato:
<CountUp to={12500} suffix="+" /> <!-- 12.500+ -->
<CountUp to={1200000} formatStyle="currency" currency="EUR" notation="compact" /> <!-- 1,2 mill € -->
<CountUp to={0.999} formatStyle="percent" maximumFractionDigits={1} /> <!-- 99,9% -->
<CountUp to={3.2} from={8} direction="down" suffix="s" maximumFractionDigits={1} /> <!-- 3,2s -->
<CountUp to={2048} formatStyle="unit" unit="byte" notation="compact" />
Renderiza data-count-up con font-variant-numeric: tabular-nums (los dígitos
no bailan de ancho mientras cuenta) y, si hay prefix/suffix, cada afijo en
su propio span ([data-count-up-prefix] / [data-count-up-value] /
[data-count-up-suffix]) para que el consumidor pueda estilarlos.
Props clave
| Prop | Tipo | Default | Notas |
|---|---|---|---|
to |
number |
— | Valor final. |
from |
number |
0 |
Valor inicial. |
direction |
'up' | 'down' |
'up' |
'down' arranca en to y asienta en from. |
delay |
number (s) |
0 |
Espera tras entrar en viewport. |
duration |
number (s) |
2 |
Duración REAL del asentamiento, para cualquier magnitud (λ derivada del recorrido; A-69). |
startWhen |
boolean |
true |
Gate adicional al viewport. |
useGrouping |
boolean |
runtime | Separador de millares on/off — el locale decide CUÁL. |
formatStyle |
'decimal' | 'currency' | 'percent' | 'unit' |
'decimal' |
Mismo vocabulario que <FormatNumber> (mismo formatter). |
currency / unit |
string |
runtime / — | Para formatStyle currency / unit. |
notation |
'standard' | 'compact' |
'standard' |
compact → "1,2M". |
maximumFractionDigits / minimumFractionDigits |
number |
auto | Precisión del display. |
prefix / suffix |
string |
'' |
Afijos estáticos ($, +, %…) en su propio span. |
locale |
BCP-47 | runtime | Override por instancia (tercer arg del runtime). |
onStart / onEnd |
() => void |
— | Callbacks camelCase. |
as |
keyof HTMLElementTagNameMap |
'span' |
Tag HTML. |
Decisiones
- Cada frame rutea por
uix.format.numbers(fallback aIntlsolo sin runtime) — el seed que porta (text/count) hardcodeabaIntl.NumberFormat('en-US')+ un hack dereplacepara el separador. - El prop
separatordel seed NO se porta: los separadores son del locale/preferencias del runtime;useGroupingcubre el on/off. - Sí se portan
prefix/suffixdel seed (los afijos que lo hacen un stat, "12.500+" / "3,2s"), en spans propios contabular-numspara que el número no salte de ancho al contar. - Superficie de formato completa de
<FormatNumber>(formatStyle·currency·unit·notation· fracciones) — cumple la promesa D-T2 de "ambos comparten el mismo formatter": un count anima hacia una moneda, un porcentaje o un compacto, no solo un decimal pelado. - Decimales derivados de
from/to(los que muestre el valor con más precisión), con umbral de reposo0.5 × 10^-decimales— el muelle para en cuanto el display ya no puede cambiar. - Spring analítico (oscilador armónico amortiguado en forma cerrada,
masa 1): el driver
springde$motionanima propiedades CSS de un elemento, no callbacks numéricos, así que no aplica aquí. - Ciudadanía: IO y rAF vía
eidos.dom(observeIntersection/requestFrame); timers de delay/end víaeidos.timers; reduced motion víaeidos.dom.prefersReducedMotion→ salta directo al valor final. textContentimperativo por frame (como el seed): el update de 60fps no pasa por la reactividad de Svelte a propósito.- No CSS recipe: marker
[data-count-up]solo para tooling. - No emite eventos sema.
Baseline
Port limpio del seed web/routes/demos/animations/text/count/count.svelte
(colección demos/animations, intacta como referencia comparativa — D5/D7 de
docs/architecture/packs.md, nota de provenance). El seed ya traía: spring DHO
analítico, trigger por viewport, direction, delay/duration,
startWhen, reduced-motion (salto al final). Sus déficits de ciudadanía
(en-US fijo, setTimeout/IO/rAF crudos, hack de separador) son
exactamente lo que este componente corrige vía runtime.
Comparativa
| Capacidad | Eidos | countup.js | react-countup | number-flow |
|---|---|---|---|---|
| Locale reactivo desde runtime | Sí | No (options fijas) | No | Parcial (Intl estático) |
Formatter compartido con el resto del sistema (uix.format) |
Sí | No | No | No |
| Spring settle (no easing lineal) | Sí | easing propio | easing propio | Sí (transiciones) |
| Trigger por viewport | Sí | Manual/plugin | Sí | No (reacciona a cambios) |
| Reduced motion | Sí | No | No | Sí |
| Timers/rAF del ecosistema (cancelables, testeables) | Sí | No | No | No |
Referencias externas: countup.js (https://github.com/inorganik/countUp.js) · react-countup (https://github.com/glennreyes/react-countup) · number-flow (https://number-flow.barvian.me) · Intl.NumberFormat (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).
Gaps
- Dígitos rodantes por columna (estilo number-flow / odometer): diferir — es otro efecto visual (máscaras por dígito), no un count; si se pide, sería componente aparte.
separatorcustom del seed: descartar — el locale del runtime es el dueño de los separadores (decisión de ciudadanía, ver Decisiones).- Formatos currency/unit/percent durante el count: diferir — el primer
consumidor real decide si
<CountUp>absorbe las options completas de<FormatNumber>o compone con él. - Re-count al cambiar
totras asentarse: diferir — el seed cuenta una vez por entrada en viewport; un contador "live" continuo es otro contrato.
Passive justification
Pasivo por diseño: no recibe input del usuario ni expone estados
interactivos — cuenta sola al entrar en viewport y termina. Sin eventos
sema (nada que percibir semánticamente: es contenido que se asienta), sin
soma (no hay máquina de estados accesible), sin CSS (hereda la tipografía
del contexto, como <FormatNumber>).
Referencias
- Seed portado:
web/routes/demos/animations/text/count/count.svelte(intacto como referencia comparativa, provenance endocs/architecture/packs.md). - Engine de números:
src/arts/format/numbers/engine-numbers.ts
Audit exceptions
E-2.2 exception:<CountUp>is a service component — it renders locale-formatted counting text via$format, with no visual recipe of its own. No{name}.cssby design.