在前端开发中,进度条并不只是水平的那一条窄带。很多后台系统、文件上传界面以及数据看板都倾向于用圆环来呈现完成比例,同时在用户把鼠标移上去时给出具体数值或状态说明。实现这类需求主要有两条路线:一条是基于SVG矢量描边,另一条是使用Canvas逐帧绘制。鼠标提示信息则可以从原生title过渡到自定义浮层,从而获得更灵活的样式与触发时机。

一、使用SVG实现圆环进度效果
SVG天生适合画圆和弧,核心是利用circle元素的stroke-dasharray与stroke-dashoffset来控制可见弧长。我们准备两个圆,底层作为轨道,上层作为进度,通过动态修改偏移量即可呈现填充动画。
下面是一段可直接运行的示例,百分比由JavaScript计算后写入样式。注意SVG内部的标签名要使用命名空间,但在HTML文档里浏览器会自动识别,我们只需按常规写法即可。
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="50" fill="none" stroke="#eee" stroke-width="10"></circle>
<circle id="prog" cx="60" cy="60" r="50" fill="none" stroke="#3b82f6" stroke-width="10"
stroke-linecap="round" stroke-dasharray="314.159" stroke-dashoffset="314.159"
transform="rotate(-90 60 60)"></circle>
<text id="txt" x="60" y="66" text-anchor="middle" font-size="18">0%</text>
</svg>
上面的stroke-dasharray值是圆周长(2×π×50≈314.159)。当dashoffset等于周长时完全隐藏,等于0时全显。我们用脚本按比例设置它:
function setProgress(percent) {
var circle = document.getElementById('prog');
var text = document.getElementById('txt');
var r = 50;
var len = 2 * Math.PI * r;
var offset = len * (1 - percent / 100);
circle.style.strokeDashoffset = offset;
text.textContent = percent + '%';
}
setProgress(65);
这种方式的优势是矢量清晰、动画可用CSS transition平滑处理,例如给circle加transition: stroke-dashoffset 0.4s;就能在数值变化时缓动。缺点是复杂交互动画需要手动计算,且若要在圆环上做渐变色,需要定义SVG线性或径向渐变。
二、使用Canvas绘制圆环进度
Canvas适合需要高频重绘或在圆环上做粒子、光影特效的场景。我们通过arc方法分段画线,配合lineWidth与strokeStyle完成轨道与进度。
下列代码封装了一个简单的绘制函数,传入画布上下文与百分比即可重绘。由于Canvas是位图,在视网膜屏上需按devicePixelRatio放大画布以避免模糊。
function drawRing(ctx, percent) {
var w = ctx.canvas.width;
var h = ctx.canvas.height;
var cx = w / 2;
var cy = h / 2;
var r = Math.min(w, h) / 2 - 10;
ctx.clearRect(0, 0, w, h);
// 轨道
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.lineWidth = 10;
ctx.strokeStyle = '#eee';
ctx.stroke();
// 进度
ctx.beginPath();
ctx.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * percent / 100);
ctx.lineWidth = 10;
ctx.strokeStyle = '#3b82f6';
ctx.stroke();
}
var canvas = document.getElementById('cv');
var ctx = canvas.getContext('2d');
drawRing(ctx, 40);
Canvas方案的优点是绘制逻辑统一、易于做逐帧动画与合成效果;不足之处在于文本与提示需自己排版,且不像SVG那样方便用CSS控制样式。如果项目里已经引入了图形库,也可以考虑用库封装,但原生API足以应付多数圆环需求。
三、实现鼠标提示信息浮层
原生title属性虽然简单,但存在约一秒延迟、样式不可定制、在移动端无效等问题。更可控的做法是监听mousemove,用一个绝对定位的div展示内容,并跟随指针移动。
我们先在页面放一个隐藏的提示框,然后在进度条容器上绑定事件,根据当前鼠标位置与进度数据写入提示文本。这样用户悬停时能立刻看到例如“已完成65%,预计剩余12秒”的信息。
<div id="wrap" style="position:relative;display:inline-block;">
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="50" fill="none" stroke="#eee" stroke-width="10"></circle>
<circle id="prog2" cx="60" cy="60" r="50" fill="none" stroke="#10b981" stroke-width="10"
stroke-linecap="round" stroke-dasharray="314.159" stroke-dashoffset="110"
transform="rotate(-90 60 60)"></circle>
</svg>
</div>
<div id="tip" style="position:fixed;display:none;background:#333;color:#fff;
padding:4px 8px;border-radius:4px;font-size:12px;pointer-events:none;"></div>
var wrap = document.getElementById('wrap');
var tip = document.getElementById('tip');
wrap.addEventListener('mousemove', function(e) {
var percent = 65; // 实际可从进度状态获取
tip.style.display = 'block';
tip.style.left = (e.clientX + 12) + 'px';
tip.style.top = (e.clientY + 12) + 'px';
tip.textContent = '当前进度:' + percent + '%,请稍候';
});
wrap.addEventListener('mouseleave', function() {
tip.style.display = 'none';
});
这种浮层不拦截鼠标事件(pointer-events:none),因此不会影响底层进度条的交互。若需要提示框不超出视口,可在赋值left、top前判断e.clientX + 偏移是否大于window.innerWidth,并做反向修正。
四、综合建议与注意事项
对于大多数管理后台与展示型页面,SVG配合CSS过渡已经足够,代码量少且易于维护;只有当你要在圆环上叠加复杂动画或大量实例时,Canvas才更合适。鼠标提示统一用自定义浮层,既保证体验一致,也方便后期接入国际化或异步数据。
另外,在可访问性方面,记得给进度区域加上role="progressbar"以及aria-valuenow等属性,让读屏软件也能读出进度。圆环颜色应满足对比度规范,避免仅用颜色区分状态。把这些细节处理好,前端进度条就能既好看又好用。
SVGCanvasCSS_animation修改时间:2026-08-08 05:24:32