Progress Circle

Section overview

In this final lesson, we will create a progress circle using CSS conic gradients that visually represents progress toward the weekly goal. You'll learn about conic gradients, CSS custom properties (variables), and how to update styles dynamically with JavaScript.

By the end of this lesson, your running tracker will have a beautiful, animated progress circle that fills up as you get closer to your weekly goal!

Understanding conic gradients

A conic gradient is a CSS gradient that rotates around a center point, creating a circular color transition. We'll use this to create a simple progress circle:

  • The gradient starts at the progress color
  • It transitions to transparent based on the percentage
  • We rotate it to start from the top
  • A background circle provides the track

Adding the progress circle HTML

First, let's update the HTML to add a div for our progress circle. Update the progress section in your HTML file:

<section class="progress">
  <h3>
    Weekly target: <span id="progressTotal">0</span> /
    <span id="target">20</span> miles
  </h3>
  <button onclick="setGoal()">Set goal</button>
  <div class="progressCircleWrapper">
    <!-- add a div for the progress circle -->
    <div class="progressCircle"></div>
  </div>
</section>
  • class="progressCircleWrapper" is a container for centering and sizing.
  • class="progressCircle" is the actual circle that will show the progress.

Adding CSS for the progress circle

Add this CSS to your styles.css file:

.progressCircleWrapper {
  display: flex;
  justify-content: center;
  margin: 2rem 0;
}

.progressCircle {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  background: conic-gradient(
    #0ad9ff 0deg,
    #0ad9ff var(--progress, 0deg),
    #141c22 var(--progress, 0deg),
    #141c22 360deg
  );
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.progressCircle::before {
  content: '';
  position: absolute;
  width: 160px;
  height: 160px;
  border-radius: 50%;
  background: #1c262f;
}
  • .progressCircleWrapper centers the circle on the page.
  • .progressCircle creates a 200px circular div.
  • border-radius: 50% makes it perfectly round.
  • conic-gradient() creates the circular gradient effect.
  • --progress is a CSS custom property (variable) that we'll update using JavaScript.
  • #0ad9ff is the progress color (cyan, matching the button).
  • #141c22 is the background/track color.
  • ::before pseudo-element creates an inner circle to make it look like a ring.

Creating the updateProgress function

Now let's create a simple function to update the progress. Add this to your script.js:

function updateProgress() {
  const progressTotal = parseFloat(document.getElementById('progressTotal').innerText) || 0;
  const target = parseFloat(document.getElementById('target').innerText) || 20;
  const percentage = Math.min((progressTotal / target) * 100, 100);

  const progressCircle = document.querySelector('.progressCircle');
  const degrees = (percentage / 100) * 360;
  progressCircle.style.setProperty('--progress', degrees + 'deg');
}
  • parseFloat() converts the text content to a number.
  • || 0 and || 20 provide default values if elements are empty.
  • (progressTotal / target) * 100 calculates the percentage of goal completed.
  • Math.min(..., 100) ensures the percentage never exceeds 100%.
  • (percentage / 100) * 360 converts percentage to degrees (0-360).
  • setProperty('--progress', degrees + 'deg') updates the CSS custom property.

Understanding the conic gradient

The conic gradient works like this:

background: conic-gradient(
  #0ad9ff 0deg,                    /* Start with progress color at top */
  #0ad9ff var(--progress, 0deg),   /* Keep progress color up to progress point */
  #141c22 var(--progress, 0deg),   /* Switch to background at progress point */
  #141c22 360deg                   /* Background for rest of circle */
);
  • At 0%: --progress = 0deg - all background color
  • At 50%: --progress = 180deg - half progress color, half background
  • At 100%: --progress = 360deg - all progress color

Calling updateProgress

The updateProgress() function does not do anything just yet, we need to call it in three places:

  1. When the page loads (to show initial progress)
  2. After adding a new entry (to update progress)
  3. After setting a new goal (to recalculate progress)

Update your functions as follows:

function calcTotal() {
  const totalValue = entries.reduce(reducer, 0);
  document.getElementById('total').innerText = totalValue.toFixed(1);
  document.getElementById('progressTotal').innerText = totalValue.toFixed(1);
  updateProgress(); // Update circle when total changes
}

function setGoal() {
  const goalInput = prompt('Enter your weekly goal in miles:');
  if (goalInput && !isNaN(goalInput) && goalInput > 0) {
    const goal = Number(goalInput);
    localStorage.setItem('runningGoal', goal);
    document.getElementById('target').innerText = goal;
    updateProgress(); // Update circle when goal changes
  }
}

// At the end of script.js
loadGoal();
updateProgress(); // Show initial progress on page load

Adding a percentage display (optional)

You can add text inside the circle to show the percentage. Update your HTML:

<div class="progressCircleWrapper">
  <div class="progressCircle">
    <span class="progressText" id="progressPercent">0%</span>
  </div>
</div>

Then update the CSS:

.progressText {
  position: relative;
  z-index: 1;
  font-size: 2.4rem;
  font-weight: 600;
  color: #0ad9ff;
}

And update the JavaScript function:

function updateProgress() {
  const progressTotal = parseFloat(document.getElementById('progressTotal').innerText) || 0;
  const target = parseFloat(document.getElementById('target').innerText) || 20;
  const percentage = Math.min((progressTotal / target) * 100, 100);

  const progressCircle = document.querySelector('.progressCircle');
  const degrees = (percentage / 100) * 360;
  progressCircle.style.setProperty('--progress', degrees + 'deg');

  // Update percentage text if it exists
  const progressPercent = document.getElementById('progressPercent');
  if (progressPercent) {
    progressPercent.innerText = Math.round(percentage) + '%';
  }
}

Complete updated code

Here's the complete updated code for this lesson:

CSS (add to styles.css):

.progressCircleWrapper {
  display: flex;
  justify-content: center;
  margin: 2rem 0;
}

.progressCircle {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  background: conic-gradient(
    #0ad9ff 0deg,
    #0ad9ff var(--progress, 0deg),
    #141c22 var(--progress, 0deg),
    #141c22 360deg
  );
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  transition: background 0.5s ease-in-out;
}

.progressCircle::before {
  content: '';
  position: absolute;
  width: 160px;
  height: 160px;
  border-radius: 50%;
  background: #1c262f;
}

.progressText {
  position: relative;
  z-index: 1;
  font-size: 2.4rem;
  font-weight: 600;
  color: #0ad9ff;
}

JavaScript (add to script.js):

function updateProgress() {
  const progressTotal = parseFloat(document.getElementById('progressTotal').innerText) || 0;
  const target = parseFloat(document.getElementById('target').innerText) || 20;
  const percentage = Math.min((progressTotal / target) * 100, 100);

  const progressCircle = document.querySelector('.progressCircle');
  const degrees = (percentage / 100) * 360;
  progressCircle.style.setProperty('--progress', degrees + 'deg');
}

Make sure to call updateProgress() in calcTotal(), setGoal(), and on page load as shown earlier.

Summary

In this final lesson, you've learned how to:

  • Use conic gradients: Created a circular gradient using CSS conic-gradient().
  • Use CSS custom properties: Used CSS variables (--progress) to update styles dynamically.
  • Calculate percentages: Converted distance/goal ratios to percentages and degrees.
  • Update styles with JavaScript: Used setProperty() to update CSS variables.
  • Create visual progress indicators: Built a simple, effective progress circle.

Congratulations! 🎉

Your running tracker is now complete! You've built a complete application that:

  • Accepts running entries
  • Displays the last 7 days of runs
  • Calculates total and average distances
  • Uses local storage
  • Tracks weekly highs
  • Sets and saves weekly goals
  • Visualizes progress with an animated circle

You've learned essential web development skills including HTML structure, CSS styling, JavaScript event handling, array methods, date manipulation, localStorage, and CSS gradients.

fireworks image