Lazy Load for videos with plain JS

Bogdan Bendziukov
3 min readJan 20, 2023

Video lazy load is very important for a site’s performance. Especially, when you have several videos on your page, you don’t need them to load immediately, because the user hasn’t even seen them yet. Your videos should be loaded only when they appear in the user’s viewport, or about to appear.

Photo by Jakob Owens on Unsplash

Unlike <img> or <iframe> tags, the <video> tag does not have a loading attribute. So browser’s native lazy loading for video currently is not supported. It does have a preload attribute, but it’s not work for autoplay videos. But let’s start with some basics.

The basic structure of HTML5 <video> element on a website looks like this:

<video controls playsinline autoplay muted preload="metadata" poster="placeholder.jpg">
<source src="videos_source.webm" type="video/webm">
<source src="videos_source.mp4" type="video/mp4">
</video>

I didn’t include all possible attributes, but you can read more about all of them at MDN Web Docs. I just quickly update you about several:

  • controls — to show video’s control elements, like play/pause button, seeking bar, volume control etc.;
  • playsinline — indicating that the video is to be played “inline”, that is within the element’s playback area;
  • autoplay — to start playing the video once it was loaded;
  • muted — to mute video’s sound (required if you want autoplay to work);
  • preload — what kind of data the browser should preload. May accept three values:
    none: means the video should not be preloaded;
    metadata(default since Chrome 64): means that only video metadata (e.g. length) is fetched;
    auto: means that the whole video file can be downloaded, even if the user is not expected to use it.
  • poster — an image placeholder, which shows until video loads.

Autoplay or not autoplay?

If you do not have autoplay video (means autoplay attribute is not present), then you can simply set the preload attribute to none. In this case browsers won’t preload any video data.

But if you’re using autoplay videos and want them to start playing automatically only when they appear in the user’s viewport — then you need some JS code to write.

The code

First, you need to replace your src attributes to data-src on each <source> element, so your HTML code looks like this:

<video class="lazy" controls playsinline autoplay muted preload="metadata" poster="placeholder.jpg">
<source data-src="videos_source.webm" type="video/webm">
<source data-src="videos_source.mp4" type="video/mp4">
</video>

I’ve also added the lazy class to the video element, so it will be easier to get desirable elements.

Then, we use some JS code to switch that data-src attribute to src and make videos to load only when they appear in the user’s viewport:

Let me explain what we do.

First of all, we wrap the code into a function that will run only after DOM is loaded, using the DOMContentLoaded event (line #1).

Secondly, we grab and store all lazy load videos as an array, using spread operator (…) (line #2):

let lazyVideos = […document.querySelectorAll(“video.lazy”)];

Now we need to detect somehow when the video is in the user’s viewport (in other words when the user has scrolled the page to the video).

To achieve this we are going to use the IntersectionObserver API.

Its constructor accepts a callback function, which has an entries argument. This entries argument is an array of objects, each representing one threshold which was crossed or about to. So we loop through entries and check if the isIntersecting property is true (means the element is in the viewport) (lines from #4 to #7).

Then, we loop through child elements of the <video> element, which stores in the video.target property. We are only interested in <source> elements, so once we locate them, we apply to each src attribute the value of data-src(lines from #8 to #13).

After that, we force the video to load using the load() method, remove class lazy, and stop observing the <video> element to prevent the same procedure again (lines from #15 to #17).

In the end, we apply the observe() method of IntersectionObserver to each <video> element. That method adds an element to the set of target elements being watched by the IntersectionObserver.

Congrats, now your videos will load only when they are in the user’s viewport, which means the videos are lazy loading 😎.

Demo

Recommended articles:

https://web.dev/i18n/en/lazy-loading-video/

--

--

Bogdan Bendziukov

I'm a web developer from Kyiv 💛💙. A WordPress enthusiast for 10 years. Writing tips and thoughts from my dev experience .