最近学习了一些canvas基础语法,也复习了一些三角函数知识,于是决定基于现有学习的知识自己摸索来实现一个时钟功能。
基础工作
这里列举一些开发中要数学公式,这里要注意的是所有用到的API都为弧度制:
弧度 = 角度 * Math.PI / 180- 已知半径为
r,圆心点为(0, 0)求圆上一点坐标:x = Math.sin(弧度) * ry = -Math.cos(弧度) * r
获取上下文
其次是一些获取canvas与上下文等基本操作,这里我们使用到Class来实现,构造函数中配置一些基础信息,例如圆心、半径等。
class Clock {
constructor(canvas, width, height, r) {
this.canvas = canvas
this.canvas.width = width
this.canvas.height = height
this.center = { x: width / 2, y: height / 2 } // 圆心
this.r = r // 半径
this.ctx = canvas.getContext('2d') // 获取渲染上下文
}
}
const canvas = document.querySelector('#canvas')
const clock = new Clock(canvas, 500, 500, 200)
绘制表盘
首先绘制表盘,为Clock添加renderBorder、renderClockDial方法。
renderBorder与renderCenter主要是绘制时钟边框与圆心,用到的主要是arc方法,绘制一个以圆心为坐标,半径为r的圆,但这里需要偏移一些,不然会与数字重合,要注意的是这里startAngle与endAngle都为弧度制,刚好2 * Math.PI为一个圆。
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
// 渲染表盘
renderBorder() {
this.ctx.beginPath()
const offset = 30 // 设置一些偏移量,不然会与后续数字重叠
this.ctx.arc(this.center.x, this.center.y, this.r + offset, 0, 2 * Math.PI)
this.ctx.closePath()
this.ctx.stroke()
}
// 渲染圆心
renderCenter() {
this.ctx.beginPath()
const centerR = 5 // 设置一些偏移量,不然会与后续数字重叠
this.ctx.arc(this.center.x, this.center.y, centerR, 0, 2 * Math.PI)
this.ctx.closePath()
this.ctx.fill()
}
renderClockDial方法为绘制圆盘1~12的数字,观察时钟,以12点为0度,1点为30度,以此类推刚好得出- 每个时间点的角度为
360 / 12 * 小时数 - 每个时间点的弧度为
角度 * Math.PI / 180 - 每个时间点的
(x, y)坐标:x = Math.sin(弧度) * r、y = -Math.cos(弧度)* r
- 每个时间点的角度为
// 渲染表盘数字
renderClockDial() {
const ctx = this.ctx
// 因为需要修改圆心点等状态,所以保存一下状态,后续恢复
ctx.save()
// 将原点移动到中心点,以便操作
ctx.translate(this.center.x, this.center.y)
// 设置一些文字的基础样式
ctx.textBaseline = 'middle'
ctx.font = '30px san-self'
ctx.textAlign = 'center'
for(let hour = 1; hour <= 12; hour++) {
const angle = (360 / 12) * hour
const radian = angle * Math.PI / 180
const x = Math.sin(radian) * this.r
const y = -Math.cos(radian) * this.r
const text = String(hour)
ctx.fillText(text, x, y)
}
// 恢复状态
ctx.restore()
}
绘制时针与分针
时针与分针其实类似,按照上面操作类似依葫芦画瓢,新建renderHours与renderMinutes方法。
- 通过
Date对象获取当前小时与分钟数。 - 每个小时角度为
360 / 12 * 小时数。 - 每个分钟角度为
360 / 60 * 分钟数。 - 弧度为
角度 * Math.PI / 180 - 获取时间点
(x, y)坐标,从中心点连线到(x, y)坐标 - 时针短一些,分针长一些。
// 渲染小时
renderHours() {
const ctx = this.ctx
ctx.save()
// 将原点移动到中心点,以便操作
ctx.translate(this.center.x, this.center.y)
const hours = new Date().getHours()
const width = 50
ctx.beginPath()
ctx.moveTo(0, 0)
const angle = (360 / 12) * hours
const radina = angle * Math.PI / 180
const x = Math.sin(radina) * width
const y = -Math.cos(radina) * width
ctx.lineTo(x, y)
ctx.closePath()
ctx.stroke()
ctx.restore()
}
// 渲染分钟
renderMinutes() {
const ctx = this.ctx
ctx.save()
ctx.translate(this.center.x, this.center.y)
const minutes = new Date().getMinutes()
const width = 100 // 长度
ctx.beginPath()
ctx.moveTo(0, 0)
const angle = (360 / 60) * minutes
const radina = angle * Math.PI / 180
const x = Math.sin(radina) * width
const y = -Math.cos(radina) * width
ctx.lineTo(x, y)
ctx.closePath()
ctx.strokeWidth = width
ctx.stroke()
ctx.restore()
}
绘制秒针
这里我们做一个围绕着圆盘,变动的秒针,依葫芦画瓢,我们可以获取当前秒数的坐标。
- 根据
Date对象获取当前秒数。 - 获取秒数对应的
(x, y)坐标。 - 画一个小圆。
// 渲染秒针
renderSeconds() {
const ctx = this.ctx
ctx.save()
ctx.translate(this.center.x, this.center.y)
const seconds = new Date().getSeconds()
const angle = (360 / 60) * seconds
const radina = angle * Math.PI / 180
const x = Math.sin(radina) * this.r
const y = -Math.cos(radina) * this.r
ctx.beginPath()
ctx.arc(x, y, 3, 0, Math.PI * 2)
ctx.closePath()
ctx.fill()
ctx.restore()
}
让时钟动起来
要想让canvas动起来,实际就是不断地清除canvas,再不断地绘制canvas
- 新建一个
clear方法,用以清除。 - 新建一个
render方法,用以绘制。 - 新建一个
start方法,不断执行clear与render。
// 启动
start() {
setInterval(() => {
this.clear()
this.render()
}, 1000)
}
// 渲染
render() {
this.renderBorder()
this.renderClockDial()
this.renderMinutes()
this.renderHours()
this.renderSeconds()
}
// 清空
clear() {
const { width, height } = this.canvas
this.ctx.clearRect(0, 0, width, height)
}