数字滚动
数字通过垂直滚动方式从 0 滚动至目标值,适合数据面板和统计展示。
交互预览
动效定义与代码
{
"slug": "counter-roll",
"name": "数字滚动",
"nameEn": "Counter Roll",
"description": "数字通过垂直滚动方式从 0 滚动至目标值,适合数据面板和统计展示。",
"descriptionEn": "Digits scroll vertically from 0 to a target value. Ideal for dashboards, stats, and data visualization sections.",
"category": "text",
"tags": [
"counter",
"number",
"scroll",
"data",
"stats"
],
"trigger": "on-scroll",
"difficulty": "intermediate",
"duration": "2s",
"easing": "cubic-bezier(0.16, 1, 0.3, 1)",
"cssProperties": [
"transform"
],
"isGPUAccelerated": true,
"previewBg": "light",
"keywords": [
"counter",
"number",
"roll",
"count up",
"statistics",
"dashboard"
],
"useCases": [
"Dashboard stat counters",
"Landing page metrics",
"Achievement milestones",
"Pricing page numbers"
],
"relatedAnimations": [
"typewriter",
"text-gradient-flow"
],
"recommendedStyles": [
"corporate-clean",
"minimalist-flat",
"dark-mode"
],
"codeSnippets": [
{
"label": "CSS Keyframes",
"language": "css",
"code": "@keyframes counter-roll {\n from {\n transform: translateY(0);\n }\n to {\n transform: translateY(calc(var(--counter-target, -9) * -1em));\n }\n}\n\n.counter-roll-container {\n display: inline-flex;\n overflow: hidden;\n height: 1em;\n line-height: 1;\n}\n\n.counter-roll-digit {\n will-change: transform;\n animation: counter-roll 2s cubic-bezier(0.16, 1, 0.3, 1) both;\n animation-delay: calc(var(--digit-index, 0) * 150ms);\n}\n\n/* Each digit column contains 0-9 stacked vertically */\n.counter-roll-column {\n display: flex;\n flex-direction: column;\n}\n\n.counter-roll-column span {\n height: 1em;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .counter-roll-digit {\n animation: none;\n overflow: visible;\n width: auto;\n }\n}"
},
{
"label": "Tailwind CSS",
"language": "css",
"code": "/* Add to your global CSS or Tailwind v4 theme */\n@keyframes counter-roll {\n from {\n transform: translateY(0);\n }\n to {\n transform: translateY(calc(var(--counter-target, -9) * -1em));\n }\n}\n\n@utility counter-roll-container {\n display: inline-flex;\n overflow: hidden;\n height: 1em;\n line-height: 1;\n}\n\n@utility animate-counter-roll {\n will-change: transform;\n animation: counter-roll 2s cubic-bezier(0.16, 1, 0.3, 1) both;\n animation-delay: calc(var(--digit-index, 0) * 150ms);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .counter-roll-digit {\n animation: none;\n overflow: visible;\n width: auto;\n }\n}"
},
{
"label": "Framer Motion",
"language": "tsx",
"code": "import { motion, useInView } from \"framer-motion\";\nimport { useRef } from \"react\";\n\n// Tip: respect user's motion preferences\n// import { useReducedMotion } from \"framer-motion\";\n// const reduced = useReducedMotion();\n// If reduced, show final number immediately without rolling\n\nfunction CounterRoll({ target }: { target: number }) {\n const ref = useRef<HTMLDivElement>(null);\n const isInView = useInView(ref, { once: true });\n\n const digits = String(target).split(\"\");\n\n return (\n <div ref={ref} style={{ display: \"inline-flex\", overflow: \"hidden\", height: \"1em\" }}>\n {digits.map((digit, i) => (\n <div key={i} style={{ overflow: \"hidden\", height: \"1em\" }}>\n <motion.div\n initial={{ y: 0 }}\n animate={isInView ? { y: `${-Number(digit)}em` } : {}}\n transition={{\n duration: 2,\n ease: [0.16, 1, 0.3, 1],\n delay: i * 0.15,\n }}\n style={{ display: \"flex\", flexDirection: \"column\" }}\n >\n {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((n) => (\n <span key={n} style={{ height: \"1em\", display: \"flex\", alignItems: \"center\" }}>\n {n}\n </span>\n ))}\n </motion.div>\n </div>\n ))}\n </div>\n );\n}"
}
]
}