lazy load -vanilla

by ian_smithz

HTML

<header>
  <div class=hero_bg></div>
  <h1>Performant lazy-load method using the Intersection Observer API</h1>
</header>


<section>
  
  <h2 class=centered-1>No visual jank, runs at the full 60fps</h2>

<!--   <img data-LL-src="https://source.unsplash.com/pFqrYbhIAXs/?w=1920x1080"> -->
  <img data-LL-src="https://websemantics.uk/unsplash/needle-on-the-record.jpg">
  
  <noscript>
    <img src="https://websemantics.uk/unsplash/needle-on-the-record.jpg">
  </noscript>
  
  <a href="https://unsplash.com/@lukechesser" target=_blank rel="noopener noreferrer">
    <svg><use xlink:href="#icon_camera"></use></svg>
    Luke Chesser
  </a>
  
</section>


<section>
  
  <h2 class=centered-2>Ultra lightweight vanilla JavaScript</h2>
  
<!--   <img data-LL-src="https://source.unsplash.com/bq-1seg1cPI/?w=1920x1080"> -->
  <img data-LL-src="https://websemantics.uk/unsplash/busy-bike.jpg">
  
  <noscript>
    <img src="https://websemantics.uk/unsplash/busy-bike.jpg">
  </noscript>
  
  <a href="https://unsplash.com/@clemono2" target=_blank rel="noopener noreferrer">
    <svg><use xlink:href="#icon_camera"></use></svg>
    Clem Onojeghuo
  </a>
  
</section>


<section>
  
  <h2 class=centered-3>Images loaded when JavaScript is unavailable</h2>

<!--   <img data-LL-src="https://source.unsplash.com/X2gM-SIufpU/?w=1920x2390"> -->
  <img data-LL-src="https://websemantics.uk/unsplash/sunday-delight.jpg">
  
  <noscript>
    <img src="https://websemantics.uk/unsplash/sunday-delight.jpg">
  </noscript>
  
  <a href="https://unsplash.com/@foodess" target=_blank rel="noopener noreferrer">
    <svg><use xlink:href="#icon_camera"></use></svg>
    Jennifer Pallian
  </a>
  
</section>


<section>
  
  <h2 class=centered-4>Images loaded when IntersectionObserver is unsupported.</h2>

<!--   <img data-LL-src="https://source.unsplash.com/AYSorKGDm10/?w=1920x1080"> -->
  <img data-LL-src="https://websemantics.uk/unsplash/red-door.jpg">
  
  <noscript>
    <img...

CSS

@media (min-width: 20em) {
  :root {
    font-size: calc(1rem + ((1vw - 0.2em) * 1));
  }
}
@media (min-width: 120em) {
  :root {
    font-size: 2em;
  }
}
body {
  font-family: sans-serif;
  line-height: 1.5;
  margin: 0;
  text-align: center;
}

h1 {
  color: #fff;
  
  font-size: 1.5em;
  font-weight: 100;
  margin: 1rem;
  padding: 1rem .5rem;
  position: absolute; /* Collapses box */
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-shadow: 0 0 2px #000, 0 1px 1px #000;
  background-color: rgba(0,0,0, .5);
/*   backdrop-filter: grayscale(1) contrast(3) blur(5px); */
}


h2  {
  margin: 2em 0 1em;
  font-size: 1.25em;
  font-weight: 100;
}

[class^="centered"] {
  color: #fff;
  background-color: rgba(0,0,0, .5);
  position: absolute; /* Collapses box */
  top: 50px;
  left: 50%;
  transform: translate(-50%, 0);
  text-shadow: 0 0 2px #000, 0 1px 1px #000;
  letter-spacing: 0.05em;
/*   white-space: nowrap; */
  z-index: 1;
  margin:0;
  padding: .25em .5em;
}


header,
footer,
section {
  display: block;
  position: relative;
  min-height: calc(100vw / 16 * 10);
}
footer {
  min-height: 0;
  max-width: 36em;
  margin: 0 auto;
  padding: 0 1rem;
}
.myStuff, .myStuff .details_lnk {
  background-color: #f7f7f7;
}


.hero_bg {
  position: absolute;
  width: 100%;
  top: 0;
  right: 0;
  bottom: 0;
  left;
  background-image: url(https://websemantics.uk/unsplash/duvet-days.jpg);
  background-size:cover;
/*   filter: blur(1px); */
}
 
img {
  display:block;
  max-width: 100%;
  margin:0 auto;
}

a {
  position: absolute;
  right: 0;
  bottom: 0;
  background-color: rgba(0,0,0,.5);
  color: #fff;
  text-decoration: none;
  padding: .25em 1em;
}
.details_lnk {
  position: relative;
  padding: 0;
  text-decoration: underline;
  background-color: #fff;
  color: #000;
}

svg {
  width: .9em;
  height: .9em;
  vertical-align: text-top;
  fill: #ccc;
  margin-right:.35em;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
 ...

JavaScript

// Dean Hume:  https://deanhume.github.io/lazy-observer-load/

// TODO: background-images

var lazy_loader = (() => {

  "use strict";
  
  const dataAttr = "data-LL-src";
  const imageLoaded = "-js-lazyImageLoaded";
  const imageFadeClass = "-js-fadeIn";

  // Get all of the images that are marked up to lazy load
  const images = document.querySelectorAll("[" + dataAttr + "]");

  const config = {
    // If the image gets within 50px in the Y axis, start the download.
    //rootMargin: '50px 0px',
    rootMargin: '-50px 0px', // 50px into viewport before loading so you can see it kick in
    threshold: 0.01
  };

  let imageCount = images.length;
  let observer;

  // If we don't have support for intersection observer, loads the images immediately
  if (!('IntersectionObserver' in window)) {

    console.log("No IntersectionObserver support");
    Array.from(images).forEach(image => preloadImage(image));

  } else {
    // It is supported, load the images
    console.log("IntersectionObserver supported");
    observer = new IntersectionObserver(onIntersection, config);
    images.forEach(image => {
      if (image.classList.contains(imageLoaded)) {
        return;
      }

      observer.observe(image);
    });
  }

  // Fetch image for given URL
  function fetchImage(url) {
    return new Promise((resolve, reject) => {
      const image = new Image();
      image.src = url;
      image.onload = resolve;
      image.onerror = reject;
    });
  }

  // Preloads the image
  function preloadImage(image) {
    const src = image.getAttribute(dataAttr);
    if (!src) {
      return;
    }

    return fetchImage(src).then(() => {
      applyImage(image, src);
    });
  }

  // Load all of the images immediately
  function loadImagesImmediately(images) {
    Array.from(images).forEach(image => preloadImage(image));
  }

  // Disconnect the observer
  function disconnect() {
    if (!observer) {
      return;
    }

    observer.disconnect();
  }

  // On intersection
 ...