elementor4fun-logo
Last update: July 20, 2022

Create a Circular Progress Bar with GSAP

75
50
100
25

With only one element (a Text element), one line of custom CSS and a little bit of JS, we can create our own circular progress bar.

Creating the Circular Bar

- Add a Text element
- Add the class .circular-pbar and set the width+height to 200px, with a border-radius of 50% (so it becomes a...circle!!!)
- Change the layout to flex and center the content, so the text in centered in the middle of the circle.
- set the position to relative
- Select the text and change it into a number, then wrap it in a Span
- Add the class .circular-pbar-counter to that Span

So now, the structure of our element is like this:

 

The Custom CSS

Simply add this CSS, so we can have a nice conic gradient :

.circular-pbar {
    background: conic-gradient(darkred 33%, 0, black)
}

Notice the percent, and the colors we used. Then check the result : we have created a kind of pie chart, with only one line of CSS.

33

Let's cover the pie!

Still with the Text element selected (by his class), change the state to :before or :after

- Set the position to absolute, and set the width and height to calc(100% - 20px)

- Set the border-radius to 50%

- and finally change the background color to the same color of your page or section background

You can see the different steps here :

33
Whateva!
Whateva!

Fix the number

You have noticed that the number just disappeared. It's actually 'under' the overlay.

To fix it, we just have to select the Span element and set the z-index to 1.

CSS Variable

The best and easiest way to animate this kind of custom CSS, it's to use a CSS variable. So we can replace the previous CSS by that one :

.circular-pbar {
    --p: 0;
    background: conic-gradient(grey var(--p, 0), 0, darkred)
}

and with GSAP, this little peace of code will do the trick :

gsap.to(".circular-pbar", {
    "--p": '33%',
    duration: 4,
    ease: 'expo.out'
});

We are animating the --p variable, from 0 to 33%

The result:

33

Animating the number

First we need to read the number and put it in a variable :

const target = document.querySelector(".circular-pbar-counter").textContent + "%";

And then GSAP will animate the content, from 0 to the number we have entered :

gsap.from(".circular-pbar-counter", {
    textContent: 0,
    modifiers: {
        textContent: textContent => textContent.toFixed()
    }
});

Combined with the circular progress bar animation, in a nice timeline + scrollTrigger, here is the full code you can use in a Code Block :

document.addEventListener("DOMContentLoaded", function() {

  const target = document.querySelector(".circular-pbar-counter").textContent + "%";
  
    const tm = gsap.timeline({
        defaults: {
            duration: 4,
            ease: 'expo.out'
        },
        scrollTrigger: {
            trigger: '.circular-pbar',
            toggleActions: "play pause resume reset"
        }
    });

    tm.from(".circular-pbar-counter", {
        textContent: 0,
        modifiers: {
            textContent: textContent => textContent.toFixed()
        }
    });
    tm.to(".circular-pbar", {
        "--p": target,
    }, 0);
  
});

Here is finally how it looks like:

33

The final Code

The previous code will work for one circular progress bar. If we have several of them, we could duplicate the code for each one of them.

Or simply create a loop and optimize the code, so it will work for each one of them, whatever the numbers they have, and whatever their positions in the page (as long as they have the same classes, obviously):

document.addEventListener("DOMContentLoaded", function() {

    const circles = document.querySelectorAll('.circular-pbar');

    circles.forEach((element) => {

        const counter = element.querySelector(".circular-pbar-counter");
        const target = counter.textContent + "%";

        const tm = gsap.timeline({
            defaults: {
                duration: 4,
                ease: 'expo.out'
            },
            scrollTrigger: {
                trigger: element,
                toggleActions: "play pause resume reset"
            }
        });

        tm.from(counter, {
            textContent: 0,
            modifiers: {
                textContent: textContent => textContent.toFixed()
            }
        });

        tm.to(element, {
            "--p": target,
        }, 0);

    })

});
closealign-justifychevron-downcaret-up