HTML & CSS
<div class="clock">
<canvas id="clock" width="500" height="500" />
</div>
.clock {
width: 500px;
height: 500px;
background-color: #202121;
margin: 50px auto;
border-radius: 100px;
}
JS 结构 和 获取Canvas 渲染环境
Canvas.getContext(contextID) 获取 canvas 渲染上下文
- 参数 contextID
- 目前只支持 ‘2d’
- 返回 CanvasRenderingContext2D 对象 ```javascript (function () { let ctx = clock.getContext(“2d”); class Clock { init() { this.draw(); } draw() { } } window.Clock = Clock; })();
new Clock().init();
<a name="rm4xH"></a>
# 准备
Clock 类的 draw() 增加绘制面板的能力
```javascript
class Clock {
init() {
this.draw();
}
draw() {
this.drawPanel();
}
drawPanel() {
}
window.Clock = Clock;
})();
画布宽高
通过上下文的 canvas 属性来获取
cWidth = ctx.canvas.width
cHeight = ctx.canvas.height
确定半径
半径为画布一半小 20px
class Clock{
constructor() {
this.r = cWidth / 2 - 20;
}
// ...
}
开始一个新路径
CanvasRenderingContext2D.beginPath()
擦除一个矩形区域
CanvasRenderingContext2D.clearRect(x, y, width, height)
画线
两点确定一条线
CanvasRenderingContext2D.moveTo(x, y)
CanvasRenderingContext2D.lineTo(x, y)
画圆弧
CanvasRenderingContext2D.arc()
void ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
参数
- x 圆弧中心(圆心)的 x 轴坐标。
- y 圆弧中心(圆心)的 y 轴坐标。
- radius 圆弧的半径。
- startAngle 圆弧的起始点,x轴方向开始计算,单位以弧度表示。
- endAngle 圆弧的终点,单位以弧度表示。
- anticlockwise 可选的Boolean值 ,如果为 true,逆时针绘制圆弧,反之,顺时针绘制。
图形绘画
stroke 描边
CanvasRenderingContext2D.strokeStyle 描边样式
CanvasRenderingContext2D.stroke() 描边
fill 填充
CanvasRenderingContext2D.fillStyle 填充样式
CanvasRenderingContext2D.fill() 填充画文字
CanvasRenderingContext2D.strokeText() 描边文字
CanvasRenderingContext2D.fillText() 填充文字
void ctx.fillText(text, x, y, [maxWidth]);
参数
- text 使用当前的 font, textAlign, textBaseline 和 direction 值对文本进行渲染。
- x 文本起点的 x 轴坐标。
- y 文本起点的 y 轴坐标。
- maxWidth 可选 绘制的最大宽度。如果指定了值,并且经过计算字符串的值比最大宽度还要宽,字体为了适应会水平缩放(如果通过水平缩放当前字体,可以进行有效的或者合理可读的处理)或者使用小号的字体。
文字大小 / 字体
CanvasRenderingContext2D.font = “40px sans-serif”
CanvasRenderingContext2D.textAlign = “center”
CanvasRenderingContext2D.textBaseline = “middle”
画布平移
CanvasRenderingContext2D.translate(x, y)
画布旋转
CanvasRenderingContext2D.rotate(angle)
保存 / 恢复 canvas 全部状态
CanvasRenderingContext2D.save()
CanvasRenderingContext2D.restore()
绘制
底板
this.drawPanel()
ctx.translate 平移画布,把时钟的中心设置为画布中心
使用画圆弧的方法,以填充方式在画布的中心点画一个圆
class Clock {
constructor() {
this.r = cWidth / 2 - 20;
}
init() {
this.draw();
}
draw() {
this.drawPanel();
}
drawPanel() {
ctx.beginPath();
ctx.translate(cWidth / 2, cWidth / 2);
ctx.fillStyle = "#fff";
ctx.arc(0, 0, this.r, 0, 2 * Math.PI);
ctx.fill();
}
}
面板数字
this.drawHourNums
使用填充文字的方法把小时数字绘制于画布上
但先要确定先个数字位置,把一个圆分为 12 等份,求出每个等份的圆弧的点的坐标
弧度角 k 为每一等份
hours = Array(12)
.fill(undefined)
.map((_, index) => index + 1); // [1,2,3...12] 的数组
drawHourNums() {
ctx.font = "40px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#000";
hours.forEach((text, k) => {
let rad = ((2 * Math.PI) / 12) * (k - 2);
// 因为在 x 轴开始(3点的位置) k-2 让开始的 1 回到时钟的位置
let x = Math.cos(rad) * (this.r - 40);
let y = Math.sin(rad) * (this.r - 40);
ctx.beginPath();
ctx.fillText(text, x, y);
});
}
面板中点
drawCentralPointer() {
ctx.beginPath();
ctx.fillStyle = "#666";
ctx.arc(0, 0, 13, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#333";
ctx.arc(0, 0, 6, 0, 2 * Math.PI);
ctx.fill();
}
时分秒针
利用 moveTo lineTo 画出来
drawHourIndicator() {
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = "round";
ctx.moveTo(0, 0);
ctx.lineTo(0, -this.r / 1.8);
ctx.stroke();
}
drawMinuteIndicator() {
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = "round";
ctx.moveTo(0, 0);
ctx.lineTo(0, -this.r / 1.5);
ctx.stroke();
}
drawSecondIndicator() {
ctx.beginPath();
ctx.strokeStyle = "orange";
ctx.lineWidth = 5;
ctx.lineCap = "round";
ctx.moveTo(0, 0);
ctx.lineTo(0, -this.r / 1.2);
ctx.stroke();
}
动起来
显示当前时间
封装一个获取当前时间函数 getTime(),返回一个带有时分秒属性的对象
function getTime() {
let d = new Date();
return {
hours: d.getHours(),
minutes: d.getMinutes(),
seconds: d.getSeconds()
};
}
然后在绘制时分秒时,把对应的值传入相应的绘制函数
draw() {
const { hours, minutes, seconds } = getTime();
this.drawPanel();
this.drawHourNums();
this.drawHourIndicator(hours, minutes);
this.drawMinuteIndicator(minutes);
this.drawSecondIndicator(seconds);
this.drawCentralPointer();
}
修改,能按传入的参数绘制指针。通过 rotate 旋转其角度
drawHourIndicator(hours, minutes) { // 时针是受, 时和分影响
const rad = ((2 * Math.PI) / 12) * hours,
mRad = ((2 * Math.PI) / 12 / 60) * minutes;
ctx.save();
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = "round";
ctx.rotate(rad + mRad);
ctx.moveTo(0, 0);
ctx.lineTo(0, -this.r / 1.8);
ctx.stroke();
ctx.restore();
}
drawMinuteIndicator(minutes) {
const rad = ((2 * Math.PI) / 60) * minutes;
ctx.save(); // 先保存 canvas 的状态
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = "round";
ctx.rotate(rad); // 要在绘制之前先旋转
ctx.moveTo(0, 0);
ctx.lineTo(0, -this.r / 1.5);
ctx.stroke();
ctx.restore(); // 完成后恢复
}
drawSecondIndicator(seconds) {
const rad = ((2 * Math.PI) / 60) * seconds;
ctx.save();
ctx.beginPath();
ctx.strokeStyle = "orange";
ctx.lineWidth = 5;
ctx.lineCap = "round";
ctx.rotate(rad);
ctx.moveTo(0, 0);
ctx.lineTo(0, -this.r / 1.2);
ctx.stroke();
ctx.restore();
}
动起来
设置 setInterval 使其每秒重绘一次,
重绘前用 clearRect 画布透明,也要结合 save 和 restore 对画布的状态做处理
let ctx = clock.getContext("2d"),
cWidth = ctx.canvas.width,
cHeight = ctx.canvas.height,
hours = Array(12)
.fill(undefined)
.map((_, index) => index + 1),
t;
init() {
this.draw();
t = setInterval(this.draw.bind(this), 1000);
}
draw() {
ctx.clearRect(0, 0, cWidth, cHeight);
const { hours, minutes, seconds } = getTime();
ctx.save();
this.drawPanel();
this.drawHourNums();
this.drawHourIndicator(hours, minutes);
this.drawMinuteIndicator(minutes);
this.drawSecondIndicator(seconds);
this.drawCentralPointer();
ctx.restore();
}