Native lazy-loading - Why doesn't the video element have it?

5 min read
Native lazy-loading - Why doesn't the video element have it? cover image

Lazy-loading is a strategy to identify resources that are not critical for the initial page load, and load them only when needed. It’s a way to shorten the length of the critical rendering path, which translates into reduced page load times. It leads to faster websites.

We have the ability to lazy-load images and iframes in browsers now via the loading attribute.

<img src="cat.jpg" alt="felix the cat" loading="lazy">

<iframe src="video-player.html" title=".." loading="lazy"></iframe>

Support for lazy-loading is good. Lazy-loading of images is supported by all major browsers now. However, it is still a bit patchy for iframes. Firefox does not support lazy-loading of iframes, and it is currently under an experimental flag in Safari. So, we are gettting there!

Data on support for the loading-lazy-attr feature across the major browsers from caniuse.com

It just made me wonder why the video element has been overlooked. Is it because most videos live on YouTube now and are embedded on websites via iframes?

I don’t know but I certainly hope not! 😕🤞

When writing an article recently, I had a short screen recording to demo some functionality. I wanted to include it as autoplaying video, similar to where you might use an animated GIF. That’s when I realised there is no native lazy loading for videos.

This nudged me towards converting the video into a WebP instead. Did you know that WebP supports animation the same as a GIF but with better compression?

You can use an online video to WEBP converter for this task.

I didn’t dig that deeply into this topic as it was a bit of tangent from what I was doing! However, I did read a recent enough article (late 2019) by Google Devs on this topic, aptly titled “Lazy-loading video”. They describe 2 distinct use cases for embedding videos that are handled differently. Let’s take a look at these to understand the topic better.

Use Case 1: Videos where playback is initiated by the user

You have controls on the video, and it up the user to play the video.

You can specify the preload attribute on the video element to control loading. By providing preload="none", the browser should be prevented from loading the video data.

<!-- disable preloading -->
<video controls preload="none" width="300" poster="img/cover.jpg">
    <source src="files/sample.mp4" type="video/mp4">
</video>

Do not include the autoplay attribute here as that may load the video, disregarding preload="none"!

The browser default behaviors with regard to preload are not set in stone, so being explicit with this is probably a good idea anyway.

On some browsers, the video will be have no background. You can make it look better by using the poster attribute to show a preview image.

When the user clicks the play button of the video, then the video will be loaded.

Use Case 2: A video acting as an animated GIF replacement

This was my use case.

Google recommends using a lazy loading library such vanilla-lazyload, or you can write your own JavaScript code.

To write your own code is not too long.

In the HTML, you do not put src attributes on the source elements, instead you stash the video URL in the data-src attribute. We mark video with a “lazy” class.

<video class="lazy" autoplay muted loop playsinline width="600" height="300" poster="cover.jpg">
  <source data-src="screen-recording.webm" type="video/webm">
  <source data-src="screen-recording.mp4" type="video/mp4">
</video>

The JavaScript code uses the IntersectionObserver API to detect when the video element with the “lazy” class comes into view, and adds a src attribute to each source element with the video URL.

document.addEventListener("DOMContentLoaded", function() {
  var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy"));

  if ("IntersectionObserver" in window) {
    var lazyVideoObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(video) {
        if (video.isIntersecting) {
          for (var source in video.target.children) {
            var videoSource = video.target.children[source];
            if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") {
              videoSource.src = videoSource.dataset.src;
            }
          }

          video.target.load();
          video.target.classList.remove("lazy");
          lazyVideoObserver.unobserve(video.target);
        }
      });
    });

    lazyVideos.forEach(function(lazyVideo) {
      lazyVideoObserver.observe(lazyVideo);
    });
  }
});

Do you always want to do this for videos?

It looks to me like the addition of this functionality would be great. You could add preload="lazy"; or theloading attribute to match the other 2 elements.

Where is the suggestion box? 😄