Some Innocent Fun With HTML Video and Progress
Publikováno: 30.4.2020
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… Read article “Some Innocent Fun With HTML Video and Progress”
The post Some Innocent Fun With HTML Video and Progress appeared first on CSS-Tricks.
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.
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="http://html5videoformatconverter.com/data/images/happyfit2.mp4"></video>
<figcaption>
<button id="play" aria-label="Play" role="button">►</button>
<progress id="progress" max="100" value="0">Progress</progress>
</figcaotion>
</figure>
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%.
Styling
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 ascurrentTime / 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="http://html5videoformatconverter.com/data/images/happyfit2.mp4"></video>
<figcaption>
<label id="timer" for="progress" role="timer"></label>
<progress id="progress" max="100" value="0">Progress</progress>
</figcaotion>
</figure>
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="http://html5videoformatconverter.com/data/images/happyfit2.mp4"></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>
</figcaotion>
</figure>
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 ) {
video.play();
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. 🤓
The post Some Innocent Fun With HTML Video and Progress appeared first on CSS-Tricks.