Image Lazy-Loading With Pure JavaScript
Tags: javascript, performance, beginners
Published: 2019-12-08 21:34:32

Half of the application size and file requests takes images. If you want to make your application faster and lighter, it is a good starting point to think about image optimisation.

You can make your application faster by lazy-loading images. Meaning to show them, only when they appear in the viewport of the device or near it. It can be done using the Intersection Observer API.

## Intersection Observer

It is a performance API, that notifies you when an element is entering or leaving the viewport. To have the information when to hide or to show elements is very useful. You can use it for analytics to track how much time a user spends by viewing the current element, you can start or stop the video when it reaches a certain point in the view or load more content, like images, when you scroll to the bottom of the page.
Intersection Observers are a very efficient and perfect tool to track scroll events.

To use Intersection Observer we call a constructor and pass a callback function. That callback will run on every element that is being observed. To observe particular elements, we select them and call the `observe` method on each of them:

```html
<head>
    <style>
        .box {
            border: 1px solid;
            height: 400px;
            width: 500px;
        }
    </style>
</head>

<body>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
    <div class="box">4</div>

    <script>
        const io = new IntersectionObserver((entries) =>
            entries.forEach((entry) =>
                console.log(
                    entry.target.innerText + " entered the view: " + entry.isIntersecting
                )
            )
        );

        document.querySelectorAll(".box").forEach((element) => io.observe(element));
    </script>
</body>
```
If you run this code and check the browser console, you should see all `div` elements are registered, and on scrolling the page, each of the element prints output when it enters and leaves the viewport.

## Lazy-Loading Images

When the browser detects a source attribute in the image tag, it downloads it from the path, parses and renders. By manipulating source and dataset attributes we can decide when to show the image.

The idea behind lazy-loading images is:
- removing image `src` attribute or adding a placeholder
- putting the source link in the dataset attribute
- call intersection observer on the images
- when the intersection is detected, take the source from dataset and place in the `src`

Dataset attributes are an ideal place to store additional information for later access. By placing the actual image path in the `data-src`, and later picking it and setting to the `src` attribute we will load the image.


```html
<body>
    <img class="lazy" data-src="lazy-image.jpg">
    <img class="lazy" data-src="lazy-image.jpg">
    <img class="lazy" data-src="lazy-image.jpg">

    <script>
        const io = new IntersectionObserver((entries) =>
            entries.forEach((entry) => {
                // set image source only when it is in the viewport
                if (entry.isIntersecting) {
                    const image = entry.target;
                    // setting image source from the dataset
                    image.src = image.dataset.src;

                    // when image is loaded, we do not need to observe it any more
                    io.unobserve(image);
                }
            })
        );

        document.querySelectorAll(".lazy").forEach((element) => io.observe(element));
    </script>
</body>
```

That is the most simple example of how to lazy-load images just with pure JavaScript.

## Things To Think About It

Image tag size is defined by the picture. If you do not specify the dimensions for it, or put a placeholder for the `src` attribute, all your images will have 0px size and will be loaded by observer right away.

Intersection Observer API is mostly used for all modern browsers. But if you need support for older ones, you should use a polyfill. Also, a good way would be to check at first if the browser has Intersection Observer. In case it does not, run a fallback code.


## Conclusion

Using image lazy-loading will make the page lighter, loading time faster, less usage of browser resources and make the user experience more enjoyable.