返回动效库
文字进阶

数字滚动

数字通过垂直滚动方式从 0 滚动至目标值,适合数据面板和统计展示。

滚动时2s

交互预览

动效定义与代码

{
  "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}"
    }
  ]
}