Blog Post

Create a Lazy Loading Custom Youtube Embed

July 7, 2021

Category: HTML

Tags: hugo performance video

Adding YouTube embeds to any page can majorly slow down web pages because by default, the embeds load on page load, even if your visitor never views the video.

Additionally, the video thumbnails look very “YouTube-ee.”

1. Add Lazy Loading

The most basic step is to add loading=“lazy” to the embed code from YouTube.

Like this … (the only change to the default embed is my addition of loading=“lazy”)

<iframe width="560" height="315" 
  src="https://www.youtube.com/embed/khdT1e7jEIE"
  title="YouTube video player" 
  frameborder="0" 
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 
  allowfullscreen
  loading="lazy"
  >
</iframe>

2. Build a Custom Embed

We can do one better than lazy loading the iframe. Let’s build out a custom embed script. Here’s the end result.


Note: Thanks to Chris Coyier of CSS for pointing me to Adrian Roselli’s embed code. I’m merely adopting and tweaking Adrian’s work.

Using the srcdoc property on the native iframe element, you can create a custom poster for the video using the thumbnail image connected to the YouTube video. Not only does this little trick prevent the video from loading on page load, but it also makes for a more customized experience.

Note two variables you’ll need to swap out in the code I have below:

  • {{ YOUTUBE-ID }} (used 3x)
  • {{ YOUTUBE-TITLE }} (used 2x)
<div style="width: 100%; max-width: 550px; box-shadow: 6px 6px 10px hsl(206.5, 0%, 75%); margin: 2rem;">
<div style="position: relative; padding-bottom: 56.15%; height: 0; overflow: hidden;">
  <iframe 
    style="position: absolute; top: 0; left:0; width: 100%; height: 100%; border: 0;"
    loading="lazy";
    srcdoc="<style>
      * {
      padding: 0;
      margin: 0;
      overflow: hidden;
      }
      body, html {
        height: 100%;
      }
      img, svg {
        position: absolute;
        width: 100%;
        top: 0;
        bottom: 0;
        margin: auto;
      }
      svg {
        filter: drop-shadow(1px 1px 10px hsl(206.5, 70.7%, 8%));
        transition: all 250ms ease-in-out;
      }
      body:hover svg {
        filter: drop-shadow(1px 1px 10px hsl(206.5, 0%, 10%));
        transform: scale(1.2);
      }
    </style>
    <a href='https://www.youtube.com/embed/{{ YOUTUBE-ID }}?autoplay=1'>
      <img src='https://img.youtube.com/vi/{{ YOUTUBE-ID }}/hqdefault.jpg' alt='{{ YOUTUBE-TITLE }}'>
      <svg xmlns='http://www.w3.org/2000/svg' width='64' height='64' viewBox='0 0 24 24' fill='none' stroke='#ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-play-circle'><circle cx='12' cy='12' r='10'></circle><polygon points='10 8 16 12 10 16 10 8'></polygon></svg>
    </a>
    "
    src="https://www.youtube.com/embed/{{ YOUTUBE-ID }}" 
    title="{{ YOUTUBE-TITLE }}"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 
    allowfullscreen>
  </iframe>
</div>
</div>

I’ve made a few changes to Adrian’s embed code.

  1. Full lazy loading. Why not prevent anything from loading while the video is out of view?
  2. Custom icon. I added a custom icon from Feathericons.com. The original script used ▶ wrapped in a span tag, but I didn’t like how it looked on mobile. The SVG takes the place of the span tag and I made the stroke color #ffffff;
  3. Animation. I added styling for a hover effect on the play button. It includes a drop-shadow and a scale effect.

Note: YouTube offers a few different thumbnails. If you want something larger or smaller than what I’ve used here, here are the other available options:

  • default (120×90)
  • mqdefault (320×180)
  • hqdefault (480×360) 👈 (what I used)
  • sddefault (640×480)
  • maxresdefault (1280×720)

Bonus: How I template YouTube embeds in Hugo SSG.

I’m using the Hugo Static Site Generator for this site. While they have a Shortcode built in for YouTube videos, it uses the default YouTube embed.

Adding my custom YouTube shortcode for .md files.

I’ve adapted the code above, writing my own shortcode for .md files. You’d use this shortcode whenever adding a video to a .md file in your site.

Note: Per Hugo’s documentation, custom shortcodes are .html files and must be placed in the layouts/shortcodes directory.

{{ $ytid := .Get 0 }}
{{ $title := .Get 1 }}

<div style="width: 100%; max-width: 550px; box-shadow: 6px 6px 10px hsl(206.5, 0%, 75%); margin: 2rem;">
  <div style="position: relative; padding-bottom: 56.15%; height: 0; overflow: hidden;">
    <iframe 
      style="position: absolute; top: 0; left:0; width: 100%; height: 100%; border: 0;"
      loading="lazy";
      srcdoc="<style>
        * {
        padding: 0;
        margin: 0;
        overflow: hidden;
        }
        body, html {
          height: 100%;
        }
        img, svg {
          position: absolute;
          width: 100%;
          top: 0;
          bottom: 0;
          margin: auto;
        }
        svg {
          filter: drop-shadow(1px 1px 10px hsl(206.5, 70.7%, 8%));
          transition: all 250ms ease-in-out;
        }
        body:hover svg {
          filter: drop-shadow(1px 1px 10px hsl(206.5, 0%, 10%));
          transform: scale(1.2);
        }
      </style>
      <a href='https://www.youtube.com/embed/{{ $ytid }}?autoplay=1'>
        <img src='https://img.youtube.com/vi/{{ $ytid }}/hqdefault.jpg' alt='{{ $title }}'>
        <svg xmlns='http://www.w3.org/2000/svg' width='64' height='64' viewBox='0 0 24 24' fill='none' stroke='#ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-play-circle'><circle cx='12' cy='12' r='10'></circle><polygon points='10 8 16 12 10 16 10 8'></polygon></svg>
      </a>
      "
      src="https://www.youtube.com/embed/{{ $ytid }}" 
      title="{{ $title }}"
      frameborder="0"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 
      allowfullscreen>
    </iframe>
  </div>
</div>

Feel free to copy the code and use it in your site. It accepts two arguments:

  1. YouTube ID ($ytid)
  2. YouTube Title ($title)

In markdown files, you’d reference the shortcode like this (assuming you’ve also called it youtube.html):

{{< youtube "khdT1e7jEIE" "Custom Lazy Loading YouTube Embed Code" >}}

The first argument is the YouTube video ID and the second is the video title. When you build your site, Hugo will use your shortcode.

Adding my custom YouTube partial for .html files.

If you want to embed a YouTube video in an HTML page (rather than a .md file), you’ll need to use a custom partial file instead.

Note: Per Hugo’s documentation, custom partials are .html files and must be placed in the layouts/partials directory.

<div style="width: 100%; max-width: 550px; box-shadow: 6px 6px 10px hsl(206.5, 0%, 75%); margin: 2rem;">
<div style="position: relative; padding-bottom: 56.15%; height: 0; overflow: hidden;">
  <iframe 
    style="position: absolute; top: 0; left:0; width: 100%; height: 100%; border: 0;"
    loading="lazy";
    srcdoc="<style>
      * {
      padding: 0;
      margin: 0;
      overflow: hidden;
      }
      body, html {
        height: 100%;
      }
      img, svg {
        position: absolute;
        width: 100%;
        top: 0;
        bottom: 0;
        margin: auto;
      }
      svg {
        filter: drop-shadow(1px 1px 10px hsl(206.5, 70.7%, 8%));
        transition: all 250ms ease-in-out;
      }
      body:hover svg {
        filter: drop-shadow(1px 1px 10px hsl(206.5, 0%, 10%));
        transform: scale(1.2);
      }
    </style>
    <a href='https://www.youtube.com/embed/{{ .video }}?autoplay=1'>
      <img src='https://img.youtube.com/vi/{{ .video }}/hqdefault.jpg' alt='{{ .title }}'>
      <svg xmlns='http://www.w3.org/2000/svg' width='64' height='64' viewBox='0 0 24 24' fill='none' stroke='#ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-play-circle'><circle cx='12' cy='12' r='10'></circle><polygon points='10 8 16 12 10 16 10 8'></polygon></svg>
    </a>
    "
    src="https://www.youtube.com/embed/{{ .video }}" 
    title="{{ .title }}"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 
    allowfullscreen>
  </iframe>
</div>
</div>

Feel free to copy the code and use it in your site. It accepts two arguments:

  1. YouTube ID ( .video )
  2. YouTube Title ( .title )

In HTML files, you’d reference the partial like this (assuming you’ve also called it youtube.html):

{{ partial "youtube.html" (dict "video" "khdT1e7jEIE" "title" "Custom Lazy Loading YouTube Embed Code") }}

You have to create a dictionary and pass in key: value pairs for partials in Hugo. So Hugo see this as:

  • video: ‘khdT1e7jEIE’
  • title: ‘Custom Lazy Loading YouTube Embed Code’

When you build your site, Hugo will use your partial to generate the code for the YouTube embeds.