规律是创造与发现的,记忆是规律的推导与提取
最近在学习前端可视化,顺便做一些自己觉得好玩的demo。 学习canvas的时候想起大学时计算机图形学老师给我们布置的第一个作业就是画一个时钟,当时使用c#画,现在发现使用canvas这种指令式的方法来画的更方便,这里就记录和分享一下。
- 思考一个时钟由哪些东西构成:
- 表盘
- 时间刻度
- 时针
- 分针
- 秒针
- 那么我们设计一个类,提供以下方法:
- 创建表盘
- 画刻度
- 画时针
- 画分针
- 画秒针
- 按秒钟绘制
- 类如下:
// clock.ts 此文件需要 tsc编译
class Clock {
// 表盘canvas
canvas = null;
// 绘制上下文
context = null;
// 时间刻度文本
timeText = [ "XII","I", "II", "III","IV", "V", "VI","VII", "VIII","IX","X", "XI" ];
// 时间刻度角度
timeTheta = Array.from({ length: 12 }, (_, i) => (i / 12) * 2 * Math.PI);
// 表盘宽度
width = 100;
// 表盘高度
height = 100;
// 构造函数
constructor(el) {
this.initClockBoard(el);
this.width = this.canvas.width;
this.height = this.canvas.height;
this.start();
}
// 初始化表盘
initClockBoard(el) {
}
// 初始化时间刻度
drawTimeText() {
}
// 绘制时间针
drawTimeCircle(r = 100, color = "blue", theta = -0.5 * Math.PI) {
}
// 绘制秒针
drawSecondCircle(size = 200, color = 'blue') {
}
// 绘制分针
drawMinuteCircle(size = 180,color = 'yellow') {
}
// 绘制时针
drawHourCircle(size = 150,color = 'red') {
}
// 定时器绘制时间走动
drawTimer() {
}
// 启动器
start() {
}
}
这样,我们直接在初始化的时候传入canvas元素即可创建一个时钟:
const clock = new Clock('canvas')
相应HTML比较简单:
<!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>candbox</title>
<style>
canvas {
width: 256px;
height: 256px;
border: 1px solid #333;
}
</style>
<script src="clock.js"></script>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script src="index.js"></script>
</body>
</html>
现在我们分别看看各个方法是如何简单实现的
初始化表盘
表盘只需要加载canvas即可:
// 初始化表盘
initClockBoard(el) {
this.canvas = document.querySelector(el);
this.context = this.canvas.getContext("2d");
}
当然这里可以对入参做更严密的判断,多获取的canvas做标准化处理,这里省略
初始化时间刻度
时间刻度即从1到12平分一个圆盘,我们把整个圆的角度2π平分为12份,然后画在相距圆点相同距离的12个位置即可:
// 初始化时间刻度
drawTimeText() {
this.context.save();
this.context.clearRect(0, 0, this.width, this.height);
this.context.translate(this.width / 2, this.height / 2);
this.timeText.forEach((text, i) => {
this.context.save();
this.context.rotate(this.timeTheta[i]);
this.context.font = "20px Arial";
this.context.fillStyle = "black";
this.context.fillText(text, 0, -220);
this.context.restore();
});
this.context.restore();
}
绘制时间针
这里我们简单以扇形为针的表现,绘制扇形也比较简单:
// 绘制时间针
drawTimeCircle(r = 100, color = "blue", theta = -0.5 * Math.PI) {
this.context.save();
this.context.beginPath();
this.context.arc(this.width / 2, this.height / 2, r, -0.5 * Math.PI, theta);
this.context.lineTo(this.width / 2, this.height / 2);
this.context.fillStyle = color;
this.context.fill();
this.context.restore();
}
绘制秒针分针时针
时针我们只需要通过上一个绘制时间针的方法来画,需要提供各个时针的长度,颜色和角度即可,角度我们根据时间比率换算得到,需要注意两个点:
- 绘制时针时,由于是24小时制度,但是表盘是12小时,所以当时间大于12时,需要进行相应换算
- 时间进位的时候需要重绘表盘和刻度 具体如下:
// 绘制秒针
drawSecondCircle(size = 200, color = 'blue') {
const second = new Date().getSeconds();
const secondTheta = -0.5 * Math.PI + (second / 60) * 2 * Math.PI;
if (second === 0) {
this.context.clearRect(0, 0, this.width, this.height);
this.drawTimeText();
}
this.drawTimeCircle(size, color, secondTheta);
}
// 绘制分针
drawMinuteCircle(size = 180,color = 'yellow') {
const minute = new Date().getMinutes();
const minuteTheta = -0.5 * Math.PI + (minute / 60) * 2 * Math.PI;
if (minute === 0) {
this.context.clearRect(0, 0, this.width, this.height);
this.drawTimeText();
}
this.drawTimeCircle(size, color, minuteTheta);
}
// 绘制时针
drawHourCircle(size = 150,color = 'red') {
const hour = new Date().getHours();
const hourTheta =
-0.5 * Math.PI +
(hour / 12 > 1 ? hour / 12 - 1 : hour / 12) * 2 * Math.PI;
if (hour === 0) {
this.context.clearRect(0, 0, this.width, this.height);
this.drawTimeText();
}
this.drawTimeCircle(size, color, hourTheta);
}
绘制时间走动
时间走动我们使用requestAnimationFrame帧函数进行绘制,每一帧都重绘时间刻度、时针即可,需要注意requestAnimationFrame的回调函数需要使用箭头函数,不然读取不到类内部方法:
// 定时器绘制时间走动
drawTimer() {
this.drawTimeText();
this.drawSecondCircle();
this.drawMinuteCircle();
this.drawHourCircle();
requestAnimationFrame(() => {
this.drawTimer();
});
}
启动函数
使用帧函数调用时间走动函数即可
// 启动器
start() {
requestAnimationFrame(() => {
this.drawTimer();
});
}
最后看一下效果: