导语:距离上一次写canvas,已经过去两年半,如今业务需要,再次拾起,随手记录。
【思考】 时钟的绘制主要在于圆的绘制:1. 使用context.arc()方法直接绘制圆或圆弧; 2. 使用圆的方程(x = r * cosA + X, y = r * sinA + Y)进行描点绘制。指针运行速率较慢,故使用setInterval进行刷新重绘。
【优化】可以使用两个canvas,一个用来绘制表盘,另一个绘制指针,如此,只需刷新重绘指针canvas,表盘canvas保持不变。
<!DOCTYPE html> <html> <head> <title>Canvas Clock</title> </head> <body> <canvas id="clock">Your borswer does not support canvas element.</canvas> <script type="text/javascript"> /** * 圆的方程:x = r * cosA + X, y = r * sinA + Y * 浏览器为了达到抗锯齿的效果会做额外的运算,故为提高渲染效率,均使用整数进行绘制。 */ (function() { let clockCvs = document.getElementById('clock') if (!clockCvs || !clockCvs.getContext) return clockCvs.width = 310 clockCvs.height = 310 let clockCtx = clockCvs.getContext('2d') // X坐标偏移 const X = 155 // Y坐标偏移 const Y = 155 // 钟的半径 const R = 150 start() setInterval(start, 1000) function start () { clockCtx.clearRect(0, 0, clockCvs.width, clockCvs.height) renderClockPlate() renderClockTime() renderClockHand() } // 渲染表盘 function renderClockPlate () { drawCircle(X, Y, '#070702', R, 1) drawCircle(X, Y, '#4f4f52', R - 3, 5) drawCircle(X, Y, '#070702', R - 6, 3) drawCircle(X, Y, '#dddddd', R - 8) drawCircle(X, Y, '#121213', R - 10, 3) drawCircle(X, Y, '#19191a', R - 12, 0, 'fill', true) drawCircle(X, Y, '#4e4738', 15, 0, 'fill') drawCircle(X, Y, '#eac55a', 10, 0, 'fill') drawCircle(X, Y, '#3e3e41', 8, 0, 'fill') drawCircle(X, Y, '#000000', 3, 0, 'fill') } // 渲染时间 function renderClockTime () { for (let angle = -90; angle < 270; angle = angle + 6) { let x = Math.round((R - 18) * Math.cos(angle / 180 * Math.PI) + X) let y = Math.round((R - 18) * Math.sin(angle / 180 * Math.PI) + Y) let r = angle % 90 === 0 ? 4 : 2 drawCircle(x, y, '#eac55a', r, 0, 'fill') if (angle % 30 === 0) { x = Math.round((R - 35) * Math.cos(angle / 180 * Math.PI) + X - 4) y = Math.round((R - 35) * Math.sin(angle / 180 * Math.PI) + Y + 6) clockCtx.font = angle % 90 === 0 ? 'bold 15px yahei' : '12px yahei' clockCtx.fillText((angle + 90) / 30 || 12, x , y) } } } // 渲染表针 function renderClockHand () { let date = new Date() let hour = date.getHours() let minute = date.getMinutes() let second = date.getSeconds() // 秒针 let angle1 = (second * 6 - 90) let x = Math.round((R - 45) * Math.cos(angle1 / 180 * Math.PI) + X) let y = Math.round((R - 45) * Math.sin(angle1 / 180 * Math.PI) + Y) drawLine([[X, Y], [x, y]], 1) // 分针 let angle2 = (minute * 6 - 90) x = Math.round((R - 65) * Math.cos(angle2 / 180 * Math.PI) + X) y = Math.round((R - 65) * Math.sin(angle2 / 180 * Math.PI) + Y) drawLine([[X, Y], [x, y]], 2) // 时针, 时针角度 = 小时角度 + 分钟角度 let angle3 = ((hour % 12) * 30 - 90) + (angle2 / 12) x = Math.round((R - 90) * Math.cos(angle3 / 180 * Math.PI) + X) y = Math.round((R - 90) * Math.sin(angle3 / 180 * Math.PI) + Y) drawLine([[X, Y], [x, y]], 4) } /** * @param {String} color 颜色 * @param {Number} r 圆半径 * @param {Number} lineWidth 线条粗细 * @param {String} type 类型,stroke/fill * @param {Boolean} isLinear 是否渐变 */ function drawCircle (x, y,color = '#000000', r = 10, lineWidth = 2, type = 'stroke', isLinear = false) { let grd = clockCtx.createLinearGradient(0, 0, clockCvs.width, clockCvs.height) grd.addColorStop(0, color) grd.addColorStop(0.5, '#555555') grd.addColorStop(1, color) clockCtx[type + 'Style'] = isLinear ? grd : color clockCtx.lineWidth = lineWidth clockCtx.beginPath() clockCtx.arc(x, y, r, 0, Math.PI * 2, true) clockCtx.closePath() clockCtx[type]() } /** * @param {Array} pos 坐标点集合,如 [[0, 0], [120, 120]] * @param {String} color 颜色 * @param {Number} lineWidth 线条粗细 */ function drawLine (pos, lineWidth = 2, color = '#eac55a') { clockCtx.strokeStyle = color clockCtx.lineWidth = lineWidth clockCtx.beginPath() clockCtx.moveTo(pos[0][0], pos[0][1]) for (let i = 0, len = pos.length; i < len; i++) { clockCtx.lineTo(pos[i][0], pos[i][1]) } clockCtx.stroke() clockCtx.closePath() } })() </script> </body> </html>
<!DOCTYPE html> <html> <head> <title>Canvas Clock</title> </head> <body> <canvas id="clock" style="position: absolute;">Your borswer does not support canvas element.</canvas> <canvas id="clockHand" style="position: absolute;">Your borswer does not support canvas element.</canvas> <script type="text/javascript"> /** * 圆的方程:x = r * cosA + X, y = r * sinA + Y * 浏览器为了达到抗锯齿的效果会做额外的运算,故为提高渲染效率,均使用整数进行绘制。 */ (function() { let clockCvs = document.getElementById('clock') let clockHandCvs = document.getElementById('clockHand') if (!clockCvs || !clockCvs.getContext) return clockCvs.width = clockHandCvs.width = 310 clockCvs.height = clockHandCvs.height = 310 let clockCtx = clockCvs.getContext('2d') let clockHandCtx = clockHandCvs.getContext('2d') // X坐标偏移 const X = 155 // Y坐标偏移 const Y = 155 // 钟的半径 const R = 150 renderClockPlate() renderClockTime() renderClockHand() setInterval(function () { clockHandCtx.clearRect(0, 0, clockHandCvs.width, clockHandCvs.height) renderClockHand() }, 1000) // 渲染表盘 function renderClockPlate () { drawCircle(clockCtx, clockCvs, X, Y, '#070702', R, 1) drawCircle(clockCtx, clockCvs, X, Y, '#4f4f52', R - 3, 5) drawCircle(clockCtx, clockCvs, X, Y, '#070702', R - 6, 3) drawCircle(clockCtx, clockCvs, X, Y, '#dddddd', R - 8) drawCircle(clockCtx, clockCvs, X, Y, '#121213', R - 10, 3) drawCircle(clockCtx, clockCvs, X, Y, '#19191a', R - 12, 0, 'fill', true) drawCircle(clockCtx, clockCvs, X, Y, '#4e4738', 15, 0, 'fill') drawCircle(clockCtx, clockCvs, X, Y, '#eac55a', 10, 0, 'fill') drawCircle(clockCtx, clockCvs, X, Y, '#3e3e41', 8, 0, 'fill') drawCircle(clockCtx, clockCvs, X, Y, '#000000', 3, 0, 'fill') } // 渲染时间 function renderClockTime () { for (let angle = -90; angle < 270; angle = angle + 6) { let x = Math.round((R - 18) * Math.cos(angle / 180 * Math.PI) + X) let y = Math.round((R - 18) * Math.sin(angle / 180 * Math.PI) + Y) let r = angle % 90 === 0 ? 4 : 2 drawCircle(clockCtx, clockCvs, x, y, '#eac55a', r, 0, 'fill') if (angle % 30 === 0) { x = Math.round((R - 35) * Math.cos(angle / 180 * Math.PI) + X - 4) y = Math.round((R - 35) * Math.sin(angle / 180 * Math.PI) + Y + 6) clockCtx.font = angle % 90 === 0 ? 'bold 15px yahei' : '12px yahei' clockCtx.fillText((angle + 90) / 30 || 12, x , y) } } } // 渲染表针 function renderClockHand () { let date = new Date() let hour = date.getHours() let minute = date.getMinutes() let second = date.getSeconds() // 秒针 let angle1 = (second * 6 - 90) let x = Math.round((R - 45) * Math.cos(angle1 / 180 * Math.PI) + X) let y = Math.round((R - 45) * Math.sin(angle1 / 180 * Math.PI) + Y) drawLine(clockHandCtx, [[X, Y], [x, y]], 1) // 分针 let angle2 = (minute * 6 - 90) x = Math.round((R - 65) * Math.cos(angle2 / 180 * Math.PI) + X) y = Math.round((R - 65) * Math.sin(angle2 / 180 * Math.PI) + Y) drawLine(clockHandCtx, [[X, Y], [x, y]], 2) // 时针, 时针角度 = 小时角度 + 分钟角度 let angle3 = ((hour % 12) * 30 - 90) + (angle2 / 12) x = Math.round((R - 90) * Math.cos(angle3 / 180 * Math.PI) + X) y = Math.round((R - 90) * Math.sin(angle3 / 180 * Math.PI) + Y) drawLine(clockHandCtx, [[X, Y], [x, y]], 4) } /** * @param {String} color 颜色 * @param {Number} r 圆半径 * @param {Number} lineWidth 线条粗细 * @param {String} type 类型,stroke/fill * @param {Boolean} isLinear 是否渐变 */ function drawCircle (ctx, cvs, x, y,color = '#000000', r = 10, lineWidth = 2, type = 'stroke', isLinear = false) { let grd = ctx.createLinearGradient(0, 0, cvs.width, cvs.height) grd.addColorStop(0, color) grd.addColorStop(0.5, '#555555') grd.addColorStop(1, color) ctx[type + 'Style'] = isLinear ? grd : color ctx.lineWidth = lineWidth ctx.beginPath() ctx.arc(x, y, r, 0, Math.PI * 2, true) ctx.closePath() ctx[type]() } /** * @param {Array} pos 坐标点集合,如 [[0, 0], [120, 120]] * @param {String} color 颜色 * @param {Number} lineWidth 线条粗细 */ function drawLine (ctx, pos, lineWidth = 2, color = '#eac55a') { ctx.strokeStyle = color ctx.lineWidth = lineWidth ctx.beginPath() ctx.moveTo(pos[0][0], pos[0][1]) for (let i = 0, len = pos.length; i < len; i++) { ctx.lineTo(pos[i][0], pos[i][1]) } ctx.stroke() ctx.closePath() } })() </script> </body> </html>