Faster YouTube Embeds with JavaScript

Alexis Ulrich
Share

We want our visitors to get our content as fast as possible, which means the content needs to be light, and with the minimum number of requests. But we want to keep users on our pages, and be entertained. This is where embedding videos enters the scene.

Videos illustrate our textual content, bring life to it, and are often served by a third party. What more to ask? Well, there’s a hidden price tag: Videos are slow and heavy to download, even when the visitor is not watching them.

One simple video on a page called through an innocuous iframe can add up to 6 HTTP requests and as much as 450kb of content. The solution I’m proposing in this article can reduce those numbers to 1 request and about 50kb per video, along with a few bytes of JavaScript (on top of the jQuery library if you don’t like vanilla flavor).

And you know what? This solution is not new. It was previously proposed by Amit Agarwal in April 2013.

So, What’s the Trick?

In Amit’s solution, the DOM is parsed by JavaScript on document load, and each call to a YouTube video (through a special div, not a regular iframe) is replaced by a thumbnail preview to which is attached the iframe when clicking on it.

This way, we get a nice preview thumbnail still served by a third-party server that’s a fraction of the weight of the full video player. The video player is loaded only when the video is actually viewed.

My Little Added Value

I rewrote Amit’s code in plain JavaScript and with jQuery for those interested. I kept the original comments in the code to keep it as understandable as possible. My version adds a new feature in the HTML5 data parameter that enables you to add any parameter to the YouTube URL to customize your video.

YouTube offers a list of parameters to show and hide controls, branding, and info, and to set the video quality or the starting frame of your video (among other things).

  • controls: set this to 0, and the layer controls are not displayed on the video player.
  • modestbranding: set this to 1, and the YouTube logo disappears from the control bar.
  • rel: set this to 0, and no related videos will be shown when the playback of the initial video ends.
  • showinfo: set this to 0, and the player will not display information like the video title and uploader before the video starts playing.
  • start: set this to a number of seconds, and the player begins playing the video from this time (or rather from the closest keyframe).
  • vq: set this to the wanted video quality, if supported (e.g.: hd720 when high quality is available)

When adding the YouTube iframe on the click event, some parameters are given values, namely autoplay (we want the video to start as soon as its thumbnail is clicked) and autohide (to hide the video progress bar and the player controls when no interaction is detected).

Supported YouTube Thumbnails

Each YouTube video comes with a list of generated images. You can access them through the URL https://img.youtube.com/vi/<youtube-video-id>/<youtube-thumbnail> (where img.youtube.com can even be shortened to i.ytimg.com, if you prefer).

The ones we are interested in are:

  • default.jpg (default version, 120px x 90px)
  • hqdefault.jpg (high quality version, 480px × 360px)
  • mqdefault.jpg (medium quality version, 320px × 180px)
  • sddefault.jpg (standard definition version, 640px × 480px)
  • maxresdefault.jpg (maximum resolution version, 1.280px × 720px)

As an example, see this URL, which you can use to play around with the values to see the different image options.

In the following code, we use the sddefault.jpg thumbnail. Depending on your needs and the capabilities of the user’s screen, this can be replaced by any of the listed supported formats.

The HTML

The HTML code sets the YouTube video ID, styles the video size (width and height), and lists the Youtube URL parameters if needed.

<div class="youtube"
     id="fsrJWUVoXeM" 
     style="width: 500px; height: 281px;">
</div>

<div class="youtube" 
     id="lR4tJr7sMPM" 
     data-params="modestbranding=1&showinfo=0&controls=0&vq=hd720"
     style="width: 640px; height: 360px;">
</div>

The CSS

In the two videos used as examples, the picture ratio is 16:9, which returns the sddefault.jpg thumbnail with horizontal black bands. To hide them when showing this thumbnail, the background-position property is set with the center value, while setting the picture size inlined in the HTML div tag, like style="width:500px;height:281px;". This way, it is possible to show different video sizes on the same page.

The play icon, which tells the users the content is not a mere picture, is added in a layer on top of the thumbnail with an opacity transition to highlight it. We are using a data URI base64-encoded png here (from IconFinder), which saves an HTTP request and works down to IE8.

.youtube {
    background-position: center;
    background-repeat: no-repeat;
    position: relative;
    display: inline-block;
    overflow: hidden;
    transition: all 200ms ease-out;
    cursor: pointer;
}

.youtube .play {
    background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAERklEQVR4nOWbTWhcVRTHb1IJVoxGtNCNdal2JYJReC6GWuO83PM/59yUS3FRFARdFlwYP1CfiojQWt36sRCUurRIdVFXIn41lAoVdRGrG1M01YpKrWjiYmaSl8ybZJL3cd+YA//NLObd3++eO8x79z5jSq5Gw+8kov0AP8vMR5l1BtBZQM4B8ks75wCdZdYZZj5qLZ4hov2Nht9Z9vhKKSIaB/gI4M4w62KeAO6Mte4lYOq20FxrlqqOibhHmeWbvNC9ZfDX1mLae391aN6limO/gwgvAPJbWeAZuSDingdwXTBw7/0IsyaA/Fkh+KqOkD+YNfHej1QKD+y7iVlOhgLvFqFfNJvNGyuBJ+KDAF8MDd0tgS8y64OlgSdJMsysL4cG7SOHkyQZLhTee7+d2R2rAVy/S+Jd7/32ouBHAP4gNNRGQyTHc/84NhqNywZp5rvjjnnvt21aABFeCQ+RLwAf2hQ8s7sv9OCLk6AHNgQvIrvbfzKCD76g/O6cu7lf/iER/aQGgy448pExZmhdegAPhR9sObFWH1gT3lp7DaA/5bkIgJhZPgsNmz02novj+KqeApj1ubwXWe4kdyeznAgNvTpE/HQmvKqOMeuFogTUVQSRno+iaLRLAJF7uIgL9O4ubgL8aWgB7S44mNX+35YpICUiAvS9sBLkq1WzT+NFffl6AuoiApi6NT37h6sWkBIRZGkQ8YtLgyji6e1mBYTqCEBPG2Naz+0BWQgtoGoRgCzEsd9hAN1X5BfnFZASUfrSAFQNsyZ1FJASUVpHiLinDJG8U2cBZYogkrcNs5waBAGdstbeU9zdqpw0gPwwSAI6VUxHyFlDpOcHUUBBIuYNs14aZAE5RVwyzPr3/0EAEY0TyfGNjBWQvwZ +CTSbehfAH29mrID8bET0+0EUkAd8WYDOmqJ3ecsG30yr9wqRfm6Y+a1BEFDEjHfHvWmY9ck6CygHvBVr8Xhtb4ZE5HZA3y8DvBNA1TjnrmXWf+sioMwZX5V/VHXMGGMMoKdDCxCRvRWBdzKzdHEO+EisilbPyopHYqp6S9UCAsz4iojI7hUDAtyXVQgIDd6KnOoaWNkbI6FaPSuZGyMArsi7MZoloB4zviI/Nhr3X95jltwTRQmoIfgisy5ai+me67OI7fE4nrqjrqfK1t0eby0FPRB6oGVlchL3rgnfrq19RKbVBdhV9IOSwJmfmJi4vi/4ThERitwyCxVAFqydshuCX5awhQ9KtmuIWd8IDZED/nXT77rvVVv6sHRKwjYi91poqP7Dr+Y6JJ1VSZIMA3wkPNy6bX+o8Bcm0sXMdwM8Fxo0A3xORPaWBp6uPXsmbxCRD0NDL0dOANhVCXy6iAjMcjbcrMt3RITKwdMVRdFo+y5yvkL4eWZ+zHt/ZVD4dEVRNGotpst+dZZZH8k86lqn2pIvT/eqrNfn2xuyqYPZ8mv7s8pfn/8Pybm4TIjanscAAAAASUVORK5CYII=") no-repeat center center;
    background-size: 64px 64px;
    position: absolute;
    height: 100%;
    width: 100%;
    opacity: .8;
    filter: alpha(opacity=80);
    transition: all 0.2s ease-out;
}

.youtube .play:hover {
    opacity: 1;
    filter: alpha(opacity=100);
}

Vanilla JavaScript Implementation

With no dependency and the fastest implementation, the vanilla JavaScript version uses the smallest DOM ready code I could find.

Browser differences still have to be taken into account, like the lack of support for the getElementsByClassName method in IE8.

'use strict';
function r(f){/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()}
r(function(){
    if (!document.getElementsByClassName) {
        // IE8 support
        var getElementsByClassName = function(node, classname) {
            var a = [];
            var re = new RegExp('(^| )'+classname+'( |$)');
            var els = node.getElementsByTagName("*");
            for(var i=0,j=els.length; i<j; i++)
                if(re.test(els[i].className))a.push(els[i]);
            return a;
        }
        var videos = getElementsByClassName(document.body,"youtube");
    } else {
        var videos = document.getElementsByClassName("youtube");
    }

    var nb_videos = videos.length;
    for (var i=0; i<nb_videos; i++) {
        // Based on the YouTube ID, we can easily find the thumbnail image
        videos[i].style.backgroundImage = 'url(http://i.ytimg.com/vi/' + videos[i].id + '/sddefault.jpg)';

        // Overlay the Play icon to make it look like a video player
        var play = document.createElement("div");
        play.setAttribute("class","play");
        videos[i].appendChild(play);

        videos[i].onclick = function() {
            // Create an iFrame with autoplay set to true
            var iframe = document.createElement("iframe");
            var iframe_url = "https://www.youtube.com/embed/" + this.id + "?autoplay=1&autohide=1";
            if (this.getAttribute("data-params")) iframe_url+='&'+this.getAttribute("data-params");
            iframe.setAttribute("src",iframe_url);
            iframe.setAttribute("frameborder",'0');

            // The height and width of the iFrame should be the same as parent
            iframe.style.width  = this.style.width;
            iframe.style.height = this.style.height;

            // Replace the YouTube thumbnail with YouTube Player
            this.parentNode.replaceChild(iframe, this);
        }
    }
});

And here is a demo of the code in action:

See the Pen YouTube Embed Optimized with JavaScript by SitePoint (@SitePoint) on CodePen.

The jQuery Implementation

While being more expressive to my eyes and with wider browser support, the jQuery implementation comes with the price of the whole jQuery library and slightly slower performance.

"use strict";
$(function() {
    $(".youtube").each(function() {
        // Based on the YouTube ID, we can easily find the thumbnail image
        $(this).css('background-image', 'url(http://i.ytimg.com/vi/' + this.id + '/sddefault.jpg)');
    
        // Overlay the Play icon to make it look like a video player
        $(this).append($('<div/>', {'class': 'play'}));
    
        $(document).delegate('#'+this.id, 'click', function() {
            // Create an iFrame with autoplay set to true
            var iframe_url = "https://www.youtube.com/embed/" + this.id + "?autoplay=1&autohide=1";
            if ($(this).data('params')) iframe_url+='&'+$(this).data('params');
    
            // The height and width of the iFrame should be the same as parent
            var iframe = $('<iframe/>', {'frameborder': '0', 'src': iframe_url, 'width': $(this).width(), 'height': $(this).height() })
    
            // Replace the YouTube thumbnail with YouTube HTML5 Player
            $(this).replaceWith(iframe);
        });
    });
 });

And here is a demo of the jQuery version:

See the Pen YouTube Embeds Optimized (jQuery Edition) by SitePoint (@SitePoint) on CodePen.

The Final Results

Now, let’s talk about what we gain in real-world situations.

This solution was implemented on this page, which is an article that contains 3 embedded YouTube videos. Here are the results:

  • Before implementing that solution, we had 20 HTTP requests and 636.2kb of content downloaded, which took 2.22s (3.59s on load).
  • Once implemented, we went down to 17 requests, 370.7kb, and 1.05s (onload: 733ms).
  • This means 15% fewer requests, and a web page that’s 41% lighter and 52% faster (onload: 80% faster).

The results are quite good even with only one embedded YouTube video, like on this next page, which is our next example. Here are the results for the single-video page:

  • Before implementing the solution we had 20 HTTP requests and 684.4kb of content downloaded, which took 2.13s (2.14s onload).
  • Once implemented, we went down to 17 requests, 322.4kb, and 1.24s (onload: 975ms).
  • This means we ended up with 15% fewer requests, and a web page that’s 53% lighter and 42% faster (onload: 54% faster).

Conclusion

In closing, I think we can all agree that cutting down on page weight from between 40 to 50% is worth that little piece of code, don’t you think?

If you have any thoughts on the code, or would like to improve it, feel free to fork the code on CodePen or share your views in the comments.

This article is also available in French, Spanish and Portuguese.