Canvas 倒计时
背景
由于最近要做一个小程序倒计时功能,搜集了许多资料发现都是顺时针,要么就是差那么一点意思; 于是自己写了一个web版的,后续翻译成小程序版。
思路
1、三个canvas层,分为: bottom(底色圆圈)、center、top(由于canvas中圆形,没有类似clearReact的方法;重新画时界面会闪,这里用两个canvas来规避这个问题)
2、center、top为每次倒计时的canvas,top比center小一帧
3、每次重绘时,先把center 重置一次,再把top重置
代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .countdown-container { display: flex; justify-content: center; align-items: center; position: relative; width: 165px; height: 165px; margin: 0 auto; } .countdonw-time-container { display: flex; flex-direction: column; justify-content: center; align-items: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .countdown-time { font-size: 56px; } .countdown-tips { font-size: 12px; } .sign-btn { width: 120px; height: 32px; background: #4F8BFF; border-radius: 4px; color: white; display: flex; flex-direction: column; justify-content: center; margin: 20px auto; text-align: center; } #myCanvas1, #myCanvas2, #myCanvas3 { position: absolute; top: 0; left: 0; } #myCanvas1 { z-index: 0; } #myCanvas2 { z-index: 1; } #myCanvas3 { z-index: 2; } .hide { display: none; width: 0; } </style> </head> <body> <div class="countdown-container"> <canvas id="myCanvas1" width="168" height="168"></canvas> <canvas id="myCanvas2" width="168" height="168"></canvas> <canvas id="myCanvas3" width="168" height="168"></canvas> <div class="countdonw-time-container"> <div class="countdown-time">1</div> <div class="countdown-tips">倒计时( S )</div> </div> </div> <div class="sign-btn">到</div> </body> </html> <script> const basePx = 168 const toZoomPx = (pxValue) => { const width = document.documentElement.clientWidth; return pxValue * (width / 750) } const pxWidth = toZoomPx(basePx) const time = 30 // 可以随意设置 let count = time // 倒计时剩余时间 const percent = 2 / time // 每一秒需要的度数 let timer const countTimeNode = document.querySelector('.countdown-time') const cvsNode1 = document.querySelector('#myCanvas1') const ctx1 = cvsNode1.getContext('2d') const cvsNode2 = document.querySelector('#myCanvas2') const ctx2 = cvsNode2.getContext('2d') const cvsNode3 = document.querySelector('#myCanvas3') const ctx3 = cvsNode3.getContext('2d') const btn = document.querySelector('.sign-btn') countTimeNode.innerHTML = time const drawBottom = () => { ctx1.beginPath() ctx1.lineWidth = 3 ctx1.strokeStyle = '#d9d9d9' ctx1.arc((basePx - 2) / 2, (basePx - 6) / 2, (basePx - 10) / 2, 0, 2 * Math.PI, false) ctx1.stroke() } const getPercent = (count) => { const deg = (time - count) * percent console.log('count , deg:', count, deg) return deg <= 0 ? -0.5 * Math.PI : (1.5 - deg) * Math.PI } const drawCenter = () => { ctx2.beginPath() ctx2.lineWidth = 5 ctx2.strokeStyle = '#4F8BFF' ctx2.lineCap = 'round' ctx2.arc((basePx - 2) / 2, (basePx - 6) / 2, (basePx - 10) / 2, 1.5 * Math.PI, getPercent(count), false) ctx2.stroke() } const drawTop = () => { ctx3.beginPath() ctx3.lineWidth = 5 ctx3.strokeStyle = '#4F8BFF' ctx3.lineCap = 'round' ctx3.arc((basePx - 2) / 2, (basePx - 6) / 2, (basePx - 10) / 2, 1.5 * Math.PI, getPercent(count), false) ctx3.stroke() } const initCanvasPx = () => { cvsNode1.setAttribute('width', pxWidth) cvsNode1.setAttribute('height', pxWidth) cvsNode2.setAttribute('width', pxWidth) cvsNode2.setAttribute('height', pxWidth) cvsNode3.setAttribute('width', pxWidth) cvsNode3.setAttribute('height', pxWidth) } const startCountDown = () => { if (count <= 0) { clearInterval(timer) return } count-- countTimeNode.innerHTML = count // 隐藏中间层,重置中间层 cvsNode2.setAttribute('width', 0) cvsNode2.setAttribute('width', basePx) setTimeout(() => { drawCenter() cvsNode3.setAttribute('width', 0) cvsNode3.setAttribute('width', basePx) setTimeout(() => { cvsNode3.className = ' ' drawTop() }, 100) }, 200) } const initCanvas = () => { // 画底圆 drawBottom() // 画倒计时圆 drawCenter() // 画倒计时圆 drawTop() } const initCircle = () => { // initCanvasPx() initCanvas() timer = setInterval(startCountDown, 1000) } const init = () => { initCircle() console.log('on init...', countTimeNode.innerHTML) } btn.addEventListener('click', () => { if (timer) { clearInterval(timer) timer = null } else { timer = setInterval(startCountDown, 1000) } }) init() </script>
遗留问题:
1、暂未考虑移动端分辨率
2、canvas 绘制的圆形会有锯齿
效果: