JS/HTML圆形进度条平滑动画实现求助
问题描述
我正在尝试用HTML Canvas的arc方法和JavaScript制作圆形进度条,需要实现平滑动画效果。进度条的数据来自PLC动态更新的XML文件(semic.cgx)。
我的思路是每100ms检查一次XML文件更新,期间每10ms将控制进度的变量增加1/10的增量,把单次增量拆成10次来实现平滑过渡。
目前没达到预期效果,代码在进度条达到上限时出现问题。我知道有其他实现方式,但网上多数示例不讲解原理,我技术基础薄弱,恳请帮助。
现有代码
var req function reloadData() { url = 'semic.cgx' try { req = new XMLHttpRequest(); } catch (e) { alert('No AJAX Support'); return; } req.onreadystatechange = myFunction; req.open('GET', url, true); req.send(null); } function myFunction() { if (req.readyState == 4) { if (req.status == 200) { var x = req.responseXML; var v1 = x.getElementsByTagName('text')[0].childNodes[1].innerHTML; var angle = (0.75 +((v1 / 100)* 1.5)); var c = document.getElementById("myCanvas"); var ctx = c.getContext("2d"); setInterval(function () { if (angle >= 2.25) { ctx.clearRect(0,0,500,500); } if (angle < 2.25) { angle = angle + 0.0015; ctx.globalCompositeOperation = "source-over"; ctx.rotate(0.5*2*Math.PI); ctx.lineWidth = 15; ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = "high"; ctx.beginPath(); ctx.arc(250, 250, 200, (0.75 * Math.PI), (angle * Math.PI)); ctx.strokeStyle = "#DE2700"; ctx.stroke(); } console.log(angle); }, 10); timeoutID = setTimeout('reloadData()', 100); } } }
解决方案
核心问题分析
- 重复创建定时器:每100ms拉取一次数据就新建一个
setInterval,多个定时器同时修改angle变量,导致进度条逻辑混乱,上限判断失效。 - 上下文状态未重置:每次绘制都调用
ctx.rotate但未恢复状态,后续绘制的圆心会持续偏移。 - 硬编码增量:固定加0.0015的逻辑无法适配不同的目标值变化,平滑过渡效果不可控。
修复后的完整代码
var req; var currentAngle = 0.75; // 初始角度对应0%进度 var targetAngle = 0.75; var animationTimer = null; var canvas, ctx; // 页面加载时初始化Canvas和启动数据拉取 window.onload = function() { canvas = document.getElementById("myCanvas"); ctx = canvas.getContext("2d"); drawProgress(currentAngle); reloadData(); }; function reloadData() { var url = 'semic.cgx'; try { req = new XMLHttpRequest(); } catch (e) { alert('No AJAX Support'); return; } req.onreadystatechange = handleDataResponse; req.open('GET', url, true); req.send(null); } function handleDataResponse() { if (req.readyState == 4 && req.status == 200) { var x = req.responseXML; var v1 = x.getElementsByTagName('text')[0].childNodes[1].innerHTML; // 计算目标角度:0%对应0.75π,100%对应2.25π targetAngle = 0.75 + ((v1 / 100) * 1.5); // 仅在无动画时启动定时器 if (!animationTimer) { animationTimer = setInterval(updateProgress, 10); } // 100ms后再次拉取数据 setTimeout(reloadData, 100); } } function updateProgress() { // 计算单次步进:取当前与目标差值的1/10,保证平滑过渡 var step = (targetAngle - currentAngle) / 10; // 避免微小差值导致的无限循环 if (Math.abs(step) < 0.0001) { currentAngle = targetAngle; clearInterval(animationTimer); animationTimer = null; } else { currentAngle += step; } drawProgress(currentAngle); } function drawProgress(angle) { // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 保存当前上下文状态,避免旋转影响后续绘制 ctx.save(); // 将原点移至画布中心后旋转,保证圆心位置正确 ctx.translate(250, 250); ctx.rotate(Math.PI * 0.5); ctx.translate(-250, -250); // 设置绘制样式 ctx.lineWidth = 15; ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = "high"; ctx.strokeStyle = "#DE2700"; // 绘制圆弧 ctx.beginPath(); ctx.arc(250, 250, 200, 0.75 * Math.PI, angle * Math.PI); ctx.stroke(); // 恢复上下文状态 ctx.restore(); }
关键原理说明
- 单一定时器控制:确保始终只有一个动画定时器运行,避免多线程修改进度变量的冲突。
- 动态步进计算:通过每次走当前与目标差值的1/10,无论目标值是跳变还是渐变,都能实现自然的平滑过渡,替代硬编码增量的僵硬逻辑。
- 上下文状态管理:使用
save()和restore()保存、恢复画布状态,避免旋转、平移操作影响后续绘制的坐标系,保证每次绘制的圆心位置准确。
内容的提问来源于stack exchange,提问作者Tooley
相关产品推荐
相关产品推荐

