HTML 5 Video - Part 4: Styling

Welcome to part 4 of my series of tutorials on implementing a video player with just HTML and Javascript.

If you missed the previous weeks tutorials, here they are:

As usual, all of the code can be found on GitHub. Each tag corresponds to one part of the tutorial.

Today, we will be adding some CSS to the controls that we built in previous weeks so that we can make it look more like a player.

Seek Bar

Previously, we used buttons to fast-forward or backwards the time of the video. But most players, normally have some kind of bar that can be used to find the part of the video to be played.

There are multiple ways of implementing this with different levels of complexities. I will leave you to research all of these but here, I will use the range input.

Although the easiest to implement, the difficulty comes later when applying CSS to it as every browser has various compatibility issues.

Lets start with the HTML. I have deleted the +10/-10 buttons that we had and put in the input:

<input id="progress_bar" type="range" min='0' max='100' value='0'/>

As you can see from the minimum and maximum, we will be dealing with percentages to show the progress of the video.

We can update the progress in the timechange event listener that we implemented last week:

player.ontimeupdate = function() {
    var percentage = Math.floor((player.currentTime / player.duration) * 100);
    document.getElementById('progress_bar').value = percentage;

As you can see, I first get the percentage that is completed and then update the value of the progress bar. Also note, that like the video element, you must get the actual DOM object. That means using getElementById or, if you use jQuery, use get(0) to get the DOM.

So when we run this, you will see that the bar does update as time goes on. But what about if we want to seek ourselves?

Well, we need to do the inverse and update player.currentTime when the value of the bar changes:

$('#progress_bar').on('input', function() {
    var time = Math.floor((this.value / 100) * player.duration);
    player.currentTime = time;

This reverses the percentage back into seconds and sets the player to that time. The input even also only runs when the user changes it otherwise it would fire every time it is changed by the timeupdate event.

One last thing whilst we are are here. I also want to display the time elapsed and the total time. If you remember from last week, the player deals with seconds and milliseconds, but we want it more human readable.

I found this solution on StackOverflow (be sure to head over there and give the answer an up-vote with you have an account with privilege to do so).

Two functions:

  var formatTime = function(time) {
      var minutes = Math.floor(time / 60);
      var seconds =  Math.floor(time - minutes * 60);
      return  str_pad_left(minutes, '0', 2) + ':' + str_pad_left(seconds, '0', 2); 

  var str_pad_left = function(string, pad, length) {
      return (new Array(length + 1).join(pad) + string).slice(-length);

str_pad_left is used to add a leading 0 when the the number is less than 10. formatTime converts seconds into as string of minutes:seconds. I then just use this function in all the places that I want to to display the time.

For example:

Volume Bar

For the volume bar, we will use the range input again. So, I have gotten rid of the buttons and replaced it with a bar:

<div id="volume_container">
        <input id="volume_bar" type="range" min='0' max='1' value='0' step='0.1' />

First of all, you can see that I have put this in a container. I want to hover over the container to reveal the controls. Clicking it, will also be what Mutes it.

The input itself reflects how the volume works on the player: the values are between 0 and 1. The step attribute ensures that each progression is at intervals of 0.1 (which is how we had the buttons working last week).

Now for the JavaScript. First, I have moved the Mute code to be attached to the click event of the Volume word:

$('#volume_container > p').click(function(e) {
    player.muted = !player.muted;
    if (!player.muted) {
        $(this).css('text-decoration', 'none');
    } else {
        $(this).css('text-decoration', 'line-through');
        $('#volume_bar').attr('disabled', 1);

There is a little CSS to show that it is muted and the volume input is disabled. Otherwise, this is the same as it was before.

Then, like the seek bar, we want to change the volume when it is changed:

$('#volume_bar').on('input', function() {
     player.volume = this.value;

As I said earlier, the range of the input is compatible with player.volume so no further code is required to "translate" it and it is directly applied.

I want to have it set to the correct volume when the video loads. So, in the loadedmetadata event, I add the line:

document.getElementById('volume_bar').value = player.volume;

When we mute or unmute, I want to set the volume bar to have 0 or the player's volume. This will also update it if the volume was changed by some other means later on:

player.onvolumechange = function() {
    if (player.muted) {
        document.getElementById('volume_bar').value = 0;
    } else {
        document.getElementById('volume_bar').value = player.volume;

Finally, I am also going to jump ahead a little and add some CSS to hide the bar unless hovered over:

#volume_container {
    display: inline-block;
#volume_container:hover {
    cursor: pointer;
#volume_container span {
    display: none;

So this hides the inner span and on hover, shows the pointer cursor. Now some JavaScript:

$('#volume_container').hover(function(e) {

This reveals the span only when the container is hovered over.

Button Symbols

Instead of having the words "play", "pause" and "restart", I want to have the symbols instead. This could be done a number of ways, but there is one way using just HTML.

So, in the code, I have replaced "Play" with:


Which appears as: ►.

"Pause" can be replaced with:


Which looks like: ❚❚

And for restart, I have used:


Which looks like: ↻.

Like I said, you can find a way to use real images for your icons, but this is a cheap and effective way!


So, putting this altogether and getting rid of the labels from last week, I get:

Player before CSS

As you can see, it is starting to look like a proper player now. But now, I can apply some CSS to it! By the way, I my CSS will be very rigid and will not at all be responsive to different screen sizes.

First, I want the volume bar, to appear vertically and be on top of the video:

#volume_container span {
    display: none;
    position: absolute;
    z-index: 2;
    height: 30px;
    width: 150px;
    top: 360px;
    left: 533px;
    background-color: #666;
    -ms-transform: rotate(270deg); /* IE 9 */
    -webkit-transform: rotate(270deg); /* Chrome, Safari, Opera */
    transform: rotate(270deg);

The z-index places it above the video. I use transform to turn the span vertically. As usual with these things, you need to have the browser specific versions of transform so -ms-transform and -webkit-transform deal with this.

Changing the background colour and colour of all the text should be very straightforward for you. The buttons are styled like this:

#play, #seek_beginning {
    border: 0;
    width: 30px;
    height: 30px;
    margin-left: 5px;
    margin-right: 5px;
    background-color: #CCC;
    color: #FFF
#play:hover, #seek_beginning:hover {
    background-color: #666;

This flattens the button and changes their colours as well as well changing the colour with hovered over.

The hardest part to style is the range input that I have used for "seeking". I used this blog post to help with this challenge. As I said, there are other ways of implementing the bar that may be easier to apply CSS to and suit your needs (but require way more JavaScript to be manually built).

You can see the full CSS on GitHub as well as on the blog post. But I will briefly touch on it here.

The main issue is that every browser has different compatibilities and so you have to implement the CSS about 3 times.

To start with:

    -webkit-appearance: none;
    /* fix for FF unable to apply focus style bug  */
    border: 1px solid #CCC; 
    /*required for proper track sizing in FF*/
    width: 400px;
    height: 5px;
 #progress_bar:focus {
      outline: none;

This removes the default style that the browsers give.

So there are 2 parts: the runnable-track, which is the line itself; and the thumb which is the things that shows the position on the track and is draggable.

So this is how to style it on Chrome:

#progress_bar::-webkit-slider-runnable-track {
    width: 400px;
    height: 5px;
    background: #ccc;
    border: none;
    border-radius: 3px;
#progress_bar::-webkit-slider-thumb {
     -webkit-appearance: none;
     border: none;
     height: 16px;
     width: 16px;
     border-radius: 50%;
     background: #CCC;
     margin-top: -4px;
 #progress_bar::-webkit-slider-thumb:hover {
     background: #000;
 #progress_bar:focus::-webkit-slider-runnable-track {
     background: #CCC;

The result of all this is:

After CSS

If you look at the full code, you will see the Firefox and Microsoft equivilent CSS that will achieve the same effect. You should be able to follow what is going on.

There is one more thing worth a mention however. With Mozilla, you can mark the progress of the bar with a different colour:

#progress_bar::-moz-range-progress {
     background: #222;

It is a shame that you can not do this with all browsers, but I guess this is not what the input was supposed to be used for. If you really need this feature, I would recommend using a different method as I said earlier.

You can also do this on IE and Edge. But there are two different CSS selectors:

#progress_bar::-ms-fill-lower {
    background: #222;
    border-radius: 10px;
#progress_bar::-ms-fill-upper {
    background: #ccc;
    border-radius: 10px;

fill-upper is obviously for the right hand side of the thumb and fill-lower the left hand side.


And that is it for this week. I only have snippets of code in the post, you can find all of the CSS and working player on GitHub.

That concludes this little mini-series too! I hope this, at the very least, has been a good place to start. As I have said previously, I have covered only the basics and missed a lot out. If the posts themselves were not useful, I hope the example on GitHub gets you started.

Good luck exploring the post-Flash era of playing media on the Web!

© 2012-2022