Article

Lazy loading an image

Remco Hendriksen on Apr 21, 2021
component events props
Last updated: May 30, 2022

Lazy loading your images greatly improves the performance of your web app. In this article i will show how easy it is to make it work in Vue.

Full component

<template>
  <img
    class="lazy-image"
    :class="{ 'lazy-image--loaded': loaded }"
    :src="loadedSrc"
    @load="isLoaded">
</template>
<script>
export default {
  props: {
    src: {
      type: String,
      default: null
    }
  },
  data() {
    return {
      loadedSrc: null,
      loaded: false
    }
  },

  mounted() {
    this.observer = new IntersectionObserver(this.loadImage, {
      root: null,
      rootMargin: '1000px',
      threshold: [0, 0.25, 0.5, 0.75]
    })
    this.observer.observe(this.$el)
  },

  methods: {
    loadImage(event) {
      if (event) {
        let isIntersecting = event.find(entry => {
          return entry.isIntersecting
        })
        if (!isIntersecting || this.loaded || this.loadedSource) {
          return
        }
      }

      this.loadedSrc = this.src

      if (this.observer) {
        this.observer.disconnect()
      }
    },

    isLoaded() {
      this.loaded = true
    }
  }
}
</script>
<style lang="scss">
.lazy-image {
  background-color: #eee;
  display: inline-block;
  width: 100%;
  height: auto;
  opacity: 0;
  visibility: hidden;
  transition: opacity 1s ease;
  pointer-events: none;

  &--loaded {
    visibility: visible;
    opacity: 1;
  }
}
</style>

What is lazy-loading?

Lazy loading basicly means that the rendering of a element is delayed until it becomes visible. The advantage of this is that your elements are not all loaded at once, but only when needed. This greatly increases the performance of your web-app.

The template

The HTML-part of this component is just an image with a :src, :class and a @load attached to it. The src is set when the element become visible (more on that in the javascript part). The @load is fired when the image is done loading and the :class makes sure the image becomes visible.

The javascript part

This part is where most of the work is done. The intersection observer does most of the work.

In the mounted hook we register the IntersectionObserver. This fires the method loadImage when the element is visible in the viewport. In the loadImage we check if the element is entering of leaving the viewport by checking the isIntersecting property of the event which is fired by the intersection observer. When the element is entering the viewport we set the loadedSrc to the intended src (which we passed to the component through a prop). Now the image will start loading and when it's done with this, the isLoaded-method will fire which sets loaded to true.

The CSS part

Initialy we want the image to be hidden, because otherwise you would see an broken image icon and we don't want that. That's why we set opacity:0 and visibility: hidden. In the template part we set a dynamic class lazy-image--loaded. This class is only applied when loaded is true. That's were you want the image to become visible. So we set opacity: 1 and visibility: visibility and our image becomes visibile.

And this is how you do lazy-loading in a Vue component.

Usage

This is how you would you inside your project.

<template>
  <lazy-image src="https://somecdn.com/assets/myimage.png" />
</template>
<script>
  export default {
    components: {
      LazyImage: () => import('~/components/LazyImage'),
    }
  }
</script>

Update: 30 may 2022

The browser-support for loading="lazy" is getting better nowadays. This attribute tells the browser to load the resource (iframe or image) when it becomes visible in the viewport. Of course this is much more performant than a custom-built solution.

So you may be better of using this attribute and use a polyfill for browsers who don't support the attribute yet.

Leave a comment below if you have got any questions about this article.

Leave a comment

More articles