<h1>How to lazy load images in a performant way</h1>
<p>
There is also an example image how to use it with an background image.
  <br>
  Take a look at the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Browser_compatibility">Browser support table.</a>
  and use <a href="https://www.npmjs.com/package/intersection-observer#browser-support">Polyfill</a> for better support. This one here does work properly in Safari, thanks to the <a href="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver">Polyfill Script</a> I added to the source. You will see that the HTML is bloated up quite a bit, this is because I added some noscript fallbacks for no-js browsing people.</p>

<noscript>
  <!-- 
    Hide the lazy load images if we do not have JS
  -->
  <style>
    .image__list-lazy-image { 
      display: none;
    }
  </style>
</noscript>
<div class="image-container">
  <ul class="image__list">
    <li class="image__list-item">
      <noscript>
        <img class="image__list-image" src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796300/pexels-photo-559768.jpg" alt="blue lake with surrounding mountains">
      </noscript>
      <img class="image__list-image image__list-lazy-image" data-src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796300/pexels-photo-559768.jpg" alt="blue lake with surrounding mountains">
    </li>
    <li class="image__list-item">
      <noscript>
        <img class="image__list-image" src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796305/pexels-photo-104336.jpg" alt="street in the middle of a forrest">
      </noscript>
      <img class="image__list-image image__list-lazy-image" data-src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796305/pexels-photo-104336.jpg" alt="street in the middle of a forrest">
    </li>
    <li class="image__list-item">
      <noscript>
        <img class="image__list-image" src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796376/les-anderson-175603.jpg" alt="two elder people opening togehter a christmas present">
      </noscript>
      <img class="image__list-image image__list-lazy-image" data-src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796376/les-anderson-175603.jpg" alt="two elder people opening togehter a christmas present">
    </li>
    <li class="image__list-item">
      <noscript>
        <img class="image__list-image" src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796381/pexels-photo-737108.jpg" alt="black and white clothes hanging on a clothes rail">
      </noscript>
      <img class="image__list-image image__list-lazy-image" data-src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796381/pexels-photo-737108.jpg" alt="black and white clothes hanging on a clothes rail">
    </li>
    <li class="image__list-item">
      <noscript>
        <img class="image__list-image" src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796475/tobias-van-schneider-122288.jpg" alt="book collection photographed from top">
      </noscript>
      <img class="image__list-image image__list-lazy-image" data-src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796475/tobias-van-schneider-122288.jpg" alt="book collection photographed from top">
    </li>
    <li class="image__list-item">
      <noscript>
        <img class="image__list-image" src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796300/pexels-photo-559768.jpg" alt="two elder people opening togehter a christmas present">
      </noscript>
      <img class="image__list-image image__list-lazy-image" data-src="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796300/pexels-photo-559768.jpg" alt="brown meadow with a tower in the very back">
    </li>
    <li class="image__list-item">
      <noscript>
        <div class="image__list-image--bg" style="background-image:url('http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796300/pexels-photo-559768.jpg');" alt="two elder people opening togehter a christmas present"></div>
      </noscript>
      <div class="image__list-image--bg image__list-lazy-image" data-lazybackground="http://res.cloudinary.com/dsteinel/image/upload/c_scale,w_450/v1523796300/pexels-photo-559768.jpg">
      </div>
    </li>
  </ul>
</div>
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  max-width: 450px;
  margin: 0 auto;
  padding: 2em 0;
  font-size: 18px;
  line-height: 1.5;
}

h1 {
  display: block;
  margin: 0 auto 2rem;
  line-height: 1.1;
}

ul {
  padding: 0;
  margin: 0;
  list-style-type: none;
}

a {
  color: #A55; /* HAHA! */
}

p {
  margin-bottom: 3rem;
}

.image__list-image {
  opacity: 1;
  min-height: 100%;
}

.image__list-lazy-image { 
  opacity: 0;
}

.image__list-item {
  width: 100%;
  height: 250px;
  overflow: hidden;
  margin-bottom: 40px;
}

.image__list-image--bg {
  background-size: cover;
  background-repeat: no-repeat;
  height: 100%;
}
const options = {
  rootMargin: '0px',
  threshold: 0.1
}
const allTheLazyImages = document.querySelectorAll('.image__list-lazy-image');
let observer;

// Does your browser support InersectionObserver?
if('IntersectionObserver' in window) {
  observer = new IntersectionObserver(lazyLoader, options);

  // select all our image which we want to have lazy loaded
  allTheLazyImages.forEach(image => {
    // put them in the observer
    observer.observe(image);
  });
} else {
  allTheLazyImages.forEach(image => {
    lazyLoadImage(image); // if it is not supported, load all
  });
}

function lazyLoader (entries) {
  // loop through all images
  entries.forEach(entry => {
    // does the viewport hit the current image
    if (entry.intersectionRatio > 0) {
      // yes! load the image
      lazyLoadImage(entry.target);
    }
  });
}

function lazyLoadImage(observedImage) { 
  /**
    *  We hit an observed image!
    *  First, remove the lazy loading class. This will show the image
    *  Then remove the observer
    */
  observedImage.classList.remove('image__list-lazy-image');
  
  // If we have an background image, replace the source with the data-lazyBackground attribute
  if(observedImage.dataset.lazybackground) {
    observedImage.style.backgroundImage = `url(${observedImage.dataset.lazybackground})`;
  }
  
  // does it have a data attribute with image-src?
  if (observedImage.getAttribute('data-src')) {
    // yes! So lets make the image source equal to the data image source and render it!
    observedImage.src = observedImage.dataset.src;
  
    if('IntersectionObserver' in window) {
      // now that the image has bee renderes, we don't need to observe it anymore
      observer.unobserve(observedImage);
    }
  }      
}
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/polyfill/v2/polyfill.min.js?features=IntersectionObserver