DEV Community

loading...
Cover image for Lazy loading videos with plain old javascript

Lazy loading videos with plain old javascript

Himanshu Mishra
Hi, My name is Himanshu and I'm an independent maker from India. Currently working on Hoverify- tryhoverify.com
・4 min read

While making landing page for Hoverify, I noticed terrible loading times and performance. Scrolling was a laggy until everything got loaded. Since page is filled with videos, loading them at once was a very bad idea so I implemented some lazy loading with some pure JS. This article is for sharing what I learned.

What is lazy loading

Lazy loading is basically loading content at the point when it is needed. In our case we want to load videos when they are completely in the viewport.

Let's start

It is mostly javascript in action so there is not much on html and css side but we will start by writing a basic page for testing.

    <html>
        <head>
            <title>Lazy Load Example</title>
        </head>
        <style>
            video {
                width: 540px;
                height: 340px;
                margin-bottom: 20rem;
                display: block;
            }
        </style>
        <body>
              <!--Without lazy load-->
              <video loop=1 muted=1 autoplay>
                  <source src="https://tryhoverify.com/videos/live_editing.mp4" type="video/mp4"></source>
              </video>
            <!--With lazy load-->
            <video loop=1 muted=1 autoplay>
                <data-src src="https://tryhoverify.com/videos/live_editing.mp4" type="video/mp4"></data-src>
            </video>

            <!--With lazy load-->
            <video loop=1 muted=1 autoplay>
                <data-src src="https://tryhoverify.com/videos/live_editing.mp4" type="video/mp4"></data-src>
            </video>
        </body>

    </html>

As you can see there are three video tag. Top one is a normal video tag which will be normally loaded. Other two does not have <source> tag because we will replace <data-src> with <source> later when the video is visible in the viewport.

Fun part

Now let's think about the problem for a second. We want to replace <data-src> with <source> whenever the video gets in the viewport for the first time. We also need to handle scroll, load and resize events as they will have effect on visibility of elements.

Detecting if the video is in the viewport or not

To do this we can use getBoundingClientRect() to get the position of element on the screen and compare that to viewport size.

    function isElementInViewport (el) 
    {
        var rect = el.getBoundingClientRect();

        return (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 
            rect.right <= (window.innerWidth || document.documentElement.clientWidth)
        );
    }

Listen to scroll, resize and load events

We will listen to load, scroll and resize event and bind them to a handler function which will loop through all the videos found in the page. It will check if they are in the viewport and do our simple trick of replacing tags so that browser can start loading videos.

    const handler = () => 
    {
        /* 
            Check videos if they are in the viewport.
            If yes then replace data-src with source.
        */ 
    }

    addEventListener('load', handler, false);
    addEventListener('scroll', handler, false);
    addEventListener('resize', handler, false);
  1. load event is fired when all subframes, images, stylesheets, scripts, etc have been loaded.
  2. scroll is fired whenever you scroll.
  3. resizeis fired when you resize the viewport.

Looping through videos

Last piece of puzzle is to handle all the events that we added above. As explained above we need to loop through and check if the videos are in the viewport or not.

    const handler = () => 
    {
        const videos = document.querySelectorAll('video');
        for (let i = 0; i < videos.length; i++)
        {
            const video = videos[i];
            const visible = isElementInViewport(video); // Check if the video is in the viewport or not.
            if (visible)
            {
                const dataSrc = video.querySelector('data-src');
                if (dataSrc) // Check if data-src exists or not. If yes, than we have never loaded this video.
                {
                    // Creating souce element and adding respective attributes.
                    const source = document.createElement('source');
                    source.src = dataSrc.getAttribute('src');
                    source.type = dataSrc.getAttribute('type');

                    video.appendChild(source); // Add new source element to video.
                    video.removeChild(dataSrc); // Remove data-src from video. 
                }
            }
        }
    }

We replaced data-src with video when video was fully visible in the viewport. But you will see that we also placed a check to see if data-src exists or not. This is because we are deleting data-src when we replace it and it also saves us extra operations of replacing it again and again.

Final javascript code

    function isElementInViewport (el) 
    {
        var rect = el.getBoundingClientRect();

        return (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 
            rect.right <= (window.innerWidth || document.documentElement.clientWidth)
        );
    }

    const handler = () => 
    {
        const videos = document.querySelectorAll('video');
        for (let i = 0; i < videos.length; i++)
        {
            const video = videos[i];
            const visible = isElementInViewport(video); // Check if the video is in the viewport or not.
            if (visible)
            {
                const dataSrc = video.querySelector('data-src');
                if (dataSrc) // Check if data-src exists or not. If yes, than we have never loaded this video.
                {
                    // Creating souce element and adding respective attributes.
                    const source = document.createElement('source');
                    source.src = dataSrc.getAttribute('src');
                    source.type = dataSrc.getAttribute('type');

                    video.appendChild(source); // Add new source element to video.
                    video.removeChild(dataSrc); // Remove data-src from video. 
                }
            }
        } 
    }

    addEventListener('load', handler, false);
    addEventListener('scroll', handler, false);
    addEventListener('resize', handler, false);

You can find final working example here

This was a very basic example of lazy loading. You can do much better than this. For instance, you can add some animations or you can optimize event handling for better performance.

Please let me know if you find this useful in comments.

Thanks for reading :)

Discussion (0)