Count Up
atroui/count-upFreeBlock
In-view count-up number with reduced-motion support.
Install it
npx shadcn@latest add https://atroui.com/r/count-up.jsonSource
1"use client"23import * as React from "react"45/**6 * Counts up from 0 to `value` over `duration` ms with out-quart easing.7 * Runs once on first in-view and respects prefers-reduced-motion.8 */9export function CountUp({10 value,11 duration = 1200,12 className,13 ariaLabel,14}: {15 value: number16 duration?: number17 className?: string18 ariaLabel?: string19}) {20 const ref = React.useRef<HTMLSpanElement | null>(null)21 const [display, setDisplay] = React.useState(0)22 const startedRef = React.useRef(false)2324 React.useEffect(() => {25 const node = ref.current26 if (!node) return2728 const reduced =29 window.matchMedia?.("(prefers-reduced-motion: reduce)").matches30 if (reduced) {31 setDisplay(value)32 return33 }3435 let raf = 036 const start = () => {37 if (startedRef.current) return38 startedRef.current = true39 const t0 = performance.now()40 const from = 041 const to = value42 const ease = (t: number) => 1 - Math.pow(1 - t, 4)4344 const step = (now: number) => {45 const t = Math.min(1, (now - t0) / duration)46 setDisplay(Math.round(from + (to - from) * ease(t)))47 if (t < 1) raf = requestAnimationFrame(step)48 }49 raf = requestAnimationFrame(step)50 }5152 if (typeof IntersectionObserver === "undefined") {53 start()54 return () => cancelAnimationFrame(raf)55 }5657 const io = new IntersectionObserver(58 (entries) => {59 if (entries[0]?.isIntersecting) {60 start()61 io.disconnect()62 }63 },64 { threshold: 0.3 }65 )66 io.observe(node)67 return () => {68 io.disconnect()69 cancelAnimationFrame(raf)70 }71 }, [value, duration])7273 return (74 <span75 ref={ref}76 className={className}77 aria-label={ariaLabel ?? String(value)}78 >79 {display}80 </span>81 )82}
Files it writes
More from atroui
All 82 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Analytics Provideranalytics-provider | atroui | Blocks | Free | 1 dep | |
| AR Portfolioar-portfolio | atroui | Blocks | Free | 1 dep | |
| Before / Afterbefore-after-slider | atroui | Blocks | Free | no deps | |
| Calendly Embedcalendly-embed | atroui | Blocks | Free | 1 dep | |
| Changelogchangelog | atroui | Blocks | Free | no deps | |
| Command Menucommand-menu | atroui | Blocks | Free | 4 deps | |
| Contact Formcontact-form | atroui | Blocks | Free | 2 deps | |
| Contextual CTAcontextual-cta | atroui | Blocks | Free | 2 deps |
