Reputation: 97
I have been working on a timer that is displaying 3 preset times based on the user's choice. I decided to include a countdown graphic in the shape of a clock which only has one hand and rotates 360 degrees for the full length of time assigned to it.
The problem I have is marrying up the original timer and the new display. I have the "clock" element drawn with canvas and I have a working timer, but I need to know how to take that time and utilise it to get the hand to move around. As it currently stands the hands will draw when I have it functioning, but don't display mostly because I haven't set an interval for the clock but I do have one for the original timer.
Full JS
((d) => {
let btn = d.getElementById("btn");
let reset = d.getElementById("reset");
let countdown = d.getElementById("countdown");
let btnLength = d.getElementById("btnLength");
// 25 Minutes
let short = 1500;
// 45 Minutes
let med = 2700;
// 90 Minutes
let long = 5400;
let counter;
let startTime = short;
let timerFormat = (s) => {
return (s - (s %= 60)) / 60 + (9 < s ? ":" : ":0") + s;
};
countdown.textContent = timerFormat(startTime);
let timer = () => {
startTime--;
countdown.textContent = timerFormat(startTime);
if (startTime === 0) clearInterval(counter);
};
let start = () => {
counter = counter || setInterval(timer, 950);
};
let stop = () => {
clearInterval(counter);
counter = undefined;
};
// Changes between Start and Stop button labelling.
btn.addEventListener("click", () => {
if (counter) {
stop();
btn.textContent = "Start";
} else {
start();
btn.textContent = "Stop";
}
});
// Stops counter, resets to original start time and
// resets button label to Start.
reset.onclick = () => {
stop();
startTime = short;
if (btn.textContent === "Stop") btn.textContent = "Start";
btnLength.textContent = "Short";
countdown.textContent = timerFormat(startTime);
};
// Changes timer legnth and button labelling
btnLength.addEventListener("click", () => {
if (startTime === short) {
startTime = med;
btnLength.textContent = "Medium";
} else if (startTime === med) {
startTime = long;
btnLength.textContent = "Long";
} else if (startTime === long) {
startTime = short;
btnLength.textContent = "Short";
}
countdown.textContent = timerFormat(startTime);
});
//Clock
let c = d.getElementById("canvas");
let ctx = c.getContext("2d");
let radius = c.height / 2;
ctx.translate(radius, radius);
radius = radius * 0.9;
showTime = (ctx, radius) => {
let startTime =
(startTime * Math.PI) / 30 + (startTime * Math.PI) / (30 * 60);
hand(ctx, startTime, radius * 0.8, radius * 0.07);
};
hand = (ctx, position, length, width) => {
ctx.beginPath();
ctx.lineWidth = width;
ctx.lineCap = "round";
ctx.moveTo(0, 0);
ctx.rotate(position);
ctx.lineTo(0, -length);
ctx.stroke();
ctx.rotate(-position);
};
clock = () => {
ctx.arc(0, 0, radius, 0, 2 * Math.PI, true);
ctx.fillStyle = "#f5afaf";
ctx.fill();
showTime();
};
clock();
})(document);
Html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../styles/styles.css" />
<script defer src="../JS/timer.js"></script>
</head>
<body>
<div class="btn__container">
<div id="countdown"></div>
<button class="btn" id="btn">Start</button>
<button class="btn" id="btnLength">Short</button>
<button class="btn" id="reset">Reset</button>
<div class="clock">
<canvas id="canvas" width="300" height="300" style="background-color:none"></canvas>
</div>
</body>
</html>
Upvotes: 2
Views: 424
Reputation: 456
The first issue you have is that you only call clock()
once - at the end of your script to initialise it.
Add it to timer()
function:
let timer = () => {
startTime--;
countdown.textContent = timerFormat(startTime);
clock(); // <-- here
if (startTime === 0) clearInterval(counter);
};
The second issue is that you don't pass ctx
and radius
when you call showTime()
:
clock = () => {
ctx.arc(0, 0, radius, 0, 2 * Math.PI, true);
ctx.fillStyle = "#f5afaf";
ctx.fill();
showTime(ctx, radius); // <-- here
};
The third issue you will have now is Cannot access 'startTime' before initialization
. This is because you re-declare startTime
in the showTime()
function. Update this function to:
showTime = (ctx, radius) => {
let time = // <-- here
(startTime * Math.PI) / 30 + (startTime * Math.PI) / (30 * 60);
hand(ctx, time, radius * 0.8, radius * 0.07);
};
The last thing you need to do is call finish the path when you draw the hand:
hand = (ctx, position, length, width) => {
ctx.beginPath();
ctx.lineWidth = width;
ctx.lineCap = "round";
ctx.moveTo(0, 0);
ctx.rotate(position);
ctx.lineTo(0, -length);
ctx.stroke();
ctx.rotate(-position);
ctx.closePath(); // <-- here
};
Now the hand moves with the timer. However, if I may, I wanted to suggest a few improvements. Currently, you move the canvas to get the center of your clock at coordinates (0,0)
, and then you move the canvas two more times every time you draw the hand. I suggest you leave the canvas alone and rather specify an offset
that tells you where the center of the clock is.
Now you will need to specify the exact coordinates to the tip of the hand, which can be done with some quick trigonometry (see full example below).
Last thing, the starting position of your hand is somewhere in the bottom right. Perhaps a good position for time 0
would be on top. If you want to change it you can add some ratio of PI
to the angle
calculated in showTime()
(e.g. add + Math.PI
to have time 0
at the bottom.
See the full example below:
((d) => {
let btn = d.getElementById("btn");
let reset = d.getElementById("reset");
let countdown = d.getElementById("countdown");
let btnLength = d.getElementById("btnLength");
// 25 Minutes
let short = 1500;
// 45 Minutes
let med = 2700;
// 90 Minutes
let long = 5400;
let counter;
let startTime = short;
let timerFormat = (s) => {
return (s - (s %= 60)) / 60 + (9 < s ? ":" : ":0") + s;
};
countdown.textContent = timerFormat(startTime);
let timer = () => {
startTime--;
countdown.textContent = timerFormat(startTime);
clock(); // <-- here
if (startTime === 0) clearInterval(counter);
};
let start = () => {
counter = counter || setInterval(timer, 950);
};
let stop = () => {
clearInterval(counter);
counter = undefined;
};
// Changes between Start and Stop button labelling.
btn.addEventListener("click", () => {
if (counter) {
stop();
btn.textContent = "Start";
} else {
start();
btn.textContent = "Stop";
}
});
// Stops counter, resets to original start time and
// resets button label to Start.
reset.onclick = () => {
stop();
startTime = short;
if (btn.textContent === "Stop") btn.textContent = "Start";
btnLength.textContent = "Short";
countdown.textContent = timerFormat(startTime);
};
// Changes timer length and button labelling
btnLength.addEventListener("click", () => {
if (startTime === short) {
startTime = med;
btnLength.textContent = "Medium";
} else if (startTime === med) {
startTime = long;
btnLength.textContent = "Long";
} else if (startTime === long) {
startTime = short;
btnLength.textContent = "Short";
}
countdown.textContent = timerFormat(startTime);
});
//Clock
let c = d.getElementById("canvas");
let ctx = c.getContext("2d");
let offset = c.height / 2;
let radius = offset * 0.9;
showTime = (ctx, radius) => {
let second = startTime % 60;
let angle = (2*Math.PI) - (2 * Math.PI * second) / 60; // Add + Math.PI; to put time 0 in the bottom
hand(ctx, angle, radius * 0.8, radius * 0.07);
};
hand = (ctx, angle, length, width) => {
// Calculate coordinates of the tip of the hand.
x = offset + Math.round(- length * Math.sin(angle));
y = offset + Math.round(- length * Math.cos(angle));
ctx.beginPath();
ctx.lineWidth = width;
ctx.lineCap = "round";
ctx.moveTo(offset, offset);
ctx.lineTo(x, y);
ctx.stroke();
ctx.closePath();
};
clock = () => {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.arc(offset, offset, radius, 0, 2 * Math.PI, true);
ctx.fillStyle = "#f5afaf";
ctx.fill();
showTime(ctx, radius);
};
clock();
})(document);
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script defer src="../Desktop/timer.js"></script>
</head>
<body>
<div class="btn__container">
<div id="countdown"></div>
<button class="btn" id="btn">Start</button>
<button class="btn" id="btnLength">Short</button>
<button class="btn" id="reset">Reset</button>
</div>
<div class="clock">
<canvas id="canvas" width="300" height="300" style="background-color:none"></canvas>
</div>
</body>
</html>
Upvotes: 1