导语:距离上一次写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>