Cloudinary Blog

Low Quality Image Placeholders (LQIP) Explained

Automate Placeholder Generation and Accelerate Page Loads

Low-quality image placeholders (LQIPs) were originally introduced to enable webpages to load correctly in an orderly manner, displaying ultra small, blurry images while the actual version is loading, which works well with lazy loading in JavaScript. Then came a dilemma: should we add more JavaScript to help images load faster even though we must wait for the same JavaScript to run before they can load? It was a chicken-and-egg situation.

Only scant guidelines on LQIPs are available on the web. In this post, partly based on the feedback we’ve collected from the Cloudinary community, we’ll describe image-placeholder generators, use cases for LQIPs, and the ways in which to leverage LQIPs to accelerate page loads and optimize user experience through Cloudinary’s built-in capabilities.


Sign up for Cloudinary free today!


The Current Options

An ongoing debate centers on the best way for delivering optimal image performance at scale and the role JavaScript could play. Here are the options:

  • Leverage HTML5's image attributes, such as srcset and sizes, to make images responsive. That is, trust the browser to make the right decision according to image densities. The result, however, is a complete lack of control over such elements as device pixel ratios (DPRs). Given that different browsers handle srcset differently, scalinge and delivering a consistent cross-browser experience is tough to do. Also, applying inline HTML code to each and every image is a labor-intensive chore.
  • Implement on the server side Google's HTTP Client Hints, which were at one time touted as the Holy Grail of automation for image-optimization strategies. However, Client Hints are currently supported by the Chrome and Opera browsers only. As soon as they work on other major browsers, Client Hints will most likely become the golden standard. We’re keeping an eye on the developments.
  • Use a client-side, JavaScript-based, responsive library to capture all the pertinent details, such as an image’s width and height, DPR, viewport, and the browser’s user agent, subsequently passing them to Cloudinary. Cloudinary then transforms the master image on the fly to the perfect size and characteristics— irrespective of the device, window size, orientation, or resolution.

For more details, check out the free Cloudinary tool Responsive Image Breakpoints Generator.

The JavaScript Options for Generating LQIPs

Given the pros and cons of the above solutions, JavaScript is the best and most reliable option for enabling LQIPs to load while the actual images are preloading in the background. When preloading is complete, JavaScript swaps the placeholders with the actual images.

For responsive designs and images, do not display white space instead of placeholders during preloading because layout changes and content shifts make for poor user experience and performance. Generic image placeholders, which lack appeal and which do not alert the audience that content is still loading, are far from being ideal, too.

Instead, display either of the following as an LQIP:

  • A simple, solid-color image (perhaps based on the predominant color in the discovered palette) with a gradient option to fill in the color during preloading. Note this handy tip: Cloudinary automatically detects and maps out (even for advanced search) the color palette and the predominant colors so that you can perform such transformations on the fly.
  • A Scalable Vector Graphics (SVG) object, aka SVG image placeholders (SQIP), as defined in the following code:
Copy to clipboard
<img src=“sample.jpg”
    style="background-size: cover; background-image:
    url(data:image/svg+xml;base64,<svg text>);"

Note
Though an elegant approach with a great user experience and an accurate representation of the original image at infinitely smaller payloads, SQIPs are not scalable, requiring preparation work, numerous resources, and processing time. For more insight, read this : article.

The Recommended Option for Generating LQIPs

Cloudinary offers an effortless and efficient way of generating LQIPs. All you need to do is upload an image to Cloudinary and add the f_auto parameter to the URL. Cloudinary would then transform the image through automation in real time.

For a scaled-up, tiny image compressed by Cloudinary for quality, potentially also chroma subsampled, blurred, or pixelated, Cloudinary also delivers color blending, such as that in WebP format. You can then apply color effects: grayscale, black and white, colorized, or various hues.

Give it a try: start by registering for a free account on Cloudinary.

Examples

Here is the original version of a JPEG with a width of 640 px. and a weight of 40 KB.

Ruby:
Copy to clipboard
cl_image_tag("string_1.jpg", :width=>640, :crop=>"scale")
PHP v1:
Copy to clipboard
cl_image_tag("string_1.jpg", array("width"=>640, "crop"=>"scale"))
PHP v2:
Copy to clipboard
(new ImageTag('string_1.jpg'))
  ->resize(Resize::scale()->width(640));
Python:
Copy to clipboard
CloudinaryImage("string_1.jpg").image(width=640, crop="scale")
Node.js:
Copy to clipboard
cloudinary.image("string_1.jpg", {width: 640, crop: "scale"})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation().width(640).crop("scale")).imageTag("string_1.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('string_1.jpg', {width: 640, crop: "scale"}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("string_1.jpg", {width: 640, crop: "scale"})
React:
Copy to clipboard
<Image publicId="string_1.jpg" >
  <Transformation width="640" crop="scale" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="string_1.jpg" >
  <cld-transformation width="640" crop="scale" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="string_1.jpg" >
  <cl-transformation width="640" crop="scale">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(640).Crop("scale")).BuildImageTag("string_1.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation().width(640).crop("scale")).generate("string_1.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setWidth(640).setCrop("scale")).generate("string_1.jpg")!, cloudinary: cloudinary)
Original image

Below are three LQIPs generated by Cloudinary, compressed for quality and optimized for WebP, weighing only 2.17 KB in WebP and 1.46 KB in JPG in non-Chrome browsers. The top version shows the grayscale effect; the middle one, the black-and-white touch; and the bottom one, a cartoon-like look through the cartoonify parameter.

Ruby:
Copy to clipboard
cl_image_tag("string_1.jpg", :transformation=>[
  {:width=>640, :crop=>"scale"},
  {:effect=>"blur:1000", :quality=>1},
  {:effect=>"grayscale"}
  ])
PHP v1:
Copy to clipboard
cl_image_tag("string_1.jpg", array("transformation"=>array(
  array("width"=>640, "crop"=>"scale"),
  array("effect"=>"blur:1000", "quality"=>1),
  array("effect"=>"grayscale")
  )))
PHP v2:
Copy to clipboard
(new ImageTag('string_1.jpg'))
  ->resize(Resize::scale()->width(640))
  ->effect(Effect::blur()->strength(1000))
  ->delivery(Delivery::format(Format::auto()))
  ->delivery(Delivery::quality(1))
  ->effect(Effect::grayscale());
Python:
Copy to clipboard
CloudinaryImage("string_1.jpg").image(transformation=[
  {'width': 640, 'crop': "scale"},
  {'effect': "blur:1000", 'quality': 1},
  {'effect': "grayscale"}
  ])
Node.js:
Copy to clipboard
cloudinary.image("string_1.jpg", {transformation: [
  {width: 640, crop: "scale"},
  {effect: "blur:1000", quality: 1},
  {effect: "grayscale"}
  ]})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation()
  .width(640).crop("scale").chain()
  .effect("blur:1000").quality(1).chain()
  .effect("grayscale")).imageTag("string_1.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('string_1.jpg', {transformation: [
  {width: 640, crop: "scale"},
  {effect: "blur:1000", quality: 1},
  {effect: "grayscale"}
  ]}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("string_1.jpg", {transformation: [
  {width: 640, crop: "scale"},
  {effect: "blur:1000", quality: 1},
  {effect: "grayscale"}
  ]})
React:
Copy to clipboard
<Image publicId="string_1.jpg" >
  <Transformation width="640" crop="scale" />
  <Transformation effect="blur:1000" quality="1" />
  <Transformation effect="grayscale" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="string_1.jpg" >
  <cld-transformation width="640" crop="scale" />
  <cld-transformation effect="blur:1000" quality="1" />
  <cld-transformation effect="grayscale" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="string_1.jpg" >
  <cl-transformation width="640" crop="scale">
  </cl-transformation>
  <cl-transformation effect="blur:1000" quality="1">
  </cl-transformation>
  <cl-transformation effect="grayscale">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(640).Crop("scale").Chain()
  .Effect("blur:1000").Quality(1).Chain()
  .Effect("grayscale")).BuildImageTag("string_1.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation()
  .width(640).crop("scale").chain()
  .effect("blur:1000").quality(1).chain()
  .effect("grayscale")).generate("string_1.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setWidth(640).setCrop("scale").chain()
  .setEffect("blur:1000").setQuality(1).chain()
  .setEffect("grayscale")).generate("string_1.jpg")!, cloudinary: cloudinary)
Grayscale LQIP
Ruby:
Copy to clipboard
cl_image_tag("string_1.jpg", :transformation=>[
  {:width=>640, :crop=>"scale"},
  {:effect=>"blur:1000", :quality=>1},
  {:effect=>"blackwhite"}
  ])
PHP v1:
Copy to clipboard
cl_image_tag("string_1.jpg", array("transformation"=>array(
  array("width"=>640, "crop"=>"scale"),
  array("effect"=>"blur:1000", "quality"=>1),
  array("effect"=>"blackwhite")
  )))
PHP v2:
Copy to clipboard
(new ImageTag('string_1.jpg'))
  ->resize(Resize::scale()->width(640))
  ->effect(Effect::blur()->strength(1000))
  ->delivery(Delivery::format(Format::auto()))
  ->delivery(Delivery::quality(1))
  ->effect(Effect::blackwhite());
Python:
Copy to clipboard
CloudinaryImage("string_1.jpg").image(transformation=[
  {'width': 640, 'crop': "scale"},
  {'effect': "blur:1000", 'quality': 1},
  {'effect': "blackwhite"}
  ])
Node.js:
Copy to clipboard
cloudinary.image("string_1.jpg", {transformation: [
  {width: 640, crop: "scale"},
  {effect: "blur:1000", quality: 1},
  {effect: "blackwhite"}
  ]})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation()
  .width(640).crop("scale").chain()
  .effect("blur:1000").quality(1).chain()
  .effect("blackwhite")).imageTag("string_1.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('string_1.jpg', {transformation: [
  {width: 640, crop: "scale"},
  {effect: "blur:1000", quality: 1},
  {effect: "blackwhite"}
  ]}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("string_1.jpg", {transformation: [
  {width: 640, crop: "scale"},
  {effect: "blur:1000", quality: 1},
  {effect: "blackwhite"}
  ]})
React:
Copy to clipboard
<Image publicId="string_1.jpg" >
  <Transformation width="640" crop="scale" />
  <Transformation effect="blur:1000" quality="1" />
  <Transformation effect="blackwhite" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="string_1.jpg" >
  <cld-transformation width="640" crop="scale" />
  <cld-transformation effect="blur:1000" quality="1" />
  <cld-transformation effect="blackwhite" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="string_1.jpg" >
  <cl-transformation width="640" crop="scale">
  </cl-transformation>
  <cl-transformation effect="blur:1000" quality="1">
  </cl-transformation>
  <cl-transformation effect="blackwhite">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(640).Crop("scale").Chain()
  .Effect("blur:1000").Quality(1).Chain()
  .Effect("blackwhite")).BuildImageTag("string_1.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation()
  .width(640).crop("scale").chain()
  .effect("blur:1000").quality(1).chain()
  .effect("blackwhite")).generate("string_1.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setWidth(640).setCrop("scale").chain()
  .setEffect("blur:1000").setQuality(1).chain()
  .setEffect("blackwhite")).generate("string_1.jpg")!, cloudinary: cloudinary)
Black and-white LQIP

Ruby:
Copy to clipboard
cl_image_tag("string_1.jpg", :transformation=>[
  {:width=>640, :crop=>"scale"},
  {:effect=>"blur:1000", :quality=>1},
  {:effect=>"cartoonify"}
  ])
PHP v1:
Copy to clipboard
cl_image_tag("string_1.jpg", array("transformation"=>array(
  array("width"=>640, "crop"=>"scale"),
  array("effect"=>"blur:1000", "quality"=>1),
  array("effect"=>"cartoonify")
  )))
PHP v2:
Copy to clipboard
(new ImageTag('string_1.jpg'))
  ->resize(Resize::scale()->width(640))
  ->effect(Effect::blur()->strength(1000))
  ->delivery(Delivery::format(Format::auto()))
  ->delivery(Delivery::quality(1))
  ->effect(Effect::cartoonify());
Python:
Copy to clipboard
CloudinaryImage("string_1.jpg").image(transformation=[
  {'width': 640, 'crop': "scale"},
  {'effect': "blur:1000", 'quality': 1},
  {'effect': "cartoonify"}
  ])
Node.js:
Copy to clipboard
cloudinary.image("string_1.jpg", {transformation: [
  {width: 640, crop: "scale"},
  {effect: "blur:1000", quality: 1},
  {effect: "cartoonify"}
  ]})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation()
  .width(640).crop("scale").chain()
  .effect("blur:1000").quality(1).chain()
  .effect("cartoonify")).imageTag("string_1.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('string_1.jpg', {transformation: [
  {width: 640, crop: "scale"},
  {effect: "blur:1000", quality: 1},
  {effect: "cartoonify"}
  ]}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("string_1.jpg", {transformation: [
  {width: 640, crop: "scale"},
  {effect: "blur:1000", quality: 1},
  {effect: "cartoonify"}
  ]})
React:
Copy to clipboard
<Image publicId="string_1.jpg" >
  <Transformation width="640" crop="scale" />
  <Transformation effect="blur:1000" quality="1" />
  <Transformation effect="cartoonify" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="string_1.jpg" >
  <cld-transformation width="640" crop="scale" />
  <cld-transformation effect="blur:1000" quality="1" />
  <cld-transformation effect="cartoonify" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="string_1.jpg" >
  <cl-transformation width="640" crop="scale">
  </cl-transformation>
  <cl-transformation effect="blur:1000" quality="1">
  </cl-transformation>
  <cl-transformation effect="cartoonify">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(640).Crop("scale").Chain()
  .Effect("blur:1000").Quality(1).Chain()
  .Effect("cartoonify")).BuildImageTag("string_1.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation()
  .width(640).crop("scale").chain()
  .effect("blur:1000").quality(1).chain()
  .effect("cartoonify")).generate("string_1.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setWidth(640).setCrop("scale").chain()
  .setEffect("blur:1000").setQuality(1).chain()
  .setEffect("cartoonify")).generate("string_1.jpg")!, cloudinary: cloudinary)
Cartoon-looking LQIP

The possibilities are truly endless, promising a wide range of effects without sacrificing performance or image weight. An example is Instagram-like filters, which you can mix and match for the desired result. Let your imagination run wild with Cloudinary’s Neural Artwork Style Transfer add-on.

Simplification of URLs With Named Transformations

You can simplify and shorten those long, complex URLs generated by Cloudinary with named transformations, which are akin to CSS classes, by grouping together URL-based, chained transformations and converting them to templates. Follow these steps:

  1. Create an LQIP for an image in your Cloudinary account.
  2. Log in to your Cloudinary account and click the third icon from the left at the top for the Media Transformations screen.
  3. Click Edit in the box above your most recent transformation.
  4. In the next screen, click Save to save the template, i.e., the named transformation, with a name of your choice, e.g., lqip.

Alternatively, start from the Media Transformations screen (see below). Click Create a new transformation in the top-right corner and, in the next screen, click Save As or Save with a name you prefer.

Console

The output shows the streamlined URL like this one: http://res.cloudinary.com/demo/image/upload/w_640,f_auto/t_lqip/string_1.jpg

The t_lqip parameter encompasses most of the transformations performed on the image, hiding the settings for resizing, cropping, overlays, and so forth.

Conclusion

Regardless of your media strategies, we recommend that you adopt LQIPs, especially if you lazy-load images for your site. To accelerate workflows, leverage Cloudinary for its automated process of generating image placeholders. We welcome your feedback and would appreciate your sharing with us your experience, preferably with examples.


Additional Resources:

Recent Blog Posts

Create Lightweight Sites With Low-Code and No-Code Technology

Consumers expect modern websites to be mainly visual. But, the more compelling and complex the related media is, the more data is involved, compounding the site’s weight. In today’s content-craving world, delivering unoptimized media can cost you because it leads to sluggish page loads, resulting in visitors abandoning your site in search of a faster alternative. In fact, a page load that takes more than three seconds can cause as many as 40% of your visitors to bounce. Given this competitive, digital-first environment, you can’t afford to lose page views, for time is of the essence.

Read more
A Blueprint for AWS-Secured Webhook Listeners for Cloudinary

tl;dr: An AWS-secured and optimized Cloudinary webhook listener for extending the Cloudinary service

Code: Github

A webhook is a communication medium for sending notifications from one platform to another about events that occurred. In place are user-defined HTTP callbacks that are triggered by specific events. When a triggered event takes place on the source site, the webhook listens to the event, collects the data, and sends it to the URL you specified in the form of an HTTP request.

Read more
New Accessibility Features for Cloudinary’s Product Gallery Widget

Cloudinary’s Product Gallery widget, which launched in 2019, has enabled many brands to effectively and efficiently showcase their products in a sleek and captivating manner, saving countless hours of development time and accelerating release cycles. By adding Cloudinary’s Product Gallery widget with its customizable UI to their product page, retailers reap numerous benefits, often turning visitors into customers in short order.

Read more
Why Successful Businesses Engage With and Convert Audiences With Visual Media

Most business buyers prefer to research purchase options online, as do many shoppers. No wonder online retail sales in the U.S. rose by 32.4% in 2020—an impressive gain of $105 billion.

For B2B and B2C businesses, text-heavy websites are no longer adequate in attracting shoppers. Instead, engaging visual media—spin images, videos, 3D models, augmented reality—are becoming a must for conveying eye-catching details and differentiators about products or services.

Read more
Making User-Generated Content (UGC) Shoppable With Cloudinary

User-generated content (UGC) is a powerful marketing tool. Not only does video complement marketing efforts for e-commerce by enabling customers to explore products in greater detail, but UGC also adds an element of trust. As a bonus, user-generated video is an exceptional opportunity for e-businesses to attract website traffic without their marketing team having to create promotional videos from scratch. User-generated content drives conversions and brand loyalty as a direct result of authentic interaction.

Read more