<p>真正可靠的做法是用服务端时间戳锚定,本地只做差值计算:每次更新UI前用Math.max(0, Math.floor((endTime - Date.now()) / 1000))重算剩余秒数,并用setTimeout递归代替setInterval。</p>

倒计时进度条必须用服务端时间戳校准,不能靠累减
直接写 this.countdown-- 或用 setInterval 每秒减 1 是错的。安卓后台节流、iOS 切页休眠、H5 页面失焦都会让这个“减法”脱离真实时间,导致跳秒、卡死甚至倒计时提前结束。
真正可靠的做法是:服务端返回可信的结束时间戳(如 endTime: 1743165680000),前端每次更新 UI 前都重算:Math.max(0, Math.floor((endTime - Date.now()) / 1000))。哪怕只显示秒数,也建议每 200–500ms 触发一次重算,而不是等“一秒到了再算”。
- 切后台再回来时,在
onShow中立刻重算一次,UI 瞬间对齐真实剩余时间 - 不要在
mounted里启动倒计时——还没发请求,时间点没拿到 - 避免用
performance.now():H5 可用,但小程序和 App 端基本不支持,会报undefined is not a function
用 SVG 实现环形进度条比 CSS transform 更稳
uni-app 官方 <uni-progress> 只支持线性进度条,没有环形。强行用 CSS 画圆 + transform: rotate() 做动画,在 iOS WebView 和部分安卓小程序里容易失真:旋转角度错位、进度跳变、低配机卡顿。
推荐用 SVG 方案:<svg> + <circle> + stroke-dasharray + stroke-dashoffset。核心是把百分比进度转成路径偏移量,比 CSS 更可控、跨端一致性更好。
- 计算公式固定:
circumference = 2 * Math.PI * radius,dashoffset = circumference * (1 - progress) - 注意设置
transform="rotate(-90)"让进度从顶部开始,否则起点默认在右侧 - 别用
canvas动态绘制:小程序 Canvas 在v-for列表中复用易内存泄漏,且canvasToTempFilePath在 H5 不触发回调
进度条动画必须用 CSS transition,不能靠 JS 频繁 setData
如果用两个 <view> 模拟横向进度条(外层背景 + 内层进度块),宽度变化必须走 CSS transition,而不是靠 JS 每次设 width: xx% 强刷。后者在小程序端 setData 频率高会卡顿,尤其列表中多个进度条同时跑时。
正确做法是只更新一个响应式字段(如 progress: 65),样式里用 width: v-bind(progress) + '%',再配合 transition: width 0.3s ease。这样动画由浏览器原生处理,更顺滑。
- H5 支持完整 CSS,但小程序对
overflow: hidden和border-radius渲染有差异,圆角进度条需额外加overflow: hidden包裹容器 - App 端要注意性能:避免在
scroll-view里放大量带 transition 的进度条,可能触发频繁重绘 - 渐变色用
background: linear-gradient(...),别用图片,否则多端缩放失真
按钮状态与倒计时必须逻辑层+UI层双重控制
只写 disabled="isCounting" 不够。用户可绕过界面直接调用发送函数;DOM 层禁用也不能阻止 JS 层重复执行。
必须定义唯一状态源:isCounting: false 存在 data 中。点击事件开头加守卫:if (this.isCounting) return;请求发出后立即设 this.isCounting = true;倒计时归零或失败时,必须显式调用 resetCountdown()(清定时器 + 设 countdown = 0 + isCounting = false)。
- 定时器 ID 必须挂到
this上(如countdownTimer: null),清除前严格校验:if (this.countdownTimer) { clearTimeout(this.countdownTimer); this.countdownTimer = null; } -
onHide中必须清理定时器,否则切后台还在跑;onShow中不能自动续,得重算剩余时间再决定是否重启 - 如果封装成公共组件,父组件要监听
isCounting变化并同步更新子组件 props,否则样式不同步
实际复杂点在于:服务端时间戳和本地时钟存在毫秒级偏差,而倒计时 UI 更新又受限于 JS 执行时机和渲染帧率。最稳妥的方式不是追求“绝对精准”,而是确保每次视图刷新都基于当前真实差值,并接受 100–300ms 的合理误差窗口。


















