Animate a progress display without inventing progress

Give a known percentage a small visual entrance, keep its value accessible and distinguish a demonstration from real upload or task progress.

Live example

A little progress goes a long way.

A four-second illustration of a progress bar—not a download, upload or network request.

0%Not started

This is the progress bar at rest. Enable JavaScript to start the simulation.

Show a known value honestly

An animation can draw attention to a percentage you already know. It should not pretend that an upload, payment or background job is finishing. The demo here is explicitly a four-second simulation in your browser; it does not measure a network operation.

For a real task, use a native <progress> element and update it from actual task information. Leave its value unset when the amount of work completed is unknown.

A presentation-only example

This version shows a known 75% value immediately and animates only its decorative fill. The text and accessible value do not spend four seconds reporting a false intermediate result.

<p id="completion-label">Checklist completion: 75%</p>
<div class="completion" role="progressbar"
  aria-labelledby="completion-label"
  aria-valuemin="0" aria-valuemax="100" aria-valuenow="75">
  <span class="completion__fill" aria-hidden="true"></span>
</div>
<button type="button" id="replay-completion" hidden>Replay animation</button>
.completion {
  overflow: hidden;
  block-size: 0.75rem;
  background: #e8e7f2;
}
.completion__fill {
  display: block;
  inline-size: 75%;
  block-size: 100%;
  background: #3b3a7a;
  transform-origin: left center;
}
const fill = document.querySelector('.completion__fill');
const replay = document.querySelector('#replay-completion');
const motion = matchMedia('(prefers-reduced-motion: reduce)');
let animation;

function showProgress() {
  animation?.cancel();
  if (motion.matches || !fill.animate) return;
  animation = fill.animate([
    { transform: 'scaleX(0)' },
    { transform: 'scaleX(1)' },
  ], { duration: 4000, easing: 'ease-out' });
}
function preferenceChanged() {
  if (motion.matches) animation?.cancel();
}
replay.hidden = false;
replay.addEventListener('click', showProgress);
motion.addEventListener('change', preferenceChanged);
showProgress();

function destroyProgress() {
  animation?.cancel();
  replay.removeEventListener('click', showProgress);
  motion.removeEventListener('change', preferenceChanged);
}

Using it on a real page

Load the JavaScript after the markup. The CSS already shows the final value, so no JavaScript or reduced motion leaves a useful static display. Call destroyProgress() when unmounting the component. Swap the colours for your existing design tokens and use the correct transform origin for right-to-left layouts.

If the value changes, update the visible text, aria-valuenow and fill together after validating a finite number in the 0–100 range. Do not communicate success through colour alone, and do not announce every animation frame with a live region.

References: progressbar semantics, Element.animate, and reduced motion.

Original version18 October 2014

Kept here for reference and earlier links. The updated guide above is the recommended starting point; older code may depend on retired services or different software versions.

This is a very easy way of getting a progress bar or similar element to transition from one state/width to another once the website has fully loaded. Useful for enhancing a point, or ability of a company/profile etc if used subtly and effectively.

Demo:

Replay

This works by first of all setting your progress by to 0% via CSS, and then setting up transition rules that any changes will happen over 4 seconds (you can, of course, change this to suit your preference, as well as colours etc.)
The javascript will then set a new width and background colour to the bar which will appear to animate due to the transitions rules mentioned above.

Achievable in simply a few lines of code.

The CSS:


.progress_bar {
  height: 15px;
  background: orange;
  width: 0%;
  -moz-transition: all 4s ease;
  -moz-transition-delay: 1s;
  -webkit-transition: all 4s ease;
  -webkit-transition-delay: 1s;
  transition: all 4s ease;
  transition-delay: 1s;
}

THE HTML:

<div id="progressBar" class="progress_bar"></div>

The Javascript (Vanilla), with a commented line if you want to animate to a percentage output by PHP etc.

  // Assign your element ID to a variable.
  var progress = document.getElementById("progressBar");
  // Pause the animation for 100 so we can animate from 0 to x%
  setTimeout(
    function(){
      progress.style.width = "100%";
      // PHP Version:
      // progress.style.width = <?php echo round($percentage150,2); ?>+"%";
      progress.style.backgroundColor = "green";
    }
  ,100);

The whole code put together, very simple:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Simple Animated CSS/JS Progress Bar</title>
  <link rel="author" href="https://plus.google.com/u/0/116030566292183707588/posts">
  <!-- A simple Animated progress by @guwii -->
</head>
<body>
<style>
.progress_bar {
  height: 10px;
  background: orange;
  width: 0%;
  -moz-transition: all 4s ease;
  -moz-transition-delay: 1s;
  -webkit-transition: all 4s ease;
  -webkit-transition-delay: 1s;
  transition: all 4s ease;
  transition-delay: 1s;
}
</style>

<div id="progressBar" class="progress_bar"></div>

<script>
  // Assign your element ID to a variable.
  var progress = document.getElementById("progressBar");
  // Pause the animation for 100 so we can animate from 0 to x%
  setTimeout(
    function(){
      progress.style.width = "100%";
      // PHP Version:
      // progress.style.width = "<?php echo $progressPercentage; ?>";
      progress.style.backgroundColor = "green";
    }
  ,100);
</script>
</body>
</html>

Keep exploring

A couple more useful notes.