demosthenes.info

Independent notes on CSS, SVG, animation and front-end design.

Articles / Article

Using The HTML5 progress Element

Marking time, progress, and achievement on web pages with HTML5

Aimless browsing on the web is changing to a goal-directed activity: uploading a file, completing a form, working through a lesson plan, watching a video. In response, we need a more complex web UI: not just signifiers for visited links, but ways of measuring progress.

HTML5 addresses that need with the <progress> element. Like all additions to HTML5, the <progress> element comes with supplementary CSS and JavaScript, which I’ll also cover in this article.

progress vs. meter

<progress> is often confused with the <meter> element, so it’s important to distinguish the two:

  • <progress> is used to display development over time of a specific task. The upper bound or limit of the task may be known (“determinate”) – proceeding through a set series of steps in an online exercise, or playing a music track of known length – or unknown (“indeterminate”) such as uploading a file to a server. The maximum value of <progress> may not be known before the element is displayed. For example, the number of steps needed to complete a form may change depending on the user’s answers, while the percentage of a file upload may change due to bandwidth, server activity,and other factors.
  • The conditions for <meter> are different in that its minimum and maximum values must be known beforehand. It would be fair to characterize <meter> as displaying a value that might fluctuate wildly (a volume meter for a music track, for example) between known minimum and maximum values.

Another example: if you were running a fundraising drive on a website, <progress> could be used to show collective donations towards a goal, whereas <meter> might display the number of online visitors to the donations page at any moment, to the maximum capacity of the server.

Another distinction: <progress> can have a minimum value of 0. The minimum value of a <meter> may be any floating-point number, including negative numbers: think of a temperature gauge, for example.

Using The Progress Tag

The <progress> element itself is pretty simple. It’s a standard closed tag:

<progress></progress>

This creates an indeterminate element. Right now, since its value is unknown, <progress> will be animated in a “waiting” or “working” state in browsers that support it:

Adding a max value to the element sets an upper limit, but doesn’t change the animation:

<progress max="100"></progress>

Adding a value sets the current state of progress:

<progress value="10" max="100"></progress>

And changes the appearance of the element:

The progress bar is still animated in most browsers, although much more subtly; naturally, it can also be completely customized with CSS.

It should be noted that progress is inline (“phrasing”) content, and can be mixed in with other content:

<p> We’ve made it to 50% of our funding goal! <span>$0</span><progress value="5012" max="10000" id="funding"></progress><span>$10,000</span>

It might be somewhat surprising to lean that you can’t use <label> with <progress> in valid HTML5, even though we might consider <progress> to be a form element. The fact is that the user will never need to interact with <progress>: it is a “reporting” element, not something that the user would ever need to enter information into, and therefore does not need a <label>.

However, the <output> element can be used with <progress> to show its value. A simple example:

Progress towards our $10,000 pledge drive:
<progress min=0 max=10000 value=5012 id=funding onchange=pledgeUpdate></progress>
<output for=funding id=totalDonations>0</output>

A script to update the total pledge amount and convert the value into a dollar amount with commas:

<script>
var funding = document.getElementById('funding').value;
pledgeUpdate();
function numberWithCommas(x) {
 return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function pledgeUpdate() {
document.getElementById('totalDonations').value = "$"+numberWithCommas(funding);
}
</script>

The result:

Progress towards our $10,000 pledge drive: 0

Of course, it’s easy to set the value of the <progress> element by writing to it directly:

document.getElementById('funding').value = 555;

The appearance of the <progress> element is up to individual browser implementation, and leans heavily on the underlying operating system’s established UI theming; customizing the appearance of the progress bar to make it appear consistent is up to CSS.

Progressing In Style

The <progress> element appears at a default size on a web page, no matter what its maximum value. We can change this by setting an explicit width and height:

progress { width: 500px; height: 12px; }

Firefox and IE obey both declarations, but you’ll find that Webkit/Blink, while it follows the width, ignores responds oddly to height. To get the element to work predictably in Chrome, Safari and Opera we need to reset its appearance:

progress { -webkit-appearance: none; width: 500px; height: 12px; }

You’ll find that this removes all native OS styling on the element in Webkit: the progress bar turns green, and the background a dark grey. To get the equivalent in Firefox, we must take a similar approach:

progress { -webkit-appearance: none; -moz-appearance: none; width: 500px; height: 12px; }

However, the result differs: the progress bar will appear blue in Firefox, for example. To make things perfectly consistent, we have to dig a little deeper into the CSS:

progress {
-webkit-appearance: none; -moz-appearance: none; appearance: none;
width: 500px; height: 12px;
background-color: #888; border: none;
color: green;
}
progress::-webkit-progress-bar, progress::-moz-progress-bar, progress::progress-bar {
background-color: green;
}
progress::-moz-progress-bar {
background-color: green;
}

The result:

You’ll note some deliberate repetition in the CSS above, due to the fact that browsers exhibit eccentricities in HTML5 CSS pseudo-element selectors. For example, Firefox will completely ignore ::-moz-progress-bar if it is grouped with any other selector. For this reason, I strongly suggest you use the selector order shown above when attempting to customize HTML5 progress bars.

Going Vertical

Like range elements, progress elements may be oriented vertically: think of the “thermometers” displayed beside heritage buildings to track donations towards a restoration project. The most consistent way I know to achieve this is to use a CSS transform. Note that it is particularly important to use a box-sizing: border-box CSS reset in this case, otherwise Mozilla and Webkit will size and shape the <progress> element differently:

* { -moz-box-sizing: border-box; box-sizing: border-box; }
progress {
-webkit-appearance: none; -moz-appearance: none; appearance: none;
background: #fff;
width: 300px; height: 20px;
border-radius: 10px; border: 5px double #aaa;
display: block;
-webkit-transform-origin: center left;
-webkit-transform: rotate(-90deg) translateX(-100%);
transform-origin: center left;
transform: rotate(-90deg) translateX(-100%);
margin-left: 1%;
}

This could be customized further:

progress::-webkit-progress-bar { background: #fff; }
progress::-webkit-progress-value {
border-radius: 6px;
background: linear-gradient(90deg, #000,#f00);
}
progress::-ms-fill {
border-radius: 6px;
background: linear-gradient(90deg, #000,#f00);}
progress::-moz-progress-bar {
border-radius: 6px;
background: linear-gradient(90deg, #000,#f00);
}

The result can be seen below:

We gain several insights from this experiment:

  • Firefox uses background to style the background color of <progress> element; Webkit uses ::webkit-progress-bar, and IE uses ::-ms-fill
  • The active measuring portion of the progress bar is referred to as ::-moz-progress-bar in Firefox, and webkit-progress-value in Webkit. In IE10, the bar’s color can only be affected by the color property.

Conclusion

<progress> is a very useful element, although it lacks utility without a means to constantly update its value. I’ll look at that, and various uses of the <progress> element, in future articles.

Photograph by Ming Gullo, licensed under Creative Commons.