canvas 动画实现圆环倒计时效果
最近要做一个圆环倒计时的效果,百度找了好几个圆环倒计时的效果,但跟效果图的设计出入太大了,无法直接拿来使用,只好自己来做一个了。
首先想到的是用css3的动画效果来实现,但研究了一段时间以后,发现扩展性太差了,而且圆圈线条的各种圆角无法实现,最终选择了用 canvas 动画来实现,canvas 动画主要是通过js来控制画画的内容,后期需要实现暂停、结束、超时等相关条件的反馈事件也比较容易。
首先看下圆环倒计时效果:

下面来看下制作这样一个动态的圆环倒计时动画制作过程
一、制作动画前先创建好所需的变量和 canvas 的默认参数
var time = 5, // 每次转圈所用时间,单位:秒
c_width = 500, // 画布的宽度
c_height = 500, // 画布的高度
radius = 90, // 圆圈的直径
lineWidth = 10, // 圆圈的宽度
lineColor = "#2C6E98", // 计时圆圈颜色
backround = "#eeeeee", // 圆圈的底色
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
canvas.width = c_width; // 设置canvas宽度
canvas.height = c_height; // 设置canvas高度
ctx.save();二、画出圆圈的背景色
画圆圈的背景色其实就是画个圆圈,圆圈的颜色定义为背景色
//画出圆环的底色 ctx.translate(c_width / 2, c_height / 2 ); ctx.beginPath(); ctx.strokeStyle = backround; ctx.lineWidth = lineWidth; ctx.arc(0, 0, radius, 0, Math.PI *2 ); ctx.stroke();
输出结果:

三、画出倒计时的圆环
倒计时的圆环同样也是画圆圈,跟背景圆圈不同的是,计时圆圈类似于进度条,根据时间的推移,圆圈的角度逐渐变大。例如倒计时完成一半,圆圈的角度刚好就180度,后面圆环的角度会通过js传值,实现动态的变化。下面先来画个90度的圆环
//画90度圆环 ctx.translate(c_width / 2, c_height / 2); ctx.rotate(-Math.PI / 2); ctx.beginPath(); ctx.strokeStyle = lineColor; ctx.lineWidth = lineWidth; ctx.arc(0, 0, radius, 0, Math.PI / 180 * 90); ctx.stroke();
输出结果:

四、画出线条首尾的圆角
虽然倒计时圆环的效果已经基本出来了,但是不是感觉有点生硬。线条的两端缺少圆角的效果,接下来需要把线两端的圆角给补上

4.1 画出线条开端的圆角
线条开端的圆角我们可以用个小圆形来实现,圆形的半径是线条宽度的一半,然后再通过定位,把小圆形的位置固定再圆圈的顶端。
//线条开始点的圆角 ctx.translate(c_width / 2 , c_height / 2 ); ctx.rotate(-Math.PI / 2); ctx.fillStyle = lineColor; ctx.beginPath(); ctx.arc(radius, 0, lineWidth / 2, 0, Math.PI*2); ctx.lineTo(0, 0); ctx.closePath(); ctx.fill();
输出效果:

4.2 画出线条末端的圆角
线条末端的这个圆角同样可以用个小圆形来实现,但这个圆形有点特别,中间有个白色的小点,同时还要跟圆环的计时来同步做圆周运动
//跟随旋转的小圆点 ctx.translate(c_width / 2, c_height / 2 ); // 画布平移到设定的中心坐标 ctx.beginPath(); ctx.fillStyle = "#ffffff"; ctx.strokeStyle = lineColor; ctx.rotate(Math.PI / 180 * 90);//根据时间改变角度 ctx.rotate(-Math.PI / 2); ctx.lineWidth = lineWidth/4; ctx.arc(radius, 0, lineWidth*3/8 , 0, Math.PI*2); ctx.closePath(); ctx.fill(); ctx.stroke();
输出效果:

五、让圆环动起来
最后,我们把上面这些代码组合起来,通过 settimeout 或 setinterval 来重复执行画圆的方法,没执行一次圆圈的角度增加一点,具体每次执行所需的角度可以通过这个公式来计算
当前角度 = 360 - 剩余时间 / (总时间 / 360)
完整代码:
var time = 5, // 每次转圈所用时间,单位:秒
c_width = 500, // 画布的宽度
c_height = 500, // 画布的高度
radius = 90, // 圆圈的直径
lineWidth = 10, // 圆圈的宽度
lineColor = "#2C6E98", // 计时圆圈颜色
backround = "#eeeeee", // 圆圈的底色
interTime = 50; //执行的阶段时间,数组越少越流畅,但越耗资源,单位:毫秒
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
canvas.width = c_width; // 设置canvas宽度
canvas.height = c_height; // 设置canvas高度
ctx.save();
function drawCircle(angle){
ctx.clearRect(0, 0, c_width, c_height);
ctx.save();
//画出圆环的底色
ctx.translate(c_width / 2, c_height / 2 );
ctx.beginPath();
ctx.strokeStyle = backround;
ctx.lineWidth = lineWidth;
ctx.arc(0, 0, radius, 0, Math.PI *2 );
ctx.stroke();
ctx.restore();
ctx.save();
//画圆环
ctx.translate(c_width / 2, c_height / 2);
ctx.rotate(-Math.PI / 2);
ctx.beginPath();
ctx.strokeStyle = lineColor;
ctx.lineWidth = lineWidth;
ctx.arc(0, 0, radius, 0, Math.PI / 180 * angle);
ctx.stroke();
ctx.restore();
ctx.save();
//线条开始点的圆角
ctx.translate(c_width / 2 , c_height / 2 );
ctx.rotate(-Math.PI / 2); // 画布旋转 -90度
ctx.fillStyle = lineColor;
ctx.beginPath();
ctx.arc(radius, 0, lineWidth / 2, 0, Math.PI*2);
ctx.lineTo(0, 0);
ctx.closePath();
ctx.fill();
ctx.restore();
ctx.save();
//跟随旋转的小圆点
ctx.translate(c_width / 2, c_height / 2 ); // 画布平移到设定的中心坐标
ctx.beginPath();
ctx.fillStyle = "#ffffff";
ctx.strokeStyle = lineColor;
ctx.rotate(Math.PI / 180 * angle);//根据时间改变角度
ctx.rotate(-Math.PI / 2);
ctx.lineWidth = lineWidth/4;
ctx.arc(radius, 0, lineWidth*3/8 , 0, Math.PI*2);
ctx.closePath();
ctx.fill();
ctx.stroke();
//重新加载、保存默认设置,不影响下次画板
ctx.restore();
ctx.save();
}
var msSecondsTime = time * 1000;
var timeangle = 0;
var remainTime = msSecondsTime;
setInterval(function() {
timeangle = 360 - remainTime / (msSecondsTime / 360);
drawCircle(timeangle);
if(remainTime > 0 ){
remainTime = remainTime - interTime;
}else{
remainTime = msSecondsTime;
}
}, interTime);