Sleek rendering for circular progress bar in JS/HTML

I'm in the process of refining the animation for a circular progress bar that I created using HTML canvas arc and JavaScript.

The progress bar is generated using data extracted from an XML file (semic.cgx) which is constantly updated by a PLC.

My plan involves checking the XML file every 100ms for any changes and gradually adding 1/10th of an increment to the variable controlling the progress every 10ms. Essentially, turning one increment into ten.

Despite my efforts, I haven't been able to achieve the desired smoothness in the animation. The code snippet below encounters problems once the upper limit of the progress bar is reached.

I understand that there are various other methods to accomplish this task; however, most of the online examples lack detailed explanations on how the code actually works. My programming knowledge is basic, so I would greatly appreciate any assistance.

var req

function reloadData() {
    
    url = 'semic.cgx'
    
    try
    {
        req = new XMLHttpRequest();
    }
    catch (e)
    {
        alert('No AJAX Support');
        return;
    }
    
   req.onreadystatechange = myFunction;
   req.open('GET', url, true);
   req.send(null);
}




function myFunction() {
    

if (req.readyState == 4)
{
    if (req.status == 200)
    {
        
        var x = req.responseXML;
        var v1 = x.getElementsByTagName('text')[0].childNodes[1].innerHTML;

        var angle = (0.75 +((v1 / 100)* 1.5));
        

        
        var c = document.getElementById("myCanvas");
        var ctx = c.getContext("2d");
        
         setInterval(function () {
            
            if (angle >= 2.25) {
        ctx.clearRect(0,0,500,500);
        }
            
            
            if (angle < 2.25) {
            angle = angle + 0.0015;
            
            ctx.globalCompositeOperation = "source-over";
        ctx.rotate(0.5*2*Math.PI);
        ctx.lineWidth = 15;
        ctx.imageSmoothingEnabled = true;
        ctx.imageSmoothingQuality = "high";
        ctx.beginPath();
        ctx.arc(250, 250, 200, (0.75 * Math.PI), (angle * Math.PI));
        ctx.strokeStyle = "#DE2700";
        ctx.stroke();   
            }
    
        
            console.log(angle);
        }, 10);
    
        
        timeoutID = setTimeout('reloadData()', 100);    
    }

}
}

Answer №1

To enhance the efficiency of your app, it is advisable to separate your arc drawing function from other app logic.

Here's a refactored version of your drawing code consolidated into a single function that requires:

  • A value between 0 and 1 to initiate the arc,
  • A value between 0 and 1 to conclude the arc,
  • The angle for the arc that equates to 0,
  • The angle for the arc that equates to 1

const ctx = document.getElementById("myCanvas").getContext("2d");
ctx.globalCompositeOperation = "source-over";
ctx.lineWidth = 15;
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";

function draw(p0, p1, start = 0.75 * Math.PI, end = 2.25 * Math.PI) {
  // Mapping from 0-1 to start-end
  const range = end - start;
  
  const fromAngle = range * p0 + start;
  const toAngle = range * p1 + start;
  
  ctx.beginPath();
  ctx.arc(250, 250, 200, fromAngle, toAngle);
  ctx.strokeStyle = "#DE2700";
  ctx.stroke();
}

draw(0, 1);
canvas { 
  transform-origin: top left;
  transform: scale3d(0.3, 0.3, 1);
}
<canvas id="myCanvas" width="500" height="500></canvas>

Once you have organized the above, you can concentrate on animation. The animate function illustrated below requires three parameters:

  • A value between 0 and 1 to trigger the animation,
  • A value between 0 and 1 to terminate the animation,
  • The duration of the animation in milliseconds

// Setup
const ctx = document.getElementById("myCanvas").getContext("2d");
ctx.globalCompositeOperation = "source-over";
ctx.lineWidth = 15;
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";

// Animate from 0 to 100% within 1s
animate(0, 1, 1000);

function draw(p0, p1, start = 0.75 * Math.PI, end = 2.25 * Math.PI) {
  const range = end - start;
  const fromAngle = range * p0 + start;
  const toAngle = range * p1 + start;
  
  ctx.beginPath();
  ctx.arc(250, 250, 200, fromAngle, toAngle);
  ctx.strokeStyle = "#DE2700";
  ctx.stroke();
}

function animate(from, to, duration) {
  const range = to - from;
  let start = null;
  
  const next = (ts) => {
    if (!start) {
      start = ts;
    }
    
    // Progression from start to end as a value ranging from 0 to 1
    const dt = Math.min(ts - start, duration);
    const p = dt / duration;
    
    draw(from, from + p * range);
    
    if (dt < duration) requestAnimationFrame(next);
  }

  requestAnimationFrame(next);
}
canvas { 
  transform-origin: top left;
  transform: scale3d(0.3, 0.3, 1);
}
<canvas id="myCanvas" width="500" height="500></canvas>

The most challenging part now involves linking the drawing and animation to updates in external values. While simulating your xml workflow may be complex, this imitation should provide an insight:

  • Retrieve the latest loading value from the xml document,
  • Instruct the animation function to transition from the previously loaded value to the new value in 100ms
  • If not at 100% upon completion of the 100ms interval, schedule another call

// Setup
const ctx = document.getElementById("myCanvas").getContext("2d");
ctx.globalCompositeOperation = "source-over";
ctx.lineWidth = 15;
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";

// Mimic a +- 50s load time
let p = 0;
const update = () => {
  const newP = Math.min(p + Math.random() / 50, 1);
  animate(p, newP, 100);
  
  p = newP;
  
  if (p < 1) setTimeout(update, 100);
}

update();

// Functions for Drawing and Animation
function draw(p0, p1, start = 0.75 * Math.PI, end = 2.25 * Math.PI) {
  const range = end - start;
  const fromAngle = range * p0 + start;
  const toAngle = range * p1 + start;
  
  ctx.beginPath();
  ctx.arc(250, 250, 200, fromAngle, toAngle);
  ctx.strokeStyle = "#DE2700";
  ctx.stroke();
}

function animate(from, to, duration) {
  const range = to - from;
  let start = null;
  
  const next = (ts) => {
    if (!start) {
      start = ts;
    }
    
    // Progression from start to end as a value ranging from 0 to 1
    const dt = Math.min(ts - start, duration);
    const p = dt / duration;
    
    draw(from, from + p * range);
    
    if (dt < duration) requestAnimationFrame(next);
  }

  requestAnimationFrame(next);
}
canvas { 
  transform-origin: top left;
  transform: scale3d(0.3, 0.3, 1);
}
<canvas id="myCanvas" width="500" height="500></canvas>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Can React-Native-HighCharts be used to load tooltips with external data?

Good Morning, I have a question regarding reading an external variable that contains a list in the settings of a HighCharts chart for React-Native. Specifically, I am using the "react-native-highcharts" component. In my code snippet below, I am trying to ...

Prevent inspection of elements in Angular 2

Is there a way to prevent users from using inspect element on my website? I have disabled text selection, but users are still able to copy content through inspect element. ...

Blending synchronous and asynchronous testing with Mocha

There is a function that calculates certain values and informs the user about events using callbacks: function returnAndCallback(callback) { callback(5); // not always called return 3; } Incorporating Mocha and Should.js, a test was created: descri ...

Error: The PDFJS variable is not recognized when trying to load a PDF file

I've been experimenting with pdf.js to load PDFs into a web application in order to extract information in real-time. However, I keep encountering an error even with a simple example. I've attempted enclosing the code in $(document).ready() as a ...

Numerous Google Gauges featuring varying sets of options

Currently, I am facing a challenge involving the insertion of multiple instances of Google Gauges (or Highchart Gauges) on a single page. The goal is to utilize different option sets and separate their placement accordingly. Unfortunately, the solution pro ...

Issues encountered with integrating external jQuery/JavaScript functions with Wordpress child theme template

After creating a custom template in my WordPress child theme, I added a link to the Bootstrap v3.0.3 .js file stored on my site. While the popup modal is functioning properly, the jQuery tabs seem to be having some issues. Although they are being display ...

React: Updating a state before calling another function - Best practices

In my code, there is a state variable named list that gets updated whenever the function setList is called. The setting of the list happens within the function AddToList, where a new value is added to the existing values in the list. However, I have notice ...

Ensure the background image completely fills the div, regardless of the image's aspect ratio

Can anyone help me figure out how to fill a div's background with an image, regardless of its aspect ratio (landscape or portrait)? I've tried different methods from using background-size: cover; to background-size: 100% 100%;, but the div doesn& ...

How can a variable be exported from a React component?

Is there a technique to export a variable from a React component for use in a custom hook? The code snippet is as follows:- function App() { const SignIn=()=>{ //Some code here var userId = data.info.username; //Some code h ...

What is the functionality of bootstrap colors?

Recently, I've been practicing my skills with Bootstrap, and I'm determined to understand all aspects of the code. However, one thing that's currently puzzling me is the navbar colors. When I apply the navbar-dark class, instead of displayin ...

What's causing the failure in the execution of the "Verify version" step?

Upon reviewing the 3DSecure GlobalPay documentation, my team decided to integrate it using JSON, incorporating our own client-side implementation. This decision was made because we already have another integration with a different 3DS verification service ...

How to create select options in Angular.js without using the ng-option directive

I receive a JSON object from a service and I am using some of its fields to populate my select option list. However, when I try to print the selected value in my controller, the output response is "undefined". Can someone help me figure out what I'm ...

enable jQuery timer to persist even after page refresh

code: <div class="readTiming"> <time>00:00:00</time><br/> </div> <input type="hidden" name="readTime" id="readTime"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script&g ...

Integrating Amazon external images in NextJS

There is a specific issue with loading images from a URL stored on Amazon S3 within the domain configured in next.config.js. Strangely, when using external URLs like Unsplash, the images load fine. The problematic URL is: idinheiro-admin-images.s3.sa-east ...

React does not recognize CSS attributes

I am facing an issue with the CSS styling when using built-in tags like "form-group" in my code. Strangely, the browser does not apply the CSS when I use these built-in tags. However, if I define my own classes, such as "classes.form", the CSS is visible i ...

What is the best way to create a dynamic pie chart in AngularJS using JSON data?

In my controller: When calling the web services, if the service is successful, then retrieve the data and push it into the corresponding arrays. for(var i = 0; i < $scope.reportRangeList.length; i++) { count++; if( ...

We were unable to locate the requested resource

I have been working on setting up an Express endpoint to fetch comments or reviews of a movie based on the movie ID. In my initial route, I manually passed the ID and retrieved data from TheMovieDB. However, I wanted to make this process dynamic in my seco ...

Encountering an issue while trying to communicate with a web

I've been following a tutorial on how to call a web service from a web page located at http://www.codeproject.com/KB/webservices/CallWebServiceFromHtml.aspx, but I'm encountering an error in the Firebug console: service is not defined Initia ...

Can anyone suggest a way to change the orientation of mapped items from column to row?

In my React app, I am creating a keyboard using the following component: Keypad.js const Keypad = () => { const letters = [ 'Q', 'W', 'E', 'R', 'T', ...

React Material-UI: Trouble with Checkbox Toggle

I'm encountering an issue with my React code where the checkbox is not toggling when clicked. Here is the link to the codesandbox: https://codesandbox.io/s/inspiring-kirch-q6p4h The checkbox state initialization looks like this: const [checkbox, set ...