Lazy Loading Images

Outline

  • Lazy Loading Image Overview
  • Different techniques
  • How to achieve better UX
  • Future native support

Mobile

  • Overall size: 1.7MB
  • Image: 800kb

Desktop

  • Overall size: 1.8MB
  • Image: 900kb

Around 40%- 50% size of a web page is image! (source)

*Based on HTTPArchive

Why need Lazy Loading Image?

  • Performance Improvement
  • Cost reduction

Which image can be lazy-loaded?

How to implement

1. Prevent image loading:

  • Use <img/> tag
  • Use CSS background

 

2. Trigger the load when image is in viewport

  • Use JS Event
  • Use Intersection Observer API

Use <img> tag

Don't set the src url to image

<img data-src="https://ik.imagekit.io/demo/default-image.jpg" />

Set src when image is in viewport

const lazyImages = document.querySelectorAll('img.lazy-image');      
lazyImages.forEach((image, index) => {
  if (isInViewport(image)) {
    image.src = image.getAttribute('data-src');
    image.classList.remove("lazy-image");
  }
});

Use CSS background-image

.lazy-image {
  background: #F1F1FA;
}

.background-image {
  background-image: url('https://ik.imagekit.io/demo/img/image10.jpeg?tr=w-600,h-400');
}

Toggle class name to add background-image

Detect if element is in viewport

getBoundingClientRect() API

Use Javascript events

function loadLazyImage() {
  const lazyImages = document.querySelectorAll('img.lazy-image');

  lazyImages.forEach((lazyImage, index) => {
    if (isInViewport(lazyImage)) {
      setImageSrc(lazyImage);
    }
  });
}

document.addEventListener("scroll", loadLazyImage);
window.addEventListener("resize", loadLazyImage);

Use Intersection Observer

const lazyImages = document.querySelectorAll('img.lazy-image');
const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const image = entry.target;
      setImageSrc(image);
      imageObserver.unobserve(image);
    }
  });
});

// Observe all lazy images
lazyImages.forEach(image => {
  imageObserver.observe(image);
});

Issues with Lazy Image

  • Placeholder is ugly
  • Slower to see the content
  • ...

To a better UX

  • Use right image placeholder
  • Add buffer time for image load
  • Avoid content shifting
  • Defer image render until ready

Use dominant color placeholder

  • Find dominant color of image to create placeholder
  • Used by Pinterest, Google

Low quality image placeholder (LQIP)

  • Use a low quality image as placeholder
  • Create the blur effect
  • Used by Facebook and Medium

Facebook Technique

  • Scale to 200 byte

  • Send in GraphQL response

Problem

User scrolls fast

Solution

Preload image

Add buffer time to load image

Problem

  • Content jump after lazy image loaded

Solution

  • Provide size or ratio to keep the layout

Avoid content shifting

Problem

Janky effect of partial render

Solution

Defer until ready

Defer render until ready

src is set

 download image

decode image

render image

const newImage = document.createElement('img');
newImage.setAttribute('src', image.getAttribute('data-src'));
newImage.classList.add('image');

newImage.decode().then(() => {
  image.parentNode.replaceChild(newImage, image);
});

What is next?

Native support for lazy image in browser is coming...

Available in Chrome 75

<img src="celebration.jpg" loading="lazy" alt="..." />
<iframe src="video-player.html" loading="lazy"></iframe>
  • lazy: is a good candidate for lazy loading. 
  • eager: is not a good candidate for lazy loading. Load right away.
  • auto: browser will determine whether or not to lazily load.

Feature detection

<script>
if ('loading' in HTMLImageElement.prototype) { 
    // Browser supports `loading`..
} else {
   // Fetch and apply a polyfill/JavaScript library
   // for lazy-loading instead.
}
</script>

But...

Thanks for listening to my talk

References

Copy of Lazy Loading Images

By thientran1707

Copy of Lazy Loading Images

  • 141