DEV Community

Robert Marshall
Robert Marshall

Posted on • Originally published at robertmarshall.dev on

Lazy Loading Videos on Scroll with Vanilla JS and Intersection Observers


This article was originally posted (and is more up to date) at https://robertmarshall.dev/blog/lazy-loading-videos-on-scroll-with-vanilla-js-and-intersection-observers/


As frontend developers, we often encounter the challenge of optimizing website performance. One powerful technique to achieve this is lazy loading.

Lazy loading allows us to load assets, such as images or videos, only when they are needed, reducing initial page load times and conserving bandwidth.

I have written pieces on using React and Lazy Loading, but sometimes Vanilla JS is needed.

In this article, we’ll focus on lazy loading videos using the Intersection Observer API. By leveraging this built-in browser feature, we can efficiently detect when videos come into the user’s viewport and load them on demand.

TLDR

Take a look at this CodeSandbox for a working example of how to load a video on scroll.

_ Update _

I recently wrote a piece on deferring the video until a user clicks. This is a more effective solution than waiting until scroll. It is about YouTube but could be refactored to work with most providers.

robertmarshall.dev/blog/on-click-lazy-load-video-iframe-in-react/

Setting up the HTML Structure

Before we delve into the JavaScript code, let’s understand the HTML structure we’ll be working with.

In the example, we’ll have multiple video elements, each representing a different video to be loaded on scroll.

<div>
  <div
    data-video-url="https://player.vimeo.com/video/588259728?h=09704145bb&dnt=1&app_id=122963&autoplay=1&muted=1"
    data-video-title="This is an iframe title"
    class="video-wrap"
    style="
      overflow: hidden;
      padding-top: 56.25%;
      position: relative;
      width: 100%;
    "
  ></div>
  <div
    data-video-url="https://player.vimeo.com/video/588259728?h=09704145bb&dnt=1&app_id=122963&autoplay=1&muted=1"
    class="video-wrap"
    style="
      overflow: hidden;
      padding-top: 56.25%;
      position: relative;
      width: 100%;
    "
  ></div>
</div>
Enter fullscreen mode Exit fullscreen mode

Each div element contains styling to ensure the video’s correct aspect ratio and prevent page jumps when the video finally loads. The first div includes data-video-url and data-video-title attributes, which we will later grab to inject into the iframe.

Section 2: The Intersection Observer API

Now to cover the Intersection Observer API. This is a built-in browser feature that allows us to observe changes in the intersection of elements with a viewport or a specific container.

The Intersection Observer is perfect for our lazy loading implementation because it can detect when a video element comes into view, signalling the browser to load the video.

Section 3: The JavaScript Implementation

First, we need to wait for the DOM to be fully loaded before starting our JavaScript implementation. We’ll use the DOMContentLoaded event to ensure our script runs at the right time.

if (document.readyState !== "loading") {
  initVimeo();
} else {
  document.addEventListener("DOMContentLoaded", initVimeo);
}

Enter fullscreen mode Exit fullscreen mode

Next, let’s define the initVimeo() function, which will be the starting point of the lazy loading implementation. It will use the querySelectorAll method to select all video elements with the data-video-url attribute.

function initVimeo() {
  const videoWraps = document.querySelectorAll(".video-wrap[data-video-url]");
  // More code will be added here later
}
Enter fullscreen mode Exit fullscreen mode

Section 4: Implementing Lazy Loading

Inside the initVimeo() function the Intersection Observer will check when each video element comes into view. If it’s the first time the element is visible (not yet loaded), it will to load the video dynamically.

function initVimeo() {
  const videoWraps = document.querySelectorAll(".video-wrap[data-video-url]");
  const videoStateMap = new Map();

  const observer = new IntersectionObserver((entries) => {
    entries.forEach(({ target, isIntersecting }) => {
      if (isIntersecting && !videoStateMap.get(target)) {
        const videoUrl = target.getAttribute("data-video-url");
        const videoTitle = target.getAttribute("data-video-title");

        const iframe = document.createElement("iframe");
        iframe.src = videoUrl;
        iframe.frameBorder = "0";
        iframe.allow = "autoplay; fullscreen; picture-in-picture";
        iframe.allowfullscreen = "";

        if (videoTitle) {
          iframe.title = videoTitle;
        }

        iframe.style = "position: absolute; inset: 0; width: 100%; height: 100%";

        target.appendChild(iframe);

        videoStateMap.set(target, true);
      }
    });
  });

  videoWraps.forEach((videoWrap) => {
    videoStateMap.set(videoWrap, false);
    observer.observe(videoWrap);
  });
}
Enter fullscreen mode Exit fullscreen mode

By leveraging the Intersection Observer API, we’ve successfully implemented lazy loading for videos, ensuring they load only when they are about to be displayed on the user’s screen. This optimization results in faster page load times and a smoother user experience.

To see a working version of this code, take a look at the Code Sandbox.

You can find me on Twitter if you have any questions.

Top comments (0)