Build a Lazy Load Images in Vue.js App

Sun Dec 30 2018

In this tutorial we’ll explain the process of how to make lazy load images in Vue js. Lazy loading is technique to render the image in lower size thereby increasing the speed of the page to render. However, when page is scrolled to the image in lower size is, the image in higher quality is loaded on the background and will swap when it’s ready, thus it’s called ‘lazy load’. You can see the demo here.

The HTML

The HTML consists of markup for the displaying the image. We loops the image using v-for. The img tag we use the src tag for the image in lower size and we add the data-src attributes for image in higher size.

<div id="app">
  <h1>Vue Lazy Load Image</h1>
  <div
    class="image__wrapper"
    v-for="(image, index) in images"
    :key="index"
   >
    <img
      class="image"
     :src="image.low"
      :data-src="image.high"
     >
  </div>
</div>

The CSS

The CSS part is very simple, we only give the image height, weight and align it vertically.

body {
  font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
  text-rendering: optimizelegibility;
  -moz-osx-font-smoothing: grayscale;
  -moz-text-size-adjust: none;
}

h1 {
  font-size: 26px;
  font-weight: 600;
  text-align: center;
}

#app {
  max-width: 30em;
  margin: 1em auto;
}

.image__wrapper {
  width: 400px;
  height: 400px;
  margin: 0 auto;
  margin-bottom: 24px;
  
}

.image__wrapper img{
  width: 100%;
  max-height: 100%;
}

The Javascript

For this tutorial we use vue.js to build the image lazy load, first we create vue instance and state data.

new Vue({
  el: '#app',
  data: {
    images: [{
      low: 'https://i.ibb.co/qmY9sGm/rural-area.jpg',
      high: 'https://i.ibb.co/M8K9cKk/rural-area.jpg',
    },
     {
      low: 'https://i.ibb.co/27h7ymZ/residential.jpg',
      high: 'https://i.ibb.co/6vZZtS2/residential.jpg',
    },
    {
      low: 'https://i.ibb.co/0MWBV5T/public.jpg',
      high: 'https://i.ibb.co/k9BRwtB/public.jpg',
    },
    {
      low: 'https://i.ibb.co/6rXm62Z/industrial.jpg',
      high: 'https://i.ibb.co/YtHgMrF/industrial.jpg',
    },
    {
      low: 'https://i.ibb.co/QX29WXs/aggriculture.jpg',
      high: '',
    },
    {
      low: 'https://i.ibb.co/q0QCn0F/solar-communty.jpg',
      high: '',
    },    
    ]
  },
  ...
 })

Once we create the Vue instance, we mount this instance using el property. In data method we pass the images that contain the link to images, low is for lower size image url and hight is for higher size image.

The lazy load

Lazy load technique that we implements right now use browser API which is Observer API. The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport. You can observe when the element enters the viewport:

const images = document.querySelectorAll('.image');
const options = {
  threshold: 0.1
};
if ('IntersectionObserver' in window) {
  const observer = new IntersectionObserver(intersectHandler, options);
  images.forEach(image => {
    observer.observe(image);
  });
} else {
  Array.from(images).forEach(image => {
    const url = image.dataset.src;
    fetchImage(image)
});

First we make sure if the browser support Intersection Observer API else we fetch all images on the first render . The Observer instance has 2 arguments, handler and options. The handler or callback is the function will fired if there is intersection occurred. In Options we define threshold, indicate at what percentage of the target's visibility the observer's callback should be executed. We also select all images using queryselector and apply the observer instance for each image.

Handling intersection

Define intersectHandler as follows:

const intersectHandler = (images) => {
  images.forEach(image => {
    if (image.intersectionRatio > 0) {
      swapImage(image.target);
    }
  });
};

Handling swap image

Swap image will call get the higher size image from the data-src, then pass it to fetchImage url. When the value return we set the image src attribute with the higher size image.

const swapImage = (image) => {
  const url = image.dataset.src;
  fetchImage(url)
    .then(src => {
        image.src = src;
    })
    .catch(src => {
      image.src = src; 
    });
};

Handling fetch image

fetchImages will return new Promise, it will resolve and return the higher size image url, if it fails will return error image placeholder

const fetchImage = (src) => {
  return new Promise((resolve, reject) => {
    const newImage = new Image();
    newImage.src = src;
    newImage.onload = () => {
      resolve(src);
    };
    newImage.onerror = () => {
      reject("http://lakefarmbeef.co.nz/wp-content/themes/lakefarm/img/noimage.png");
    };
  });
};