You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);    
        }
    }
}

解决方案

核心问题分析

  1. 重复创建定时器:每100ms拉取一次数据就新建一个setInterval,多个定时器同时修改angle变量,导致进度条逻辑混乱,上限判断失效。
  2. 上下文状态未重置:每次绘制都调用ctx.rotate但未恢复状态,后续绘制的圆心会持续偏移。
  3. 硬编码增量:固定加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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 22:40:44