You are here


HTML5 Video & Skinning Tutorial Part 2: Play Buttons

nicholas.davison | Digitaria
By Nicholas Davison , Director of Web Development
Jun 22, 2010

Yesterday we covered the core HTML5 <video> element and its basic implementation.

Today we will be looking at everything involved with creating a separate, skinned play button: controlling the video and tracking its current state in order to update the video correctly.

We will be breaking each of the tutorials in to two parts:

First we will cover the concepts. This will let us quickly list just the specific HTML5 <video> element attributes, events and so on for those of you who want to quickly get building your own way.

Next we will cover a full implementation, showing one way of using everything we have just discussed. The implementations will be built off jQuery to avoid spending a whole bunch of time writing trivial JavaScript. jQuery also lets us focus on the techniques used to get a full working version that’s flexible enough for generic usage and how to optimize it.


Controlling The <video> Element

To play a video via JavaScript, get the element and send the play() method:


To pause a video via JavaScript, get the element and send the pause() method:


Monitoring The <video> Element

Most play buttons tend to actually be a combined play/pause button. If the video is not currently playing, it shows an inactive play icon and clicking it makes the video play. If the video is already playing, it shows an active play icon or a pause icon and clicking it pauses the video.

To find out whether a video is currently playing, we can check the paused property:

if (document.getElementById('myVideo').paused) {

      /* Video is paused */

} else {

      /* Video is playing */


If we were the only ones controlling whether a video is playing or paused, that would be plenty for us. However, a lot of other events cause videos to start and stop:

  • Auto play will start a video without our control.
  • A video reaching the end will stop a video without our doing anything.
  • A video running out of preloaded data may cause it to pause.
  • The user right clicking and choosing play/pause will change its state.
  • The user right clicking in Firefox, turning on the default controls and interacting them will also change its state.

All of the above are situations where a video’s play/pause state can change without our awareness. To monitor for them – and to change our play/pause button accordingly – we need to listen for the play and pause events:

document.getElementById('myVideo').onplay=function() {

      /* Video has started playing */


document.getElementById('myVideo').onpause=function() {

      /* Video has been paused */




The <video> Element

We start by creating a basic video element with no controls, no autoplay, options for H.264 and Ogg files and a default text message for non HTML5 <video> users.

<video id="myvideo">

      <source src="//" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"' />

      <source src="//" type='video/ogg; codecs="theora, vorbis"' onerror='alert("We are sorry: Your browser does not support any of the video formats available at this time.");' />

      This video requires a browser that supports HTML5 video.


The Controls

We now create a div to contain all of our video controls – the buttons we are starting with today go in to it and the progress bar we will be creating later in the week will go in there as well.

Notice it has a rel attribute that matches the id from the video. We are going to use this in the JavaScript, later, to let us have multiple videos and multiple control bars on the same page.

The unordered list of class “video_buttons” is currently just a single item but we are using a list as more buttons will be getting added throughout the week.

<div class="video_controls" rel="myvideo">

      <ul class="video_buttons">

            <li class="video_play"><a href="//">Play</a></li>




We start with defining a simple 640x480 video player and matching width controls:

video {

      background-color: black;

      color: white;

      height: 480px;

      width: 640px;


.video_controls {

      width: 640px;


The buttons themselves are set up as a 50px high bar and set up to use a CSS sprite for all of the different states (off, hover, active, active hover).

For this example, we are only showing the play button’s styling. The other buttons, as we add them throughout the week, will work exactly the same way, each needing just the four single lines at the end as everything else is common to “.video_buttons a”:

.video_buttons {

      background: url(controls_bg.gif) top left repeat-x;

      height: 50px;

      margin: 0;

      overflow: visible;

      padding: 0 0 0 120px;


.video_buttons li {

      clear: none;

      float: left;

      list-style: none;

      margin: 0;

      padding: 0 20px;


.video_buttons a {

      background-image: url(controls.png);

      background-repeat: no-repeat;

      display: block;

      height: 50px;

      outline: none;

      overflow: hidden;

      text-indent: -9999px;

      width: 62px;


.video_play a { background-position: -262px 0px }

      .video_play a:hover { background-position: -262px -50px }

      .video_play { background-position: -262px -100px }

      .video_play { background-position: -262px -150px }


Helper Functions

We start by creating a pair of helper functions:

The first allows us to pass a .video_controls element, read its rel and pass back the video with an id that matches it (or all videos with no ids if there was no rel).

The second does the reverse: We pass it a video element. If it has no id, we find the first .video_controls on the page. If it does have an id, we return an .video_controls with a matching rel.

Although we are only using the logic in each of them once, today, we know we are likely to need the same code for other buttons in the future and so it makes sense to encapsulate them in their own functions now.

function findVideoForElement(el) {

      var parentControls=$(el).parents('.video_controls[rel]');

      if (parentControls.length==0) {

            return $('video[id=]');

      } else {

            return $('#'+parentControls.attr('rel'));



function findControlsForVideo(el) {

      if ($(el).attr('id')=="") {

            return $('.video_controls:first');

      } else {

            return $('.video_controls[rel='+$(el).attr('id')+']');



Storing Local Variables vs. Searching Every Time

With jQuery, it’s very easy to write code like this:

$('some selector').bind('someevent', function() {

      $('some secondary selector').someMethods();


The problem with this is that every time the event fires, jQuery has to perform that secondary selector search again. In cases where the secondary selector never changes, that’s wasteful.

Later in the week, we are going to get in to events firing many times per second. Having jQuery repeating searches it doesn’t need to perform moves from wasteful to outright dangerous.

Because of that, we will be using the following alternative a lot:

$('some selector').each(function() {

      var _secondarySearch=$('some secondary selector');

      $(this).bind('someevent', function() {




In the latter code, the secondary search is only performed once, at initialization time and then its results stored to be repeatedly used every time the someevent event fires – much more efficient.

Yes, the former code is shorter and a little easier to read but video already taxes a lot of browsers – the less we can add to that, the better.

The Play Button Initialization Function

We start by creating an initialization function that finds all of the play buttons.

Using the above approach, we pre search for their matching videos and store them locally before we get in to any event binding as we only need to do it once, not every time the button is clicked.

function initVideoPlayButtons() {

      $('.video_play a').each(function() {

            var $_video=findVideoForElement(this);


/* Bind The Click Events */


/* Set Initial State */



      /* Bind Video State Changes */


Next, we replace that /* Bind The Click Events */ with a click binding for each of those play buttons. Within it, we stop the link from running then check to see if the video(s) are paused or playing, telling them to do the opposite.

$(this).click(function(e) {


      $_video.each(function(index, _video) {

            if (_video.paused) {


            } else {





We now have our play buttons starting and stopping the videos their parent .video_controls tell them to control.

Next, right after the click binding (where we have /* Set Initial State */), we need to set the play buttons’ initial active (playing)/inactive (paused) states. We let the browser tell us what the video is already doing, rather than assuming it starts paused, incase autoplay has been set or the user has managed to start it playing before the JavaScript manages to initialize:

if ($_video.length>0) {

      if ($_video.get(0).paused) {


      } else {




Finally, we want to monitor when the video state changes from play-to-pause and pause-to-play. Again, we are listening to the video element and letting the browser tell us, rather than assuming we know state in the click event, because there are many other ways this state can change outside of our control.

This time around, we seek out the play button for the matching controls and keep it outside the bound events, avoiding repeated searching. Then we listen for play and pause methods, adding or removing the “active” class to match.

$('video').each(function(index, _video) {

      var $_controls=findControlsForVideo(_video);

      var $_play_button=$_controls.find('.video_play a');


      $(_video).bind('play', function() {


      }).bind('pause', function() {




Simple Document Ready

With all of the rest of the code written, all we need to do is call the initialization function from within $(document).ready():

$(document).ready(function() {



Note: We haven’t included sample video files in order to keep download sizes small. You can test with your own .ogg and .mp4 files or download ones you wish to use from the web.


Tomorrow we will be looking at fast forward and rewind within HTML5 <video> along with some of the limitations imposed by current browser implementations.


The W3C spec for HTML5 video is available at:

However, we would recommend the whatwg version as its linked table of contents makes it slightly more usable (even if the text is just as dense):