Better YouTube videos embedding & on demand loading in Jekyll

How improve website performances, YouTube videos embedding and on demand loading

Today many bloggers embed YouTube videos on their websites, we all do that. The problem is many don’t know or don’t care that embedding just a simple video adds additional weight to your page and can noticeably decrease loading performances. Your website performances are not anymore a choice, it’s a must. Google started ranking websites based on page loading time, and I strongly agree with this decision.

As I was building this website (bojan.io) I had set the goal to make its loading as fast as possible. One of the problems to that goal was YouTube video embedding. I found many similiar solutions for this particular problem, but I did it in a much better way where I leveraged less on JavaScript and more on Jekyll generator.

Let me demonstrate the problem and my solution which you can integrate on your Jekyll website right away.

Note: This method is not specifically only for Jekyll, with some modifications it can be easily applied to other non-Jekyll websites.

Page weight caused by YouTube

Every time you embed a video on your page, you paste this iframe somewhere on your page:

<iframe width="853" height="480" src="<YouTube_Video_URL_HERE>" frameborder="0" allowfullscreen><iframe>

And behind your eyes you also do this to your page:

YouTube embed video network calls and data size
YouTube embed video network calls and data size

That’s 11 new HTTP requests with additional 540KB of data. You have loaded 540KB, decreased loading performances, increased loading time which leads to poor UX for no reason. And that’s a problem! Also YouTube iframes are shared with fixed dimensions, that is another problem.

I will try in this tutorial to narrow the problem to 1 HTTP request with drastically less download of data and responsive iframe.

Public YouTube video thumbnails

I started googling around and found an interesting discussion on Stack Overflow about YouTube videos. Apparently every video has publicly accessible thumbnails images through YouTube Data API, predictable formatted in different sizes and image formats without requesting an API key. Bingo! I must admit I was surprised to find WebP1 format of the thumbnails. (Thumb up Google!) So to understand better which images we can use I have tested all of them and made the following list with complete details.

Note: YouTube is serving thumbnails from 2 servers:

//img.youtube.com
//i.ytimg.com

Examples are with //i.ytimg.com server just because it’s shorter, no other particular reason. You can use both.

  • Player Background Thumbnail (480x360):
WebP
//i.ytimg.com/vi_webp/<YouTube_Video_ID_HERE>/0.webp

JPG
//i.ytimg.com/vi/<YouTube_Video_ID_HERE>/0.jpg
  • Video frames thumbnails (120x90)
WebP:
Start: //i.ytimg.com/vi_webp/<YouTube_Video_ID_HERE>/1.webp
Middle: //i.ytimg.com/vi_webp/<YouTube_Video_ID_HERE>/2.webp
End: //i.ytimg.com/vi_webp/<YouTube_Video_ID_HERE>/3.webp

JPG:
Start: //i.ytimg.com/vi/<YouTube_Video_ID_HERE>/1.jpg
Middle: //i.ytimg.com/vi/<YouTube_Video_ID_HERE>/2.jpg
End: //i.ytimg.com/vi/<YouTube_Video_ID_HERE>/3.jpg
  • Lowest quality thumbnail (120x90)
WebP
//i.ytimg.com/vi_webp/<YouTube_Video_ID_HERE>/default.webp

JPG
//i.ytimg.com/vi/<YouTube_Video_ID_HERE>/default.jpg
  • Medium quality thumbnail (320x180)
WebP
//i.ytimg.com/vi_webp/<YouTube_Video_ID_HERE>/mqdefault.webp

JPG
//i.ytimg.com/vi/<YouTube_Video_ID_HERE>/mqdefault.jpg
  • High quality thumbnail (480x360)
WebP
//i.ytimg.com/vi_webp/<YouTube_Video_ID_HERE>/hqdefault.webp

JPG
//i.ytimg.com/vi/<YouTube_Video_ID_HERE>/hqdefault.jpg
  • Standard quality thumbnail (640x480)
WebP
//i.ytimg.com/vi_webp/<YouTube_Video_ID_HERE>/sddefault.webp

JPG
//i.ytimg.com/vi/<YouTube_Video_ID_HERE>/sddefault.jpg
  • Unscaled thumbnail resolution
WebP
//i.ytimg.com/vi_webp/<YouTube_Video_ID_HERE>/maxresdefault.webp

JPG
//i.ytimg.com/vi/<YouTube_Video_ID_HERE>/maxresdefault.jpg

Important!

  • WebP format may not be available for older videos. And from my own research that is valid for all videos older then 2015.
  • Older videos doesn’t have automatically saved thumbnails in SD and Max Resolution (Unscaled) size.

Now let’s get our hands dirty!

Preconnect to YouTube servers

This will save time by doing all the work ahead of time, necessary to establish a connection with a given domains including the DNS lookup, TCP handshake, and TLS negotiation if you’re on HTTPS. So images and videos will download instantly without latency. 2

This is not supported by all browsers , but will take use of it where supported.

Add this code inside <head> tag:

<link rel=“preconnect” href=“//i.ytimg.com">
<link rel=“preconnect” href=“//s.ytimg.com">
<link rel=“preconnect” href="//www.youtube.com">

Note: This is completely optional.

HTML

In the root of your Jekyll website, create inside _include directory a new file called youtube.html. This file will be used as a template for every YouTube video you add.

There are 2 options how to populate this file, and this depends on type of videos you add.

  1. This method is safe and supports all videos on YouTube. Completely usable since all videos have hqdefault.jpg thumbnail version.

    • The image is ~20KB but in lower resolution (480x360).
    • Progressive enchacement support.
    • Inlined responsive SVG play button. (No need for another network request).
{% assign url = include.url | split: "/" %}
{% assign id = url.last | remove: "watch?v=" %}

<div class="yt">
  <img src="//i.ytimg.com/vi/{{ id }}/hqdefault.jpg" />

  <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" onclick="playVideo('{{ id }}', this)">
    <path d="M85.2 47.7L20.6 6.5c-5.3-3.3-9.5-1-9.5 5.2V88c0 6.2 4.4 8.8 9.8 5.8l63.9-34.7C90.1 56 90.3 50.9 85.2 47.7z"/>
  </svg>

  <noscript>
    <a href="{{ include.url }}">
      <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" onclick="playVideo('{{ id }}', this)">
        <path d="M85.2 47.7L20.6 6.5c-5.3-3.3-9.5-1-9.5 5.2V88c0 6.2 4.4 8.8 9.8 5.8l63.9-34.7C90.1 56 90.3 50.9 85.2 47.7z"/>
      </svg>
    </a>
  </noscript>
</div>
  1. This method supports only newer videos (From ~2015) with responsive technique is taking advantage of all image sizes and formats using HTML5 picture element.

    • Browsers with picture element support will serve appropriate image with the best resolution for the screen, preferably in WebP format with fallback on JPG.
    • If picture element is not supported, browser will fallback on img element.
    • Progressive enchacement support.
    • Inlined responsive SVG play button. (No need for another network request).

Note: This method might show broken images on some videos.

{% assign url = include.url | split: "/" %}
{% assign id = url.last | remove: "watch?v=" %}


<div class="yt">
  <picture>
    <!--[if IE 9]><video style="display: none"><![endif]-->
    <source type="image/webp"
            srcset="//i.ytimg.com/vi_webp/{{ id }}/maxresdefault.webp 1080w,
                    //i.ytimg.com/vi_webp/{{ id }}/sddefault.webp 640w,
                    //i.ytimg.com/vi_webp/{{ id }}/hqdefault.webp 480w,
                    //i.ytimg.com/vi_webp/{{ id }}/mqdefault.webp 320w" />

    <source srcset="//i.ytimg.com/vi/{{ id }}/maxresdefault.jpg 1080w,
                    //i.ytimg.com/vi/{{ id }}/sddefault.jpg 640w,
                    //i.ytimg.com/vi/{{ id }}/hqdefault.jpg 480w,
                    //i.ytimg.com/vi/{{ id }}/mqdefault.jpg 320w" />

    <img src="//i.ytimg.com/vi/{{ id }}/hqdefault.jpg" />
    <!--[if IE 9]></video><![endif]-->
  </picture>

  <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" onclick="playVideo('{{ id }}', this)">
    <path d="M85.2 47.7L20.6 6.5c-5.3-3.3-9.5-1-9.5 5.2V88c0 6.2 4.4 8.8 9.8 5.8l63.9-34.7C90.1 56 90.3 50.9 85.2 47.7z"/>
  </svg>

  <noscript>
    <a href="{{ include.url }}">
      <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
        <path d="M85.2 47.7L20.6 6.5c-5.3-3.3-9.5-1-9.5 5.2V88c0 6.2 4.4 8.8 9.8 5.8l63.9-34.7C90.1 56 90.3 50.9 85.2 47.7z"/>
      </svg>
    </a>
  </noscript>
</div>

CSS

This includes video wrapper (.yt) with aspect ration of 16:9, makes the player iframe and SVG play button responsive.

.yt {
  position: relative;
  padding-bottom: 56.25%;
  height: 0;
  overflow: hidden;
  max-width: 100%;
  margin-bottom: 10px;
  background: #000;
}

.yt iframe, .yt object, .yt embed {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.yt svg {
  cursor: pointer;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 20%;
  height: 20%;
  -webkit-transform: translateY(-50%) translateX(-50%);
          transform: translateY(-50%) translateX(-50%);
  transition: 200ms;
  transition-timing-function: ease-in-out;
  -webkit-transition: 200ms;
  -webkit-transition-timing-function: ease-in-out;
}

.yt svg:hover {
  fill: #ccc;
}

Video loading on demand

This is where the magic happens in pure vanilla JavaScript with no external dependencies. So what happens here, when the user clicks on the play button, all the DOM child nodes of the parent div with class .yt will get removed, and the YouTube video iframe tag will be added in their place with autoplay option. Simple as that.

//http://stackoverflow.com/questions/22119673/find-the-closest-ancestor-element-that-has-a-specific-class
function findAncestor (el, sel) {
  while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
  return el;
}

function playVideo(id, e){
  // Create an iFrame with autoplay set to true
  var iframe = document.createElement("iframe");
  var iframe_url = "https://www.youtube-nocookie.com/embed/" + id + "?rel=0&autoplay=1&autohide=1";
  iframe.setAttribute("src", iframe_url);
  iframe.setAttribute("frameborder", "0");
  iframe.setAttribute("allowfullscreen", "true");

  // Replace the YouTube thumbnail with YouTube Player
  var video_wrapper = findAncestor(e, ".yt");
  video_wrapper.innerHTML = "";
  video_wrapper.appendChild(iframe);
}

Note: Autoplay might not work on all mobile devices, users might need to tap 2 times.

Usage

Just include youtube.html file with the URL of the video in your Markdown post or HTML page, like in this example:

{% include youtube.html url="<YouTube_Video_URL_HERE>" %}

And that’s it. Do some tests and you should see improvements in your page loading.

Result

The final is result is 1 network request and iframe loaded only on demand with the support for responsive images, iframe and progressive enchacement. So instead of loading 540KB unnecessarily at the page load, we will load only a very light thumbnail image and wait for user to click the play button to start the video loading.

This example could be further improved by using lazy load technique, which means loading the thumbnail image only when is visible in the viewport.3 I did some tests on this blog and got it working, but it requires an external JavaScript library as dependency. My goal of this post was to make a minimalist version without any external libs.

Note: The code could probably be improved, you can fork it from my GitHub Gists.

Demo | Video loading on demand

Most recent entries

Comments