Some Innocent Fun With HTML Video and Progress

Some Innocent Fun With HTML Video and Progress

The idea came while watching a mandatory training video on bullying in the workplace. I can just hear High School Geoff LOL-ing about a wimp like me have to watch that thing.

But here we are.

The video UI was actually lovely, but it was the progress bar that really caught my attention – or rather the [progress].value. It was a simple gradient going from green to blue that grew as the video continued playing.

If only I had this advice in high school…

I already know it’s possible to create the same sort gradient on the <progress> element. Pankaj Parashar demonstrated that in a CSS-Tricks post back in 2016.

I really wanted to mock up something similar but haven’t played around with video all that much. I’m also no expert in JavaScript. But how hard can that actually be? I mean, all I want to do is get know how far we are in the video and use that to update the progress value. Right?

My inner bully made so much fun of me that I decided to give it a shot. It’s not the most complicated thing in the world, but I had some fun with it and wanted to share how I set it up in this demo.

The markup

HTML5 all the way, baby!

<figure> <video id="video" src=""></video> <figcaption> <button id="play" aria-label="Play" role="button">►</button> <progress id="progress" max="100" value="0">Progress</progress> </figcaption>

The key line is this:

<progress id="progress" max="100" value="0">Progress</progress>

The max attribute tells us we’re working with 100 as the highest value while the value attribute is starting us out at zero. That makes sense since it allows us to think of the video’s progress in terms of a percentage, where 0% is the start and 100% is the end, and where our initial starting point is 0%.


I’m definitely not going to get deep into the process of styling the <progress> element in CSS. The Pankaj post I linked up earlier already does a phenomenal job of that. The CSS we need to paint a gradient on the progress value looks like this:

/* Fallback stuff */
progress[value] { appearance: none; /* Needed for Safari */ border: none; /* Needed for Firefox */ color: #e52e71; /* Fallback to a solid color */
} /* WebKit styles */
progress[value]::-webkit-progress-value { background-image: linear-gradient( to right, #ff8a00, #e52e71 ); transition: width 1s linear;
} /* Firefox styles */
progress[value]::-moz-progress-bar { background-image: -moz-linear-gradient( to right, #ff8a00, #e52e71 );

The trick is to pay attention to the various nuances that make it cross-browser compatible. Both WebKit and Mozilla browsers have their own particular ways of handling progress elements. That makes the styling a little verbose but, hey, what can you do?

Getting the progress value from a video

I knew there would be some math involved if I wanted to get the current time of the video and display it as a value expressed as a percentage. And if you thought that being a nerd in high school gained me mathematical superpowers, well, sorry to disappoint.

I had to write down an outline of what I thought needed to happen:

  • Get the current time of the video. We have to know where the video is at if we want to display it as the progress value.
  • Get the video duration. Knowing the video’s length will help express the current time as a percent.
  • Calculate the progress value. Again, we’re working in percents. My dusty algebra textbook tells me the formula is part / whole = % / 100. In the context of the video, we can re-write that as currentTime / duration = progress value.

That gives us all the marching orders we need to get started. In fact, we can start creating variables for the elements we need to select and figure out which properties we need to work with to fill in the equation.

// Variables
const progress = document.getElementById( "progress" ); // Properties
// progress.value = The calculated progress value as a percent of 100
// video.currentTime = The current time of the video in seconds
// video.duration = The length of the video in seconds

Not bad, not bad. Now we need to calculate the progress value by plugging those things into our equation.

function progressLoop() { setInterval(function () { document.getElementById("progress").value = Math.round( (video.currentTime / video.duration) * 100 ); });

I’ll admit: I forgot that the equation would result to decimal values. That’s where Math.round() comes into play to update those to the nearest whole integer.

That actually gets the gradient progress bar to animate as the video plays!

I thought I could call this a win and walk away happy. Buuuut, there were a couple of things bugging me. Plus, I was getting errors in the console. No bueno.

Showing the current time

Not a big deal, but certainly a nice-to-have. We can chuck a timer next to the progress bar and count seconds as we go. We already have the data to do it, so all we need is the markup and to hook it up.

Let’s add a wrap the time in a <label> since the <progress> element can have one.

<figure> <video controls id="video" src=""></video> <figcaption> <label id="timer" for="progress" role="timer"></label> <progress id="progress" max="100" value="0">Progress</progress> </figcaption>

Now we can hook it up. We’ll assign it a variable and use innerHTML to print the current value inside the label.

const progress = document.getElementById("progress");
const timer = document.getElementById( "timer" ); function progressLoop() { setInterval(function () { progress.value = Math.round((video.currentTime / video.duration) * 100); timer.innerHTML = Math.round(video.currentTime) + " seconds"; });
} progressLoop();

Hey, that works!

Extra credit would involve converting the timer to display in HH:MM:SS format.

Adding a play button

The fact there there were two UIs going on at the same time sure bugged me. the <video> element has a controls attribute that, when used, shows the video controls, like play, progress, skip, volume, and such. Let’s leave that out.

But that means we need — at the very minimum — to provide a way to start and stop the video. Let’s button that up.

First, add it to the HTML:

<figure> <video id="video" src=""></video> <figcaption> <label id="timer" for="progress" role="timer"></label> <button id="play" aria-label="Play" role="button">►</button> <progress id="progress" max="100" value="0">Progress</progress> </figcaption>

Then, hook it up with a function that toggles the video between play and pause on click.

button = document.getElementById( "play" ); function playPause() { if ( video.paused ) {; button.innerHTML = "❙❙"; } else { video.pause(); button.innerHTML = "►"; }
} button.addEventListener( "click", playPause );
video.addEventListener("play", progressLoop);

Hey, it’s still working!

I know it seems weird to take out the rich set of controls that HTML5 offers right out of the box. I probably wouldn’t do that on a real project, but we’re just playing around here.

Cleaning up my ugly spaghetti code

I really want to thank my buddy Neal Fennimore. He took time to look at this with me and offer advice that not only makes the code more legible, but does a much, much better job defining states…

// States
const PAUSED = 'paused';
const PLAYING = 'playing'; // Initial state
let state = PAUSED;

…doing a proper check for the state before triggering the progress function while listening for the play, pause and click events…

// Animation loop
function progressLoop() { if(state === PLAYING) { progress.value = Math.round( ( video.currentTime / video.duration ) * 100 ); timer.innerHTML = Math.round( video.currentTime ) + ' seconds'; requestAnimationFrame(progressLoop); }
} video.addEventListener('play', onPlay);
video.addEventListener('pause', onPause);
button.addEventListener('click', onClick);

…and even making the animation more performant by replacing setInterval with requestAnimationFrame as you can see highlighted in that same snippet.

Here it is in all its glory!

Oh, and yes: I was working on this while “watching” the training video. And, I aced the quiz at the end, thank you very much. 🤓

Leave a Reply

Your email address will not be published. Required fields are marked *