Skip to content

Lazy Loading Images Using Low-quality Placeholders

Ensuring our web pages load quickly is our duty as builders of the internet. By lazy loading our images, we are able to save valuable load time for our users. The challenge with lazy loading is pre-generating and storing low-quality placeholders for the full quality image. Let us learn how to generate the placeholders on-demand low-quality image placeholders.

The final project can be viewed on Codesandbox.

You can find the full source code on my Github repository.

Nuxt.Js is a Vue.Js framework that allows us to build our applications with confidence. To get started, ensure you have Yarn, NPM v5.2+ or v6.1+ installed. Open the terminal in your preferred working directory and run the following command.

yarn create nuxt-app nuxtjs-low-quality-placeholder
# OR
npx create-nuxt-app nuxtjs-low-quality-placeholder
# OR
npm init nuxt-app nuxtjs-low-quality-placeholder
Code language: PHP (php)

This will initiate some questions to help set up the project. Here are our recommended defaults:

Project name: nuxtjs-low-quality-placeholder Programming language: JavaScript Package manager: Yarn UI framework: Tailwind CSS Nuxt.Js modules: None Linting tools: None Test frameworks: None Rendering mode: Universal (SSR / SSG) Deployment target: Server (Node.js hosting) Development tools: None What is your Github username? < your-github-username >

Once the setup is complete, you may now enter and run your project. It will be accessible on http://localhost:3000.

cd nuxtjs-low-quality-placeholder

yarn dev
# OR
npm run dev
Code language: PHP (php)

Cloudinary is a media management platform that allows us to build engaging and customized visual experiences. Let us prepare our account for the project. If you do not have an account, feel free to register here.

Proceed to the media library section and create a folder named nuxtjs-low-quality-placeholder. Add the following files to the folder.

You should now have a folder similar to this one.

Cloudinary folder

To allow our application to communicate with Cloudinary, we are going to install @nuxtjs/cloudinary. Run the following command in the root project folder.

yarn add @nuxtjs/cloudinary
# OR
npm run install @nuxtjs/cloudinary
Code language: CSS (css)

Add @nuxtjs/cloudinary as a module in the nuxt.config.js file.

// nuxt.config.js
export default {
    ...
    modules: [
        '@nuxtjs/cloudinary'
    ]
    ...
}
Code language: JavaScript (javascript)

Add a cloudinary section in the nuxt.config.js file for our configurations.

// nuxt.config.js
export default {
    cloudinary: {
        cloudName: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
        useComponent: true,
        secure: true
    }
}
Code language: JavaScript (javascript)

The above code refers to the NUXT_ENV_CLOUDINARY_CLOUD_NAME environmental variable. These are values we don’t want to be stored in our codebase for either security or portability reasons. Let’s create the .env file in the root project folder.

touch .env
Code language: CSS (css)

Then add your cloud name. This can be located on your Cloudinary dashboard.

NUXT_ENV_CLOUDINARY_CLOUD_NAME=<your-cloudinary-cloud-name>
Code language: HTML, XML (xml)

Before optimizing the images, we first need to display them in the user interface. To do this, let us load them into our component state and generate the image path using the Cloudinary API.

// pages/index.vue
<script>
export default {
  name: 'IndexPage',
  data(){
    return {
      images: [
        {
          public_id:'nuxtjs-low-quality-placeholder/ryan-ancill-aJYO8JmVodY-unsplash',
          credit: 'Photo by <a href="https://unsplash.com/@ryanancill?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Ryan Ancill</a> on <a href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>',
        },
        {
          public_id:'nuxtjs-low-quality-placeholder/ali-karimiboroujeni-XAwyrABB2IA-unsplash',
          credit:'Photo by <a href="https://unsplash.com/@alikarimi_photography?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Ali Karimiboroujeni</a> on <a href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>',
        },
        {
          public_id:'nuxtjs-low-quality-placeholder/nathan-dumlao-f5DHtik4hWc-unsplash',
          credit:'Photo by <a href="https://unsplash.com/@nate_dumlao?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Nathan Dumlao</a> on <a href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>',
        },
        {
          public_id:'nuxtjs-low-quality-placeholder/pawel-czerwinski-DKzsQT9zUPk-unsplash',
          credit:'Photo by <a href="https://unsplash.com/@pawel_czerwinski?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Pawel Czerwinski</a> on <a href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>',
        },
        {
          public_id:'nuxtjs-low-quality-placeholder/toa-heftiba-FV3GConVSss-unsplash',
          credit:'Photo by <a href="https://unsplash.com/@heftiba?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Toa Heftiba</a> on <a href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>',
        },
        {
          public_id:'nuxtjs-low-quality-placeholder/elise-wilcox-Q6GRqKGuswc-unsplash',
          credit:'Photo by <a href="https://unsplash.com/@elise_outside?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Elise Wilcox</a> on <a href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>'
        }
      ]
    };
  },
  methods:{
    imagePath(publicId){ 
      return this.$cloudinary.image.url(`${publicId}.jpg`);
    }
  }
}
</script>
Code language: HTML, XML (xml)

We may now loop throw the images array and display them.

<!-- pages/index.vue -->
<template>
    ...
    <div v-for="image in images" :key="image.public_id">
        <div>
            <Img 
                :src='imagePath(image.public_id)' 
                :smallSrc='placeholderPath(image.public_id)' 
                :alt="credit" 
            />
        </div>
        <div>
            <h3 v-html="image.credit"></h3>
        </div>
    </div>
    ...
</template>
Code language: HTML, XML (xml)

To generate low-quality placeholders, we are going to use the cartoonify transformation. We specify a color reduction of 2 and a detail reduction to 0.1.

After cartooning the image, we scale it down to a width of 150 pixels.

// pages/index.vue
<script>
export default {
  ...
  methods:{
    ...
    placeholderPath(publicId){
      return this.$cloudinary.image.url(
        `${publicId}.svg`, 
        {
          effect: "cartoonify:colors:2:detail:0.1",
          width: 150,
          crop:'scale'
        }
      );
    }
  }
}
</script>
Code language: HTML, XML (xml)

Now that we now have a high quality and a low-resolution version of each photo, we need to implement lazy loading. We will make use of the vue-blur-loader package for this. To get started, let’s install the package.

npm install vue-blur-loader --save
#or 
yarn add vue-blur-loader
Code language: CSS (css)

Let us not use the BlurLoader component to lazy load the images. First, let’s load the component into the page.

// pages/index.vue
<script>

import BlurLoader from 'vue-blur-loader'

export default {
  ...
  components:{
    BlurLoader
  },
  ...
}
</script>
Code language: HTML, XML (xml)

We can now replace the img tag we had early with the BlurLoader component injecting both image sources into the component.

<!-- pages/index.vue -->
<template>
    ...
    <div v-for="image in images" :key="image.public_id">
        <div>
            <div>
              <BlurLoader 
                :src='imagePath(imagde.public_id)' 
                :smallSrc='placeholderPath(image.public_id)' 
                :alt="credit" 
              />
            </div>
            <div>
                <h3 v-html="image.credit"></h3>
            </div>
        </div>
    </div>
    ...
</template>
Code language: HTML, XML (xml)

Our app should now automatically lazy load the low-quality placeholder before the high-resolution image.

To learn more about the transformations available, feel free to go through the transformation URL API reference.

Back to top

Featured Post