canvas是uni-app跨端绘制圆环进度条最稳定方案,需用id获取上下文、动态计算圆心、requestanimationframe驱动动画,并在onready后调用draw且小程序端加16ms延时确保重绘。

uni-app 里用 canvas 绘制圆环进度条最稳
纯 CSS 实现的圆环在 uni-app 里容易错位、缩放失真,尤其在 iOS 微信 WebView 和小程序端表现不一致;canvas 是唯一能跨端精确控制弧长、起始角、动画帧的方案。别碰 SVG 或 CSS clip-path,它们在 H5 和小程序渲染逻辑差异大,调试成本远高于直接写 canvas。
实操建议:
- 用
uni.createCanvasContext获取上下文,**必须传入 canvas 组件的id字符串**,不能传 ref 或 DOM 对象(小程序不支持) - 绘制前先调用
ctx.clearRect清空画布,否则多端复用时旧帧残留 - 圆心坐标按 canvas 宽高动态算:
const cx = width / 2; const cy = height / 2;,别写死像素值 - 动画用
requestAnimationFrame驱动,**不要用setInterval** —— 小程序里后者无法触发 canvas 重绘
progress 值变化时 canvas 不重绘?检查 draw 调用时机
常见错误是把 ctx.draw() 放在 setData 之后但没等下一次渲染周期,或在非主线程(如 onPullDownRefresh 回调里)直接调用,导致 canvas 内容卡在旧状态。
实操建议:
-
ctx.draw()必须在uni.createCanvasContext创建后、且 canvas 元素已挂载到页面上再执行(可用onReady生命周期确保) - 进度更新时,先改 data 中的
percent,再在$nextTick里调用绘制逻辑(H5 有效),**小程序端要加一层setTimeout(() => { ctx.draw() }, 16)模拟帧同步** - 避免在
watch里频繁调用ctx.draw(),每次调用都触发全量重绘,卡顿明显;建议加节流,比如if (Math.abs(newVal - oldVal)
小程序和 H5 圆环粗细不一致?重点看 lineWidth 和 devicePixelRatio
canvas 默认以物理像素绘制,但 uni-app 的 canvas 组件在不同平台对 width/height 属性解释不同:H5 是 CSS 像素,小程序是设备独立像素(rpx),导致同样 lineWidth: 8 在 iPhone 上看起来细,在安卓模拟器上看起来粗。
实操建议:
- 统一用
uni.getSystemInfoSync().pixelRatio动态计算线宽:ctx.lineWidth = 8 / pixelRatio - canvas 组件的
style="width: 200px; height: 200px;"必须显式设置,且宽高等比(否则圆变椭圆),**不能只靠 class 或 flex 布局撑开** - 抗锯齿关闭更稳妥:
ctx.setLineCap('round')+ctx.setLineJoin('round'),避免边缘毛刺
动画卡顿或跳变?别用百分比插值,改用角度缓动
直接对 percent 做线性插值(比如从 0→100 每帧加 2)在低帧率设备上会明显跳变,因为 canvas 弧长计算依赖 startAngle 和 endAngle,而角度本身是非线性的视觉感知。
实操建议:
- 用缓动函数控制角度增量,例如
const eased = 1 - Math.pow(1 - t, 3)(三次缓动),再映射到endAngle = startAngle + eased * 2 * Math.PI * 0.8 - 起始角统一设为
-Math.PI / 2(12 点钟方向),避免不同平台默认方向差异 - 动画结束时务必手动设回目标角度,别依赖最后一帧——
requestAnimationFrame可能被丢帧,导致进度条永远差 0.5%
最麻烦的其实是 canvas 在某些低端安卓机上首次绘制延迟明显,得在页面 onLoad 里提前创建 context 并 draw 一次空内容,不然用户滑动时才第一次画,会卡住。这事没人提,但真会发生。










