
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>高级数字翻滚动画</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script>tailwind.config = {theme: {extend: {colors: {primary: '#3B82F6',secondary: '#10B981',accent: '#8B5CF6',dark: '#1F2937',number: '#1E293B', },fontFamily: {inter: ['Inter', 'system-ui', 'sans-serif'],},},}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.digit-container {position: relative;display: inline-block;height: 1.2em;overflow: hidden;margin: 0 0.05em;background: rgba(255, 255, 255, 0.1);border-radius: 0.15em;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);}.digit-wrapper {display: flex;flex-direction: column;transition: transform 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);}.digit {height: 1.2em;line-height: 1.2em;text-align: center;font-variant-numeric: tabular-nums;padding: 0 0.1em;color: theme('colors.number'); }.separator {margin: 0 0.15em;color: theme('colors.number/70'); }.btn-pulse {animation: pulse 2s infinite;}@keyframes pulse {0% {box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);}70% {box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);}100% {box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);}}}</style>
</head>
<body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen font-inter text-dark"><div class="container mx-auto px-4 py-16 max-w-5xl"><header class="text-center mb-12"><h1 class="text-[clamp(2rem,5vw,3.5rem)] font-bold text-dark mb-4">高级数字翻滚动画</h1><p class="text-gray-600 text-lg max-w-2xl mx-auto">体验流畅的数字变化动画效果,当设置新数字时,当前数字会平滑滚动到目标值</p></header><main class="bg-white rounded-3xl shadow-xl p-8 mb-12 transform hover:shadow-2xl transition-all duration-300 relative overflow-hidden"><!-- 装饰元素 --><div class="absolute -top-10 -right-10 w-40 h-40 bg-primary/10 rounded-full blur-3xl"></div><div class="absolute -bottom-16 -left-16 w-48 h-48 bg-accent/10 rounded-full blur-3xl"></div><div class="flex flex-col md:flex-row gap-8 items-center justify-center relative z-10"><div class="number-display mb-8 md:mb-0 w-full max-w-md"><div id="number-display" class="text-[clamp(2.5rem,8vw,5rem)] font-bold flex justify-center flex-wrap"><!-- 数字将在这里动态生成 --></div></div><div class="controls w-full md:w-auto"><div class="bg-white/80 backdrop-blur-sm p-6 rounded-2xl shadow-lg border border-gray-100"><h2 class="text-xl font-semibold mb-4 text-center">设置数字</h2><div class="mb-5"><label for="target-number" class="block text-sm font-medium text-gray-700 mb-1.5">目标数字</label><div class="relative"><span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500"><i class="fa fa-hashtag"></i></span><input type="number" id="target-number" class="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-all" placeholder="输入目标数字" value="12345"></div></div><div class="mb-5"><label for="animation-duration" class="block text-sm font-medium text-gray-700 mb-1.5">动画速度</label><div class="relative"><input type="range" id="animation-duration" min="300" max="3000" step="100" value="1500" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-primary"><div class="flex justify-between text-xs text-gray-500 mt-1"><span>快</span><span id="duration-value">中等</span><span>慢</span></div></div></div><div class="mb-5"><label class="block text-sm font-medium text-gray-700 mb-1.5">数字格式</label><div class="grid grid-cols-2 gap-3"><label class="flex items-center p-3 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50 transition-all"><input type="radio" name="number-format" value="comma" checked class="form-radio text-primary focus:ring-primary"><span class="ml-2 text-sm text-gray-700">逗号分隔 (12,345)</span></label><label class="flex items-center p-3 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50 transition-all"><input type="radio" name="number-format" value="none" class="form-radio text-primary focus:ring-primary"><span class="ml-2 text-sm text-gray-700">无分隔 (12345)</span></label></div></div><div class="mb-5"><label class="block text-sm font-medium text-gray-700 mb-1.5">动画类型</label><div class="grid grid-cols-2 gap-3"><label class="flex items-center p-3 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50 transition-all"><input type="radio" name="animation-type" value="normal" checked class="form-radio text-primary focus:ring-primary"><span class="ml-2 text-sm text-gray-700">普通滚动</span></label><label class="flex items-center p-3 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50 transition-all"><input type="radio" name="animation-type" value="random" class="form-radio text-primary focus:ring-primary"><span class="ml-2 text-sm text-gray-700">随机滚动</span></label></div></div><button id="set-number" class="w-full bg-gradient-to-r from-primary to-accent hover:opacity-95 text-white font-medium py-3 px-4 rounded-lg transition-all duration-300 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary/50 btn-pulse"><i class="fa fa-refresh mr-2"></i>应用动画</button></div></div></div></main><section class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12"><div class="bg-white rounded-xl shadow-md p-6 hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1"><div class="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-4"><i class="fa fa-tachometer text-xl text-primary"></i></div><h3 class="text-lg font-semibold mb-2">实时统计</h3><p class="text-gray-600">适合展示实时变化的数据,如网站访问量、销售额或股票价格等,让数字变化更加生动直观。</p></div><div class="bg-white rounded-xl shadow-md p-6 hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1"><div class="w-12 h-12 bg-secondary/10 rounded-full flex items-center justify-center mb-4"><i class="fa fa-line-chart text-xl text-secondary"></i></div><h3 class="text-lg font-semibold mb-2">数据可视化</h3><p class="text-gray-600">为数据图表添加动画效果,使数据变化过程更加清晰,增强数据展示的视觉冲击力。</p></div><div class="bg-white rounded-xl shadow-md p-6 hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1"><div class="w-12 h-12 bg-accent/10 rounded-full flex items-center justify-center mb-4"><i class="fa fa-gamepad text-xl text-accent"></i></div><h3 class="text-lg font-semibold mb-2">游戏计分</h3><p class="text-gray-600">用于游戏中的计分系统,当分数变化时提供流畅的动画效果,提升游戏体验。</p></div></section><footer class="text-center text-gray-500 text-sm"><p>高级数字翻滚动画 © 2025</p></footer></div><script>let currentNumber = 0;const numberDisplay = document.getElementById('number-display');const targetNumberInput = document.getElementById('target-number');const animationDurationInput = document.getElementById('animation-duration');const durationValue = document.getElementById('duration-value');const setNumberButton = document.getElementById('set-number');const numberFormatRadios = document.querySelectorAll('input[name="number-format"]');const animationTypeRadios = document.querySelectorAll('input[name="animation-type"]');function updateNumberDisplay(number) {numberDisplay.innerHTML = '';const formatSetting = Array.from(numberFormatRadios).find(radio => radio.checked).value;let formattedNumber;if (formatSetting === 'comma') {formattedNumber = number.toLocaleString('en-US');} else {formattedNumber = number.toString();}const parts = formattedNumber.split(',');let digitCount = 0;parts.forEach((part, partIndex) => {for (let i = 0; i < part.length; i++) {const digit = part[i];const digitContainer = document.createElement('div');digitContainer.className = 'digit-container';const digitWrapper = document.createElement('div');digitWrapper.className = 'digit-wrapper';digitWrapper.dataset.current = digit;for (let j = 0; j < 10; j++) {const digitElement = document.createElement('div');digitElement.className = 'digit';digitElement.textContent = j;digitWrapper.appendChild(digitElement);}const translateY = -parseInt(digit) * 10; digitWrapper.style.transform = `translateY(${translateY}%)`;digitContainer.appendChild(digitWrapper);numberDisplay.appendChild(digitContainer);digitCount++;if (formatSetting === 'comma' && i === part.length - 1 && partIndex < parts.length - 1) {const separator = document.createElement('span');separator.className = 'separator';separator.textContent = ',';numberDisplay.appendChild(separator);}}});}function animateWithRandomRoll(targetNumber, duration = 1500) {if (isNaN(targetNumber)) return;const digits = numberDisplay.querySelectorAll('.digit-wrapper');const currentNumberString = currentNumber.toString();const targetNumberString = targetNumber.toString();const maxLength = Math.max(currentNumberString.length, targetNumberString.length);const paddedCurrent = currentNumberString.padStart(maxLength, '0');const paddedTarget = targetNumberString.padStart(maxLength, '0');if (digits.length !== maxLength) {updateNumberDisplay(targetNumber);currentNumber = targetNumber;return;}for (let i = 0; i < maxLength; i++) {const currentDigit = parseInt(paddedCurrent[i]);const targetDigit = parseInt(paddedTarget[i]);if (currentDigit !== targetDigit) {const digitWrapper = digits[i];const randomRolls = 3 + Math.floor(Math.random() * 5); const totalFrames = 60 * (duration / 1000); const framesPerRoll = Math.max(10, Math.floor(totalFrames / randomRolls));let currentFrame = 0;let currentDisplayedDigit = currentDigit;const rollInterval = setInterval(() => {currentFrame++;if (currentFrame % framesPerRoll === 0 && currentFrame < totalFrames - framesPerRoll) {currentDisplayedDigit = Math.floor(Math.random() * 10);digitWrapper.style.transform = `translateY(${-currentDisplayedDigit * 10}%)`;}if (currentFrame >= totalFrames - framesPerRoll) {clearInterval(rollInterval);digitWrapper.style.transitionDuration = `${framesPerRoll * 16.67}ms`; digitWrapper.style.transform = `translateY(${-targetDigit * 10}%)`;digitWrapper.dataset.current = targetDigit;}}, 16.67); }}currentNumber = targetNumber;}function animateWithNormalRoll(targetNumber, duration = 1500) {if (isNaN(targetNumber)) return;const digits = numberDisplay.querySelectorAll('.digit-wrapper');const currentNumberString = currentNumber.toString();const targetNumberString = targetNumber.toString();const maxLength = Math.max(currentNumberString.length, targetNumberString.length);const paddedCurrent = currentNumberString.padStart(maxLength, '0');const paddedTarget = targetNumberString.padStart(maxLength, '0');if (digits.length !== maxLength) {updateNumberDisplay(targetNumber);currentNumber = targetNumber;return;}for (let i = 0; i < maxLength; i++) {const currentDigit = parseInt(paddedCurrent[i]);const targetDigit = parseInt(paddedTarget[i]);if (currentDigit !== targetDigit) {const digitWrapper = digits[i];digitWrapper.style.transitionDuration = `${duration}ms`;const translateY = -targetDigit * 10; digitWrapper.style.transform = `translateY(${translateY}%)`;digitWrapper.dataset.current = targetDigit;}}currentNumber = targetNumber;}function animateToNumber(targetNumber, duration = 1500) {const animationType = Array.from(animationTypeRadios).find(radio => radio.checked).value;if (animationType === 'random') {animateWithRandomRoll(targetNumber, duration);} else {animateWithNormalRoll(targetNumber, duration);}}function updateSpeedDisplay() {const duration = parseInt(animationDurationInput.value);let speedText;if (duration < 800) {speedText = "极快";} else if (duration < 1200) {speedText = "快速";} else if (duration < 1800) {speedText = "中等";} else if (duration < 2500) {speedText = "慢速";} else {speedText = "极慢";}durationValue.textContent = speedText;}updateNumberDisplay(currentNumber);updateSpeedDisplay();animationDurationInput.addEventListener('input', updateSpeedDisplay);setNumberButton.addEventListener('click', () => {const targetNumber = parseInt(targetNumberInput.value);const duration = parseInt(animationDurationInput.value);animateToNumber(targetNumber, duration);setNumberButton.classList.add('scale-95');setTimeout(() => {setNumberButton.classList.remove('scale-95');}, 150);});targetNumberInput.addEventListener('keydown', (e) => {if (e.key === 'Enter') {setNumberButton.click();}});numberFormatRadios.forEach(radio => {radio.addEventListener('change', () => {updateNumberDisplay(currentNumber);});});animationTypeRadios.forEach(radio => {radio.addEventListener('change', () => {});});setTimeout(() => {animateToNumber(56789, 2000);}, 500);</script>
</body>
</html>