最近在项目中遇到一个需求:绘制圆形进度条倒计时功能
UI设计图如下:
对于图片中数字的显示实现起来是比较容易的,这里就直接忽略掉,主要是想把绘制动态圆的整个思路写下来
动画就是由一幅幅静态图片以极快的速度连续播放而产生的效果
canvas实现动画的原理亦是如此
每隔一段时间重新绘制图形随后清除图形,模拟动画的过程
分析
有了对动画的理解,我们先分析一下应该如何实现
- 最底下先画一个颜色为蓝色的静态圆(我们先称其为基础圆)
- 最上面是一个动态的圆,效果是每隔一定的时间圆走过一定的角度
- 循环采用定时器(setTimeout 或者 setInterval)
实现
根据上面的分析就可以着手实现了,有关canvas的api不了解的请参考w3school
绘制基础圆
- 定义canvas画布
<canvas id="canvas" width="200px" height="200px"></canvas>
- 画基础圆
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d'); //返回对象,该对象提供画图用到的所有方法和属性三绘制路径
funcation drawBase(){
ctx.beginPath(); //定义一天起始路径
ctx.lineWidth = 10; //设置线条的宽度
ctx.strokeStyle = '#f6f6f6'; //设置笔触颜色
ctx.arc(x, y, r, start. end, false); //x,y为圆的圆心坐标,start为开始角度,end为结束角度,false是设置绘制圆以顺时针方向
ctx.arc(0, 0, 50, 0, Math.PI * 2, false);
ctx.stroke(); //绘制定义好的路径 (该函数是绘制图形的关键哦)
}
有了上面的代码,设计图中灰色的圆就画好了
绘制动态圆
// 动态圆
function drawValue(srcDegree, targetDegree) {
ctx.beginPath();
ctx.lineWidth = 12;
ctx.lineCap = 'butt'; //设置线条结束时端点的样式
ctx.strokeStyle = this.config.lineColor;
ctx.arc( 0, 0, 50, degreeToRadian(srcDegree - 90), degreeToRadian(targetDegree - 90), false); //这里需要把度数处理为弧度,由于圆是默认从0度开始画,所以需要把角度减掉90度,从正上方的位置开始
ctx.stroke();
}
drawValue这个函数其实与drawBase没有区别,都是实现画圆的功能,差别在于一个的目标角度是变的,目前有利于理解先这么写李,之后可以重构代码合为一个函数会更加简洁。
// 处理角度
function degreeToRadian(degree){
return degree * Math.PI / 180;
}
现在我们要计算执行一次画圆函数角度应该增加多少?
如果让浏览器1s中渲染50次,那么一次需要20ms,需求是120s画完360度,120s等于120000ms, easing = 360/(120000/20)
// 清除画布
function clearAll(){
ctx.clearRect(0, 0, 200, 200);
}
//圆形进度条
function draw(srcDegree, targetDegree){
clearAll();
drawBase();
drawValue(srcDegree, targetDegree);
}
现在定义好的两个画圆函数被draw调用,已经实现了我们分析过程的一大部分,还需要对每次的目标角度进行计算。
let targetDegree = 0; //全局变量
function drawFrame(){
let easing = 360 / 6000;
//当目标角度达到满圆时就可以清空计时器对象和画布
if(Math.round(targetDegree) >= Math.PI * 2){
clearInterval(timer);
clearAll();
}else{
targetDegree += easing; //实现匀速
draw(srcDegree, targetDegree);
}
}
现在所有的功能已经实现,只差调用,哈哈哈!
render(){
let timer = setInterval(drawFrame, 20);
}
render(); //哇欧!
是不是感觉还挺容易的,其实只要理解了动画的原理还是很容易上手的,But问题还有完。。。
问题:如果说用户一直停留在倒计时这个页面,那么整个过程是没有问题出现的,但是当用户在倒计时的过程中想隐藏页面或者tab切换页签,这个时候你就会发现在离开的过程中画圆的速度跟你定义好的是不一样的,那么问题出在哪里呢?
这个现象的出现是定时器导致的,由于
HTML5标准规定,为了节电,对不处于当前窗口的页面,浏览器会将时间间隔扩大到1000ms
,因此单位时间内的画圆角度就变了,以至于不能同步。
出现这个好像
解决不了的bug,真的是很恼人唉!多亏,多亏我有小眼睛,哈哈,我们两个一起讨论出了,是否能判断用户离开当前页面,然后在离开期间扩大角度的变化速率,这样单位时间内的角度是不变的,当用户再回到页面时也已经渲染到正确的位置。
查找资料后发现,浏览器支持visibilitychange
事件,窗口隐藏或tab页签切换都属于该事件,根据document.isHideen属性可以知道页面是否被隐藏。
document.addEventListener('visibilitychange', this.eventHandler.bind(this));
eventHandler() {
let isHidden = document.hidden;
if (isHidden) {
this.easing = 360 / 5950 * 50;
} else {
this.easing = 360 / 5950;
}
}
我想对于这个需求或许还有其他更好的实现方法,后面会继续查找资料, 若这篇文章哪里写的不够好的,请大家不吝赐教!