Make writing a habit together! This is my first day to participate in the “Gold Digging Day New Plan · April More text challenge”, click to see the details of the activity.
Implementation effect
In addition to the pikachu background, the hour, minute and second hand scales are drawn on Canvas.
The implementation process
Set the basic size and background
- Full face footage of Pikachu
- The basic structure
- Set the HTML structure as follows:
<div class="canvas-box">
<canvas width="300" height="300"></canvas>
</div>
Copy the code
Question: Why embed a div box here?
Later, we set background-image to div to make the clock’s background pikachu. The reason for not setting the background image directly to the canvas is that the background image will overwrite the canvas clock we draw behind.
- Set the style style as follows:
.canvas-box {
width: 300px;
height: 300px;
background-image: url("img/pikachu.webp");
/* Stretch the image to see the panorama */
background-size: contain;
/* Horizontal center + vertical move up */
background-position: 50% 25%;
/* If the image is symmetric, it can be set as */
/* background-position: center; * /
/ * * / it become
border-radius: 50%;
}
Copy the code
Basic API (in order of basic use)
- GetContext: Returns the context of the Canvas object, which can be interpreted as a brush.
- ClearRect: Sets the pixel to transparent to erase a rectangular area, avoiding repeated drawing.
- Save: Press to save the canvas state. (corresponding to restore)
- Translate: If you add a translation to the current grid, x, y will change the relative translation position.
- Rotate: Adds a rotation to the transformation matrix, which changes the relative rotation Angle.
- BeginPath: method of starting a new path by clearing a list of empty subpaths, indicating the start of a brush drawing.
- Arc: Draws an arc path
- MoveTo: Moves the starting point of a new subpath to (x, y) coordinates
- LineTo: use a straight lineTo connect the end of a child path to the x, y coordinates
- Stroke: A method of drawing current or existing paths based on the current drawing style using a non-zero wrap rule. Basically, stroke.
- fillTextIn:(x, y)Position fill text. Similarly, fillRect, fill.
- ClosePath: Method of returning the pen point to the start of the current subpath. It tries to draw a line from the current point to the starting point. If the graph is already closed or has only one point, this method does nothing.
- Restore: The stack restores the brush state.
Of course, there are also some brush properties, such as lineWidth for lineWidth, lineCap for line end display style, fillStyle for fill color and strokeStyle for line color.
With this knowledge in mind, we’ll start drawing the clock
Draw scales and numbers
General content: After obtaining the basic information of the clock, first move the brush to the center of the circle according to the routine of save-draw-restore, then draw an arc as the border.
How to set the angles of the numbers and scales: Divide the angles evenly throughout the circle (2* math.pi), because there are 12 numbers, so they are divided into 12 parts, and because there are 60 scales, so the scales need to be divided into 60 parts.
How to set the coordinates of the number and scale: the unit circle’s sine and cosine functions can be used to find the relative x and y coordinates of the center of the circle. Of course, since the clock is not a unit circle, we multiply it by one more radius.
// Get basic data
let canvas = document.querySelector("canvas")
let ctx = canvas.getContext('2d')
let width = ctx.canvas.width
let height = ctx.canvas.height
let r = width / 2; / / radius
// Draw the background
function drawBackground() {
// Basic routine stack - draw - stack
ctx.save(); // Save the original context information to the stack
ctx.translate(r, r); // Move the brush to the center
ctx.beginPath() // Start drawing
ctx.lineWidth = 2 // Set the line width
// Draw the border of the clock
// Prototype: void ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
ctx.arc(0.0, r - ctx.lineWidth / 2.0.2 * Math.PI) // draw a circle with a radius of r- half the line width relative to 0,0
ctx.closePath() // Finish drawing
ctx.strokeStyle = '# 222' // Set the fill color
ctx.stroke() / / fill
// Scale number: since the circle starts in the positive x direction, it starts with 3
var hourNum = [3.4.5.6.7.8.9.10.11.12.1.2]
hourNum.map(function (num, i) {
var rad = 2 * Math.PI / 12 * i // 2 PI is a circle, broken into 12 parts
var x = Math.cos(rad) * (r * 10 / 12)
var y = Math.sin(rad) * (r * 10 / 12)
// Set the text style to be filled next
ctx.font = '13px Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillStyle = '# 000'
ctx.fillText(num, x, y)
})
// Draw the minute scale
for (var i = 0; i < 60; i++) {
var rad = 2 * Math.PI / 60 * i
var x = Math.cos(rad) * (r * 95 / 100)
var y = Math.sin(rad) * (r * 95 / 100)
ctx.beginPath()
if (i % 5= =0) {
ctx.fillStyle = '# 000'
ctx.arc(x, y, 1.0.2 * Math.PI, false)}else {
ctx.fillStyle = '# 808080'
ctx.arc(x, y, 1.0.2 * Math.PI, false)
}
ctx.fill()
}
ctx.restore(); // Restore the original context information
}
Copy the code
Draw hour hand, minute hand, second hand
Drawing an hour hand is actually like pulling a straight line from the center of a circle. But we need to set the Angle it points to according to an Hour. We still split the angles of the 12 squares (2 * math.pi / 12) to see how many angles there are for hour.
Minor detail: If we follow hour, the hour hand will only point to the hour, so following this idea, we need to slice the Angle even more finely, and then add the offset between the two hours according to minutes.
// Draw an hour hand
function drawHour(hour, minute) {
ctx.save();
ctx.translate(r, r);
var rad = 2 * Math.PI / 12 * hour; //2 π is a circle divided into 12 cells
var mad = 2 * Math.PI / 12 / 60 * minute; // The ratio of the corresponding minutes
ctx.rotate(rad + mad);
ctx.beginPath();
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.moveTo(0.0);
ctx.lineTo(0, -r / 3);
ctx.stroke();
ctx.restore();
}
Copy the code
Minute hand and second hand are more simple, just need to change the line thickness and length of the corresponding clockwise routine.
// Draw the minute hand
function drawMinute(minute) {
ctx.save();
ctx.translate(r, r);
let rad = 2 * Math.PI / 60 * minute; //2 π is a circle divided into 60 cells
ctx.rotate(rad);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.lineCap = 'round';
ctx.moveTo(0.0);
ctx.lineTo(0, -r / 2);
ctx.stroke();
ctx.restore();
}
// Draw the second hand
function drawSecond(second) {
ctx.save()
ctx.translate(r, r);
var rad = 2 * Math.PI / 60 * second
ctx.rotate(rad)
ctx.beginPath()
ctx.lineWidth = 1
ctx.lineCap = 'round'
ctx.strokeStyle = 'red'
ctx.moveTo(0.0)
ctx.lineTo(0, -r * 9 / 10);
ctx.stroke()
ctx.restore()
}
Copy the code
Regular update time
Finally, we only need to constantly update the time to achieve dynamic clock.
function drawMyClock() {
ctx.clearRect(0.0, width, height) // Empty, otherwise the drawing will be repeated
let now = new Date(a);let hour = now.getHours();
let minute = now.getMinutes();
let second = now.getSeconds();
drawBackground();
drawHour(hour, minute);
drawMinute(minute);
drawSecond(second);
}
drawMyClock(); // called once directly when the page is loaded
setInterval(function () { // Set timer directly
drawMyClock();
}, 1000);
Copy the code
The effect is as follows:
Finally, we tried to encapsulate the clock more elegantly with class, pumping the same CTX state, such as translate(r, R), into refresh, a continuously updated function. The final code is as follows:
class MyClock {
constructor(canvas,isNeedBorder = true,isNeedNumberDot = true,isNeedMinuteDot = true) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.width = this.ctx.canvas.width
this.height = this.ctx.canvas.height
this.r = this.width / 2; / / radius
this.isNeedBorder = isNeedBorder;
this.isNeedNumberDot = isNeedNumberDot;
this.isNeedMinuteDot = isNeedMinuteDot;
this.timer = null;
}
setTime(hour, minute, second) {// Set the clock time
if (this.isValid(hour, minute, second)) {
this.hour = hour;
this.minute = minute;
this.second = second; }}isValid(hour, minute, second) {// Check whether the time is valid
if (typeof hour == 'number' && typeof minute == 'number' && typeof second == 'number' && hour >=
0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60) {
return true;
}
return false;
}
start(hour, minute, second) {// Start running the clock
let now = new Date(a);this.setTime(hour || now.getHours(), minute || now.getMinutes(), second || now.getSeconds());
console.log(this.hour, this.minute, this.second);
this.timer = setInterval(() = > {
this.refresh();
this.addTime();
}, 1000);
}
end(){// End the clock
clearInterval(this.timer);
}
addTime() {// Update time
this.second++;
if (this.second >= 60) {
this.second -= 60;
this.minute++;
if (this.minute >= 60) {
this.minute -= 60;
this.hour++;
if (this.hour >= 24) {
this.hour -= 24; }}}}refresh() {// Refresh the clock
this.ctx.save(); // Adjust the public CTX context here
this.ctx.clearRect(0.0.this.width, this.height) // Empty, otherwise the drawing will be repeated
this.ctx.translate(this.r, this.r);
this.ctx.lineCap = 'round'
if (this.hour == undefined) return; // No initialization time
this.drawBackground(this.isNeedBorder,this.isNeedNumberDot,this.isNeedMinuteDot);
this.drawHour(this.hour, this.minute);
this.drawMinute(this.minute);
this.drawSecond(this.second);
this.ctx.restore();
}
drawBackground() {// Draw background (border + scale)
let ctx = this.ctx;
if (this.isNeedBorder) {
this.drawBorder();
}
if (this.isNeedNumberDot) {
this.drawNumberDot();
}
if (this.isNeedMinuteDot) {
this.drawMinuteDot(); }}drawBorder() {// Draw the border
let ctx = this.ctx;
let r = this.r;
ctx.save(); // Save the original context information to the stack
ctx.beginPath() // Start drawing
ctx.lineWidth = 2 // Set the line width
// Prototype: void ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
ctx.arc(0.0, r - ctx.lineWidth / 2.0.2 * Math.PI) // draw a circle with a radius of r- half the line width relative to 0,0
ctx.closePath() // Finish drawing
ctx.strokeStyle = '# 222' // Set the fill color
ctx.stroke() / / fill
ctx.restore(); // Restore the original context information
}
drawNumberDot() { // Draw a numeric scale
let ctx = this.ctx;
let r = this.r;
ctx.save();
// Scale number: since the circle starts in the positive x direction, it starts with 3
var hourNum = [3.4.5.6.7.8.9.10.11.12.1.2]
hourNum.map(function (num, i) {
var rad = 2 * Math.PI / 12 * i // 2 PI is a circle, broken into 12 parts
var x = Math.cos(rad) * (r * 10 / 12)
var y = Math.sin(rad) * (r * 10 / 12)
// Set the text style to be filled next
ctx.font = '13px Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillStyle = '# 000'
ctx.fillText(num, x, y)
})
ctx.restore();
}
drawMinuteDot() { // Draw the minute scale
let ctx = this.ctx;
let r = this.r;
ctx.save();
for (var i = 0; i < 60; i++) {
var rad = 2 * Math.PI / 60 * i
var x = Math.cos(rad) * (r * 95 / 100)
var y = Math.sin(rad) * (r * 95 / 100)
ctx.beginPath()
if (i % 5= =0) {
ctx.fillStyle = '# 000'
ctx.arc(x, y, 1.0.2 * Math.PI, false)}else {
ctx.fillStyle = '# 808080'
ctx.arc(x, y, 1.0.2 * Math.PI, false)
}
ctx.fill()
ctx.closePath();
}
ctx.restore();
}
drawHour(hour, minute) {// Draw an hour hand
let ctx = this.ctx;
let r = this.r;
ctx.save();
var rad = 2 * Math.PI / 12 * hour; //2 π is a circle divided into 12 cells
var mad = 2 * Math.PI / 12 / 60 * minute; // The ratio of the corresponding minutes
ctx.rotate(rad + mad);
ctx.beginPath();
ctx.lineWidth = 3;
ctx.moveTo(0.0);
ctx.lineTo(0, -r / 3);
ctx.stroke();
ctx.restore();
}
drawMinute(minute) {// Draw the minute hand
let r = this.r;
let ctx = this.ctx;
ctx.save();
let rad = 2 * Math.PI / 60 * minute; //2 π is a circle divided into 60 cells
ctx.rotate(rad);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(0.0);
ctx.lineTo(0, -r / 2);
ctx.stroke();
ctx.restore();
}
drawSecond(second) {// Draw the second hand
let ctx = this.ctx;
let r = this.r;
ctx.save()
var rad = 2 * Math.PI / 60 * second
ctx.rotate(rad)
ctx.beginPath()
ctx.lineWidth = 1
ctx.strokeStyle = 'red'
ctx.moveTo(0.0)
ctx.lineTo(0, -r * 9 / 10);
ctx.stroke()
ctx.restore()
}
}
// Get basic data
let canvas = document.querySelector("canvas")
let yyClock = new MyClock(canvas);
yyClock.start();
Copy the code