Tutorial

Lazy Image Component Using the Intersection Observer API in Vue.js

Published on February 18, 2018
author

Alex Jover Morales

Lazy Image Component Using the Intersection Observer API in Vue.js

Images, as most of media formats, can be really heavy and take up a large chunk of time to load. We’ve been taught as web developers to compress them as much as possible, having a 2x version for retina displays… and lazy loading them when it makes sense.

The question is, when does it make sense to lazy load an image? Well, if an image is at the top of the page, it’s likely to be visible from the start. In that case, lazy loading isn’t needed. But if an image is visible only later after scrolling, or if it’s part of a list/grid of images (think Google Images) then it makes sense to lazy load them.

Intersection Observer API

In the past, detecting the visibility of an element on a page was hard. Developers needed to implement it themselves or use libraries for that, often with sluggish results and error-prone solutions.

The Intersection Observer API solves this problem in a really neat and performant way. It provides a subscribable model that we can observe to be notified when an element enters the viewport.

Here’s a simple example:

const observer = new IntersectionObserver(entries => {
  const rainbowDiv = entries[0];
  if (rainbowDiv.isIntersecting) {
    // Do something cool here
  }
});

const rainbowDiv = document.querySelector("#rainbowDiv");
observer.observe(rainbowDiv);

We must create an instance of IntersectionObserver, passing the subscribe callback as its parameter. In there we check if it intersects with the rainbowDiv using the method isIntersecting. Finally we must call the observe method, where we pass an element or list of elements.

As you might have noticed, the subscribe callback receives an array of entries, meaning that you can observe multiple elements:

const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // ...
    }
  });
});

const allRainbows = document.querySelector(".rainbow");
observer.observe(allRainbows);

Check out this post of ours for another example of using the Intersection Observer API in vanilla JavaScript.

LazyImage Vue Component

Given the example above, you probably have an idea of how a LazyImage.vue component could be implemented:

LazyImage.vue
<template>
  <img :src="srcImage" />
</template>

<script>
export default {
  props: ['src'],
  data: () => ({ observer: null, intersected: false }),
  computed: {
    srcImage() {
      return this.intersected ? this.src : '';
    }
  },
  mounted() {
    this.observer = new IntersectionObserver(entries => {
      const image = entries[0];
      if (image.isIntersecting) {
        this.intersected = true;
      }
    });

    this.observer.observe(this.$el);
  },
}
</script>

We’re setting up the observer in the mounted hook, making sure the component has been attached already to the DOM. We can access the component element by using this.$el and then pass it to the observe method.

Then, we have an intersected flag in the state that we use in a computed property, srcImage, so that when it intersects with the viewport, it will return the actual value of the src prop, but if not it returns an empty string, and the browser loads nothing.

A Note on Performance

Keep in mind that observing elements takes up memory and CPU, that’s why it’s important to stop observing them as soon as we don’t need to.

We have a couple of methods on the IntersectionObserver instance:

  • unobserve: Stops observing an element.
  • disconnect: Stops observing all elements.

Since in our case we only have one element, either of them will work fine:

LazyImage.vue
<template>
  <img :src="srcImage" />
</template>

<script>
export default {
  props: ['src'],
  data: () => ({ observer: null, intersected: false }),
  computed: {
    srcImage() {
      return this.intersected ? this.src : '';
    }
  },
  mounted() {
    this.observer = new IntersectionObserver(entries => {
      const image = entries[0];
      if (image.isIntersecting) {
        this.intersected = true;
        this.observer.disconnect();
      }
    });

    this.observer.observe(this.$el);
  },
  destroyed() {
    this.observer.disconnect();
  }
}
</script>

First, we stop observing right after we set this.intersected = true; because the image is already loaded at that point and it wouldn’t make sense to continue observing.

Additionally, it doesn’t make sense to observe a component if it gets destroyed, that’s why we added the destroyed hook to stop observing it when that happens.

If you try out the LazyImage component with a list of images and open the Network tab in your browser’s DevTools, you’ll see that the images are being loaded as they enter the viewport.

If your interested in lazy loading images in your Vue apps, but not in implementing the intersection observer code yourself, you can check out the vue-clazy-load component.

Wrapping Up

Lazy Loading images can improve your page performance, in particular the page load time and time to interactive performance metrics. With the IntersectionObserver API, it’s both easy and performant to create your own LazyImage component and use it in your apps.

The IntersectionObserver API is not fully supported by all modern browsers just yet, but there’s a polyfill for it maintained by the w3c so you can use it in your apps today.

Stay cool 🦄

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Alex Jover Morales

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.