Animating Progress

“How hard can it be?” (Famous last words.)

The <progress> element has been in browsers for years. (Lea Verou worked on a polyfill back in 2011!) One might expect that after all these years, things are rock solid. Turns out, not much has changed over the years. It’s still a mess and the process of animating the <progress> element is… not at all smooth.

Which browsers

The goal from the outset was to get this working in latest versions of Chrome, Safari, Firefox, Edge, and maybe Internet Explorer.

Progress HTML

The <progress> element has a max attribute and a value attribute. The max attribute must be a value greater than 0. If you don’t set the max value, it defaults to 1. The value is a number between 0 and max value.

For the sake of this article, I won’t get into indeterminate progress bars, which don’t have a value set.

Styling progress

All browsers provide their own styling of the <progress> element but this is easily overwritten. Just start setting some styles!

progress {
    background-color: white;
    border: 1px solid blue;
    /* and so on and so on */
}

(Many tutorials will tell you to set the appearance — and all of its vendor-prefixed equivalents — to none, but that’s not necessary now.)

In Blink and Webkit, you might be wondering why the background colour of the progress bar didn’t actually change. For that, you need to target a pseudo-element.

::-webkit-progress-value {
    background-colour: white;
}

Excellent.

Now we need to style the bar that shows us the progress. The, um, progress bar. Each browser is a bit different in their choice of naming.

::-webkit-progress-value {
  background-color: blue;
}
::-moz-progress-bar {
  background-color: blue;
}
::-ms-fill {
  background-color: blue;
}

We now have something that looks reasonably consistent across browsers.

Changing Progress Value

To animate progress, we need to have the value of the progress element change. For that, we need some JavaScript.

// let's assume only 1 progress element on our page.
var p = document.querySelector('progress'); 
// where val is our new value
p.setAttribute('value', val); 

Let’s Animate!

Edge and Internet Explorer animate the progress bar right out of the box. Perfect! Hashtag shipit.

For Chrome and Safari, we can simply set a transition on the width of the progress bar.

::-webkit-progress-value {
  transition: width 1s;
}

* chef kisses fingers *

Hellfirefox

One would wish/hope/desire that setting a simple transition would also work for Firefox but you’d be wrong. Each browser has certain things that you’re not allowed to do with each of the elements.

In Firefox, you can’t set a transition on the width. You also can’t modify the width. But you can transform it. And you can transition other properties.

I suspect there’s an easier way to do this but this is where my mind went: let’s animate padding-bottom!

Animating padding-bottom means that I could get the height of the progress bar to equal the width of the progress bar. The problem is that it’s going in the wrong direction. For that, I transform the progress bar to rotate it 90 degrees, getting it to animate to the right, just like we want it!

::-moz-progress-bar {
  transition: padding-bottom 1s;
  padding-bottom: var(--value);
  transform-origin: 0 0;
  transform: rotate(-90deg) translateX(-15px);
  padding-left: 15px;
}

We set the transform-origin to 0 0 to put it in the top left corner. (You can also specify top left but 0 0 takes up less bytes.)

The progress bar will now grow up instead of to the right. (When we set the --value custom property, it’ll grow up and to the right.)

If we just did the rotation, the progress bar would sit on top of progress area. We need to shift it down. (Which, since, we’ve rotated 90 degrees, is actually to the left.) We need to use translate to shift the element the height of the progress area. In my example, it’s 15px in height, so shift the element down 15px. If it was 30px in height, you’d shift it down 30px.

Things are starting to come together.

Now that it’s rotated, the height of the bar changes when the value changes. If it’s longer than the height of the progress area, it’ll obscure other things on the page. We can fix this by setting clipping the overflow on the progress element.

progress {
  overflow: hidden;
}

Also due to the rotation, the height of the progress bar (which defaults to the height of the progress area) will affect the width of the rotated bar. Therefore, we need to set the height of the progress bar to 0.

::-moz-progress-bar {
  height: 0;
}

With all that done, we can set the value of the custom property to the same value as the value attribute.

p.style.setProperty('--value', val + '%');

Like butter!

But I want no/different animation in IE and Edge

“Uh, look over there!” * runs away *

There’s no way to turn off or adjust the animation or transition timing for the progress bar in IE or Edge (that I have found).

(It looks like Microsoft actually has a wrapping element and animates that. That wrapping element clips the ::-ms-fill element, preventing any adjustments to animation from happening.)

If you’d like to see all of this wrapped up in a Codepen, head on over to my demo.

Published April 23, 2018
Categorized as HTML and CSS
Short URL: https://snook.ca/s/1120