Basic interactive video with HTML5 and vanilla JavaScript

I implemented the following interactive video using vanilla JavaScript and HTML5 with with the excuse to play with the <video> element and the HTMLMediaElement API.

The general idea of this interactive video is to play a video of introduction and present the users with options to control their experience. Click the play icon to see the demo:

Please, change your phone to landscape view!

Note: this demo is just a basic example of an interactive video. If you want a more complete example, you can read Mozilla’s documentation on how to create cross-browsing video players. You can also have a look at the interactive video my friend and colleague Jack Nyaga implemented for Sucuri, it’s pretty awesome!

Note 2: If you’re better at reading code than blog posts, here’s the link to the GitHub gist for this demo.

Intro: Controlling HTML5 videos with JavaScript

Thanks to the <video> element introduced in HTML5, we can embed a video to a web page with as little code as this:

<video controls>
    <source src="/videos/video.mp4" type="video/mp4">
</video>

Without the use of any third-party video player!

The HTMLMediaElement API

The HTMLMediaElement API also allows us to target those elements and control them via JavaScript, which opens our world to many possibilities. Such as creating interactive videos like the one at the beginning of this post.

For example, in order to play a video using JavaScript, all you need to do is target the element and call the load() & play() functions.

var video = document.getElementsByTagName("video")[0]; // first video on the page
video.load();
video.play();

The HTMLMediaElement API has many properties and methods available for developers. I used the following list in the previous demo (some of them only while developing):

  • currentTime to get the current playback in seconds
  • duration to get the length of the video in seconds
  • ended to get information on whether the video has finished playing
  • loop to repeat a video (very useful for interactive menus)
  • playbackRate to set the rate at which a video is played (time saver for development)
  • preload to start the loading of a video (indicating it should be ready to be played)
  • load to set a video to start from the beginning and to start loading the best source
  • play to play a video

How the interactive video works

Note: The following code is a modified version of my running demo.

HTML markup

<div class="videos">
    <div class="active initial closing">
        <video autoplay="autoplay" loop>
            <source src="/path/video.mp4" type="video/mp4">
        </video>
    </div>
    <div class="category hide">
        <video>
            <source src="/videos/video-dog.mp4" type="video/mp4">
        </video>
    </div>
    <!-- More videos here -->
    <div class="play"><!-- SVG image here --></div>
    <div class="menu hide">
        <div class="menu-wrapper">
            <div class="initial hide">
                <h3>Content for first menu</h1>
            </div>
            <div class="closing hide">
                <h3>Content for last menu</h3>
            </div>
            <div class="options hide">
                <ul>
                    <li><a id="opt-1">Option 1</a></li>
                    <li><a id="opt-1">Option 2</a></li>
                    <li><a id="opt-3">Option 3</a></li>
                </ul>
            </div>
        </div>
    </div>
</div>

There’s three main sections inside the .videos container:

  1. The videos identified with the classes .initial.closing & .category. Meaning that adding new videos a “category” is as simple as using the right class on their container element.
  2. The .play container to host an svg play button (courtesy of Pixabay).
  3. The .menu container that will host as many menus as necessary. In this case I have an .initial and a .closing menu, with a shared .options container.

JavaScript code

The first thing I did was to declare the variables I was going to be using more than once.

var videosContainer = document.getElementsByClassName("videos")[0];
var menu = document.getElementsByClassName("menu")[0];
var menuOptions = menu.getElementsByClassName("options")[0];

Next I created a function to handle the loading and the playing of a given video, and an optional argument to know whether to loop the video or not. I came up with the following:

function playVideo(videoContainer, loop=false) {
    var lastVideoContainer = videosContainer.getElementsByClassName("active")[0];
    lastVideoContainer.classList.remove("active");
    lastVideoContainer.style.display = "none";

    videoContainer.style.display = "block";
    videoContainer.classList.add("active");

    var video = videoContainer.getElementsByTagName("video")[0];
    video.preload = "auto";
    video.load();
    video.play();
    video.loop = loop;

    return video;
}

Besides the loading and playing functionality of this function, is important to note its use of the .active class to identify the playing video. At the beginning of the function we retrieve the last element with said class, remove the class and then hide the element. Right after that, the function look for the videoContainer element, add the .active class to it and show it as a block element.

For my demo I also needed to play multiple videos belonging to the same category. So I created the following function:

function showVideos(index, videos) {
    if (index < (videos.length - 1)) {
        hasNextVideo = true;
    } else {
        hasNextVideo = false;
    }

    // If videos are playing, we don't need the menus
    menu.style.display = "none";

    var video = playVideo(videos[index]);

    video.addEventListener("timeupdate", function() {
        var currentTime = (this.currentTime / this.duration) * 100;

        // Helps to smooth the transition between videos
        if (hasNextVideo && currentTime > 70) {
            nextVideo = videos[index + 1];
            nextVideoTag = nextVideo.getElementsByTagName("video")[0];
            nextVideoTag.preload = "auto";
        }
    })

    video.onended = function() {
        if (hasNextVideo) {
            showVideos(index+1, videos);
        } else {
            playClosingMenu();
        }
    }
}

After this, I set a few events handlers to control the flow of the experience through the menus and the options.

Here’s what I did:

var playButton = document.getElementsByClassName("play")[0];
playButton.onclick = function() {
    playButton.style.display = "none";
    playInitialMenu();
}

var option1 = document.getElementById("option-1");
option1Link.onclick = function() {
    showVideos(0, videosContainer.getElementsByClassName("category-opt-1"));
}

// handlers for more options here...

function playInitialMenu() {
    playVideo(videosContainer.getElementsByClassName("initial")[0], loop=true);

    setTimeout (function () {
        menu.style.display = "block";
        menu.getElementsByClassName("initial")[0].style.display = "block";
        menuOptions.style.display = "block";
    }, 1000);
};

There’s also a playClosingMenu() which is almost exactly as the playInitialMenu(), with the difference that the playClosingMenu() function hides the initial menu and displays the closing menu.

And that’s all the code necessary for this demo… Plus a few lines of CSS which I’m not going to address here because I think it goes out of the scope. However, all the code is available on this GitHub gist.

Note: I took some inspiration from this blog post on Sitepoint.

Thanks for reading! I’m on Twitter in case you want to chat!