Home > Software engineering >  Circular Spinner like CircularProgress from MUI, but using html5 canvas
Circular Spinner like CircularProgress from MUI, but using html5 canvas

Time:10-22

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>

  • Related