I'm working on an html5 video player where I use canvas to show videos. And when the video is not ready, I need to show the spinner (the same as in MUI's CircularProgress
).
It would be possible to overlay an svg spinner on top of the canvas. But it seems to me, since I'm using canvas, it's better to draw a spinner on it. That's what I got.
window.addEventListener("load", () => {
let canvas = document.querySelector("canvas");
let context = canvas.getContext("2d");
let x = canvas.width / 2;
let y = canvas.height / 2;
let radius = 30;
let cycleNum = 0;
let startAngle = 0;
let endAngle = Math.PI / 64;
let totalCycles = 200;
let linearStep = ((2 * Math.PI) / totalCycles) * 2;
let quadroStep;
let totalSteps = totalCycles / 4;
const drawSpinner = () => {
if (cycleNum >= totalCycles) cycleNum = 0;
cycleNum = 1;
context.clearRect(0, 0, canvas.width, canvas.height);
let curStep = cycleNum % totalSteps;
if (
cycleNum <= totalCycles / 4 ||
(cycleNum > totalCycles / 2 && cycleNum <= (totalCycles * 3) / 4)
) {
// 1st and 3rd quarters of cycle
quadroStep = ((Math.PI * curStep) / totalSteps) ** 2 / 75;
} else if (
(cycleNum > totalCycles / 4 && cycleNum <= totalCycles / 2) ||
cycleNum > (totalCycles * 3) / 4
) {
// 2nd and 4rd quarters of cycle
quadroStep = ((Math.PI * (totalSteps - curStep)) / totalSteps) ** 2 / 75;
}
if (cycleNum <= totalCycles / 2) {
// 1st half-cycle we move endAngle
startAngle = linearStep;
endAngle = linearStep quadroStep;
} else {
// 2nd half-cycle we move startAngle
startAngle = linearStep quadroStep;
endAngle = linearStep;
}
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, 0);
context.lineWidth = 6;
context.strokeStyle = "rgba(0,0,0,0.3)";
context.stroke();
}
setInterval(drawSpinner, 10);
});
<html>
<head></head>
<body>
<canvas></canvas>
</body>
</html>
This works fine, but the quadroStep
calculation confuses me - although it provides acceleration/deceleration, it is selected experimentally (especially division by 75). However, I understand that this value can somehow be calculated more accurately - you need to somehow divide the distance between the startAngle
and the endAngle
(taking into account the remaining gap between them) into "quadratic steps", but how exactly this can be done?
It would be great if someone could guide me in the right direction. Thanks!
CodePudding user response:
In order to divide the segment into quadratic steps, I took an example where there are 3 steps:
x1 x2 x3 = sum
sum
is the total distance that we need to divide into quadratic steps,
xN
is the value of the segment length at a specific step N
.
Our function is quadratic, so we will set the following function:
xN = N ** 2
Let 's calculate an example:
1 ** 2 2 ** 2 3 ** 2 = sum
1 4 9 = sum
sum = 14
Ok, it's clear with this example, but what if our sum
is not equal to 14? How then to find all xN
? Let's introduce a coefficient k
, which will change the sum
of all xN
:
k * x1 k * x2 k * x3 = sum
k * (x1 x2 x3) = sum
k = sum / (x1 x2 x3)
And then knowing k
we can calculate the value of any xN
as follows:
xN = N ** 2 * k
So the code for calculating k
came out as follows:
let gap = Math.PI / 2;
let sum = (2 * Math.PI - gap) / 2;
let totalSteps = totalCycles / 4;
let calcSum = 0;
for (let i = 1; i <= totalSteps; i ) calcSum = i ** 2;
let k = sum / calcSum;
Here gap
is which part of the ring will remain open.
And that's what I got as a result, maybe it will be useful to someone:
window.addEventListener("load", () => {
let canvas = document.querySelector("canvas");
let context = canvas.getContext("2d");
let x = canvas.width / 2;
let y = canvas.height / 2;
let radius = 30;
let cycleNum = 0;
let startAngle = 0;
let endAngle = Math.PI / 64;
let totalCycles = 200;
let linearStep = ((2 * Math.PI) / totalCycles) * 2;
let quadroStep;
let gap = Math.PI / 2;
let sum = (2 * Math.PI - gap) / 2;
let totalSteps = totalCycles / 4;
let calcSum = 0;
for (let i = 1; i <= totalSteps; i ) calcSum = i ** 2;
let k = sum / calcSum;
const drawSpinner = () => {
if (cycleNum >= totalCycles) cycleNum = 0;
cycleNum = 1;
context.clearRect(0, 0, canvas.width, canvas.height);
let curStep = ((cycleNum - 1) % totalSteps) 1;
if (
cycleNum <= totalCycles / 4 ||
(cycleNum > totalCycles / 2 && cycleNum <= (totalCycles * 3) / 4)
) {
// 1st and 3rd quarters of cycle
quadroStep = curStep ** 2 * k;
} else if (
(cycleNum > totalCycles / 4 && cycleNum <= totalCycles / 2) ||
cycleNum > (totalCycles * 3) / 4
) {
// 2nd and 4rd quarters of cycle
quadroStep = (totalSteps - curStep) ** 2 * k;
}
if (cycleNum <= totalCycles / 2) {
// 1st half-cycle we move endAngle
startAngle = linearStep;
endAngle = linearStep quadroStep;
} else {
// 2nd half-cycle we move startAngle
startAngle = linearStep quadroStep;
endAngle = linearStep;
}
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, 0);
context.lineWidth = 6;
context.strokeStyle = "rgba(0,0,0,0.3)";
context.stroke();
}
setInterval(drawSpinner, 10);
});
<html>
<head></head>
<body>
<canvas></canvas>
</body>
</html>