携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情
vue项目中有个需求,需要实现进度条功能,目前所用的组件是element-ui,因此直接用组件自带的<el-progress>实现,那前端部分就是间隔0.1s更新一次进度条,当进度条达到90%时,开始停止进度条,timer是允许的时间展示,percentage是进度条展示。
实现效果如下图:
首先我们设置布局如下:
<el-tag type="warning" effect="dark" size="small" color="#67C23A" disable-transitions>{{timer}}s</el-tag>
<el-progress :text-inside="true" :stroke-width="14" :percentage="percentage" status="warning"></el-progress>
然后再在执行接口时,进行定时更新进度条。
定时器方式实现
定时顾名思义,首先想到的就是定时器,代码就如下所示:
clearInterval(this.timer)
this.timer = null
this.timer = setInterval(() => {
result.timer = Math.round((result.timer + 0.1) * 100) / 100
let per = result.percentage + Math.ceil(Math.random() * 10)
if (per >= 90 || (result.timer % 1 !== 0)) {
return
}
result.percentage = per
}, 100)
每100ms进一次定时器,改变计时器展示,result.timer % 1 !== 0实现每1s更新一次进度条,而为了让这个假进度条更加可信,可采取随机数的方式每次增加进度条量。当超过90%时停止进度条更新,直至接口返回数据,直接将进度条更新为100%。
这样功能是实现了,但是有几个问题:
-
setInterval属于宏任务,在任务队列中,会受到队列中其他任务的影响,如果存在很大的其他任务,setInterval的计时将会非常不准
-
setInterval的里的每次更新
percentage,会重新出发重排和重绘,性能较差,下图为进度条运行时的重绘数量,由此可见,只是小小进度条,重绘却有非常多次
requestAnimationFrame方式
由于上文提到的,定时器方式实现进度条的几个问题,我尝试了更好的解决方案,即为requestAnimationFrame方式,代码如下:
let preTime = 0
function timerFun (timestamp) {
console.log('timestamp', timestamp, timestamp - preTime)
if (result.percentage === 100) {
cancelAnimationFrame(timerFun)
return
}
if (timestamp - preTime < 100) {
requestAnimationFrame(timerFun)
return
}
preTime = timestamp
console.log(2, result.percentage, Date.now())
result.timer = Math.round((result.timer + 0.1) * 100) / 100
let per = result.percentage + Math.ceil(Math.random() * 10)
requestAnimationFrame(timerFun)
// 每间隔1s,进度条更新一次:result.timer % 1 !== 0
if (per >= 90 || (result.timer % 1 !== 0)) {
return
}
result.percentage = per
}
requestAnimationFrame(timerFun)
此方案的优点为:
-
并不在任务队列中,因此不会受其他任务的影响,计时更准确
-
requestAnimationFrame的原理为页面在下一次重绘之前调用传入的方法,因此不会单独触发新的重绘,同样记录了进度条运行时的重绘数量,如下图,可以看出比定时器方式好了很多,现在有的重绘不再包含进度条运行
-
还有一点,setInterval方式,就算页面隐藏,处于后台状态,比如切换网站tab标签,定时器依然还会继续执行,而requestAnimationFrame在后台状态不会继续执行
-
根据这一点,对动画来说,性能会很好,但是对于项目中会有个timer变量的计时,就会出现问题
-
考虑这一点对代码做优化,每次timer的更新将不再是固定的0.1,而是根据回调函数的入参,时间戳来确定,更新后代码如下
let preTime = 0 function timerFun (timestamp) { if (result.percentage === 100) { cancelAnimationFrame(timerFun) return } if (timestamp - preTime < 100) { requestAnimationFrame(timerFun) return } // 由于requestAnimationFrame在后台不会运行,因此需要计算interval let interVal = !preTime ? 0.1 : +((timestamp - preTime) / 1000).toFixed(1) preTime = timestamp result.timer = Math.round((result.timer + interVal) * 100) / 100 let per = result.percentage + Math.ceil(Math.random() * 10) requestAnimationFrame(timerFun) // 每间隔1s,进度条更新一次:result.timer % 1 !== 0 if (per >= 90 || (result.timer % 1 !== 0)) { return } result.percentage = per }
-