🔥 Create a progress circle without a plugin

Need to create a progress circle in your app?

Here’s some sneaky code so you can do it for free without a plugin :wink: :point_down:

Step 1

Add the following code to the HTML header of the page(s) where you want to display the progress circle.

Why? This imports a javascript library that contains a lot of the necessary underlying code.

Pro tip: download this file and add to the file manager of your app (self-host) in case this file becomes unavailable at some point

<script type="text/javascript" src="https://s3.amazonaws.com/appforest_uf/f1653801289839x298259557076639100/progressbar.js" async></script>

Step 2

Create an HTML element wherever you’d like to display the progress circle, and add this code

<style>
#container {
  width: 140px;
  height: 140px;
  position: relative;
}
</style>

<script>
var bar = new ProgressBar.Circle(container, {
  color: '#aaa',
  // This has to be the same size as the maximum width to
  // prevent clipping
  strokeWidth: 8,
  trailWidth: 1,
  easing: 'easeInOut',
  duration: 0,
  text: {
    autoStyleContainer: false
  },
  from: { color: '#33CD5F', width: 4 },
  to: { color: '#33CD5F', width: 4 },
  // Set default step function for all animate calls
  step: function(state, circle) {
    circle.path.setAttribute('stroke', state.color);
    circle.path.setAttribute('stroke-width', state.width);

    var value = Math.round(circle.value() * 100);
    if (value === 0) {
      circle.setText('0%');
    } else {
      circle.setText(value + '%');
    }

  }
});
bar.text.style.fontFamily = 'Montserrat';
bar.text.style.fontSize = '1.25rem';

bar.animate(0.75);  // Number from 0.0 to 1.0
</script>

<div id="container"></div>

Step 3

Customize it.


Change the width/height to a size of your liking (make sure this is slightly smaller than the size of your HTML element so it doesn’t get clipped

Screen Shot 2022-08-01 at 9.14.46 am


Set the progress

Make the ‘0.75’ dynamic to show progress

Screen Shot 2022-08-01 at 9.18.46 am


Change the color & width

The default is a gradient, but in this case, I’ve set the color to a solid green. You can also change the width here.


Josh @ Support Dept
Helping no-code founders get unstuck fast :rocket:save hours, & ship faster with an expert :man_technologist: on-demand

I post daily about no-code and Bubble on Twitter Follow Support Dept on Twitter

9 Likes

@josh24
Hi,

When the page loads the progress circle is loaded, but when i reload the page it vanishes and I get the following error in console.

run.js:8 Bug in custom html:
ReferenceError: ProgressBar is not defined

Any Idea how can i resolve this.

Regards,
Ram

Very useful thank you for posting. This was a great idea and I would love to do it because I’ve suffered in the past with things disappearing. But I cant see how to do it:

I copied the code from the SW3 page, prettified it, saved and uploaded it into Bubble’s file manager. I right clicked and copied the address link and pasted it into the page HTML header but it did not work.

What am I missing?

REPEATING GROUP USEAGE

For anyone wishing to use this in a repeating group, it is necessary to initialise the progress for each cell. I am not a coder so I used Claude.ai and I am not in a position to support this so if you need something ask Claude.ai for yourself. Here’s how I did it…

  1. Hidden HTML
    First I created a HTML element on the page level with width and height of zero and not visible. Into that I pasted this codee
<style>
.progress-container {
  width: 70px;
  height: 70px;
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
}
</style>
<script>
function createProgressBar(container, percentage) {
  // Check if a progress bar already exists
  if (container.querySelector('svg')) {
    console.log('Progress bar already exists, skipping:', container);
    return;
  }

  // Determine color based on percentage
  var progressColor = percentage === 1 ? '#A1EC7E' : '#FBBC04';

  var bar = new ProgressBar.Circle(container, {
    color: '#6C6C6C',
    trailColor: '#E0E0E0',
    strokeWidth: 10,
    trailWidth: 10,
    easing: 'easeInOut',
    duration: 1000,
    text: {
      autoStyleContainer: false,
    },
    from: { color: progressColor, width: 10 },
    to: { color: progressColor, width: 10 },
    step: function(state, circle) {
      circle.path.setAttribute('stroke', state.color);
      circle.path.setAttribute('stroke-width', state.width);
      circle.path.setAttribute('stroke-linecap', 'round');
      var value = Math.round(circle.value() * 100);
      if (value === 0) {
        circle.setText('0%');
      } else {
        circle.setText(value + '%');
      }
    }
  });
  bar.animate(percentage, function() {
    bar.text.style.fontFamily = 'Inter, sans-serif';
    bar.text.style.fontWeight = 'bold';
    bar.text.style.fontSize = '0.9rem';
  });
  return bar;
}

function initProgressBars() {
  console.log('Initializing progress bars...');
  var containers = document.querySelectorAll('.progress-container');
  containers.forEach(function(container) {
    // Clear existing content
    container.innerHTML = '';
    
    var index = container.getAttribute('data-index');
    var rawPercentage = parseFloat(container.getAttribute('data-percentage')) || 0;
    
    // Convert percentage to a decimal between 0 and 1
    var percentage = rawPercentage / 100;
    
    // Ensure percentage is between 0 and 1
    percentage = Math.min(Math.max(percentage, 0), 1);
    
    console.log('Creating progress bar for index:', index, 'with percentage:', percentage);
    createProgressBar(container, percentage);
  });
}

// Only initialize on DOMContentLoaded and with a delay
document.addEventListener('DOMContentLoaded', initProgressBars);
setTimeout(initProgressBars, 1000);

// Additionally, you might want to reinitialize when Bubble updates the DOM
// You'll need to trigger this function when your repeating group updates
function reinitializeProgressBars() {
  console.log('Reinitializing progress bars...');
  initProgressBars();
}

console.log('Script loaded');
</script>
<p>If you see this text, the HTML element is rendering correctly.</p>
  1. HTML in RG
    Inside the RG I put a single HTML of fixed width (in my case 75x75) and I put this code into it (notice the cell index used to initialise for each instance of the progress) and the status I refer to is a value in my dB that ranges between 0 and 100 representing percentage complete (if you used decimal e.g. 0.75 instead of 75% you need to change the code in the hidden HTML to prevent it dividing by 100).

Screenshot 2024-09-16 at 15.00.26

This is the result

As I said, I’m not a coder so if this does not work I can’t help. I just worked with Claude.ai to get this far myself, you can do the same. Good luck and I hope it helps.

1 Like