Canvas - 时钟绘制
导语:距离上一次写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>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | <!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> |
只有足够努力,才能看起来毫不费力