倒计时计时器与SVG圆环动画不同步问题求助
解决倒计时数字与SVG圆环动画不同步问题
问题根源
setInterval并非精确计时:浏览器事件循环会导致每次回调存在几毫秒延迟,长时间累积后误差被放大,设置的分钟数越多,偏差越明显。- CSS动画与JS定时器独立运行:两者由浏览器不同模块调度,无法保证时间基准一致,自然会出现不同步。
解决方案
放弃CSS动画,改用时间戳计算剩余时间比例,让数字显示和SVG圆环进度绑定到同一时间基准,实时同步更新。
修改后的完整代码
HTML(无需改动)
<div class="body"> <div class="container text-center"> <div class="row"> <div class=" col-xs-12"> <h1 class="text-center">Timer</h1> </div> </div> <div class="row sliders"> <div class="col-xs-12 col-lg-6"> <h4>Minutes<span id="workspan"></span></h4> <input type="number" min="1" max="60" id="workrange" /> </div> </div> <div class="row" id="svg-container"> <div class="col-xs-12 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3"> <svg id="svgElem" viewbox="0 0 240 240" preserveAspectRatio="xMinyMin meet"> <g id="group" transform="rotate(-90 120,120)"> <circle id="backdrop" r="90" cy="120" cx="120" stroke-width="3" stroke="#444" fill="none"></circle> <circle id="workProgress" r="90" cy="120" cx="120" stroke-width="3" stroke="#45b1a1" fill="none"></circle> </g> </svg> </div> <div class="timeBtn"> <button id="start">Start</button> <div id="timer"></div> <button id="reset">Reset</button> </div> </div> </div> </div> <audio id="beep"> <source src="https://actions.google.com/sounds/v1/alarms/beep_short.ogg"/> </audio>
CSS(移除动画相关代码)
.body { font-size: 14px; background: #222; color: #e7e7e7; } h1 { font-size: 2em; } h4 { font-size: 1em; } #svg-container { position: relative; } .timeBtn { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 1.5em; } #timer { font-size: 3em; } button { background: none; border: none; font-size: 2em; color: #444; cursor: pointer; } button:hover { color: #e7e7e7; } /*---------------style the range bars-----------------------*/ input[type=range] { -webkit-appearance: none; } /*---------------------FIREFOX------------------------*/ #workrange { background: transparent; border: none; } /*---------------work range------------------------*/ #workrange::-moz-range-thumb { background: #45b1a1; border: none; cursor: pointer; } #workrange::-moz-range-track { height: 6px; background: transparent; border-bottom: 1px solid #45b1a1; } #workrange::-moz-range-progress { height: 6px; background: #45b1a1; } /*-----------------------CHROME-----------------------*/ /*---------------------work range----------------------*/ #workrange::-webkit-slider-thumb { -webkit-appearance: none; margin-top: -10px; height: 25px; width: 25px; background: #45b1a1; border: none; border-radius: 25px; cursor: pointer; } #workrange::-webkit-slider-runnable-track { width: 100%; height: 6px; background: #45b1a1; } /*----------------IE(edge only)?--------------------*/ /*----------workrange------*/ #workrange::-ms-track { background: transparent; color: transparent; border-bottom: 1px solid #45b1a1; padding-top: 10px; } #workrange::-ms-fill-lower { background: #45b1a1; } #workrange::-ms-thumb { background: #45b1a1; cursor: pointer; } /*------------------svg样式----------------*/ #workProgress { stroke-dasharray: 570px; stroke-dashoffset: 570px; } @media screen and (min-width: 525px) { body { font-size: 24px; } }
JavaScript(重写计时逻辑)
$(document).ready(function() { var worktime = 1; var isRunning = false; var countdownTimer; var totalSeconds; var startTime; var getAudio = $('#beep').trigger('load'); document.getElementById('workrange').disabled = false; var workrange = $('#workrange'); var workspan = $('#workspan'); var timer = $('#timer'); var progressCircle = $('#workProgress'); const FULL_DASH_OFFSET = 570; // 设置初始值 timer.text((worktime < 10 ? "0" + worktime : worktime) + ":00"); workrange.val(1); workspan.text(' ' + '1'); // 更新滑块显示 workrange.on("input", function() { workspan.text(' ' + workrange.val()); }); // 保存用户设置的时间 workrange.bind('mouseup touchend', function() { worktime = parseInt($(this).val()); timer.text((worktime < 10 ? "0" + worktime : worktime) + ":00"); resetProgressCircle(); }); // 重置圆环进度 function resetProgressCircle() { progressCircle.css('stroke-dashoffset', FULL_DASH_OFFSET); } // 计算并更新剩余时间和进度 function updateCountdown() { const elapsed = Date.now() - startTime; const remaining = Math.max(totalSeconds - Math.floor(elapsed / 1000), 0); // 更新数字显示 const mins = Math.floor(remaining / 60); const secs = remaining % 60; timer.text((mins < 10 ? "0" + mins : mins) + ":" + (secs < 10 ? "0" + secs : secs)); // 更新SVG圆环进度 const progressRatio = remaining / totalSeconds; progressCircle.css('stroke-dashoffset', FULL_DASH_OFFSET * progressRatio); // 倒计时结束 if (remaining <= 0) { clearInterval(countdownTimer); isRunning = false; getAudio.trigger('play'); document.getElementById('workrange').disabled = false; } } // 开始倒计时 $('#start').click(function() { if (!isRunning) { totalSeconds = worktime * 60; startTime = Date.now(); isRunning = true; document.getElementById('workrange').disabled = true; // 用高频定时器保证流畅更新,接近60fps countdownTimer = setInterval(updateCountdown, 16); } }); // 重置倒计时 $('#reset').click(function() { if (isRunning) { clearInterval(countdownTimer); isRunning = false; } timer.text((worktime < 10 ? "0" + worktime : worktime) + ":00"); resetProgressCircle(); document.getElementById('workrange').disabled = false; }); });
关键改进点
- 时间戳精准计算:通过
Date.now()获取绝对时间,计算已流逝时长,彻底避免setInterval的累积误差。 - 进度比例绑定:数字显示和SVG圆环都基于同一剩余时间比例更新,确保两者完全同步。
- 移除CSS动画:所有进度控制由JS统一处理,不再依赖浏览器独立调度的动画模块。
内容的提问来源于stack exchange,提问作者jottin
相关产品推荐
相关产品推荐

