a17-lazyload

Simple, tiny, no dependency, lazy loader. Source code available on code.area17.com.

Doesn't preload, unload, mess with media queries, emit events, offer APIs etc.
Uses IntersectionObserver if available and if not, it uses a throttled requestAnimationFrame if available. If neither are available it does nothing.

When an element is in the view port it swaps data-src/data-srcset on img, source and iframes to src/srcset. It also adds a load listener and removes the data- attribute on load to allow you to hook styles up to the two different states.

It will also look for data-lazyload and swap that attribute for data-lazyloaded; again so you can hook up styles to the two different states.

If data-srcset to srcset and picturefill is present, attempts to run picturefill on the element.

When it runs out of elements to watch, the loop ends.

NB: v0.1.0 of @area17/a17-lazyload correlates to v2.1.1 of the original development

Setting up

In your HTML:

<script src="path/to/lazyload.min.js"></script>

(minified JS file)

In your JavaScript (on DOM ready):

document.addEventListener('DOMContentLoaded', function(){
  lazyload();
});

Also available via NPM:

npm install @area17/a17-lazyload

Remember to import it:

import lazyload from '@area17/a17-lazyload';

document.addEventListener('DOMContentLoaded', function(){
  lazyload();
});

Options

These options are the defaults, and can be overridden on init:

var options = {
  pageUpdatedEventName: 'page:updated', // how your app tells the rest of the app an update happened
  elements: 'img[data-src], img[data-srcset], source[data-srcset], iframe[data-src], video[data-src], [data-lazyload]', // maybe you just want images?
  rootMargin: '0px', // IntersectionObserver option
  threshold: 0, // IntersectionObserver option
  maxFrameCount: 10, // 60fps / 10 = 6 times a second
  forceAutoplayVideoPlay: true // force autoplay of autoplay videos
};
lazyload(options);
pageUpdatedEventName
string - an event name to listen for when the page has new content that might need to be lazy loaded. If set to `false` lazyLoad will only run on initial page load.
elements
string - string of items to look to lazy load.
rootMargin
string - IntersectionObserver rootMargin option.
threshold
integer/array - IntersectionObserver threshold option.
maxFrameCount
integer - as requestAnimationFrame runs as the monitor refreshes, this could be 60 times a second, to throttle this we can tell the script every NUM of frames.
forceAutoplayVideoPlay > 0.1.2
boolean - defaults to true, iOS Safari doesn't always autoplay lazy loaded videos with the autoplay attribute, this will attempt to force it, but beware, it potentially replaces the video in order to force Safari to play it, which will likely mean any event listeners you have on the video will be lost. You may need to alter your JavaScript or disable this option.

Useful CSS

This demo page uses the following CSS:

picture,
img,
iframe {
  display: block;
  border: 0 none;
  opacity: 1;
  transition: opacity .25s;
  width: 500px;
  height: 281px;
  background: #f2f2f2;
  outline: 0 none;
}

iframe {
  width: 560px;
  height: 315px;
}

img[data-src]:not([src^="data:image"]),
img[data-srcset]:not([src^="data:image"]),
iframe[data-src] {
  opacity: 0;
}

img[data-srcset][src=""] {
  content: url("data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==");
}
      

Its well advisable to give the images/picture/iframes an intrinsic size to stop huge repaints: Either by giving a pixel size and using a transparent gif:

<img width="500" height="281" data-src="/images/greenflash_800.jpg" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==">
      

Or using the padding trick:

.img-container {
  position: relative;
  height: 0;
  padding-bottom: 56.25%; // for 16:9 images
}

.img-container img {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
      

Standard image, no src

  <img data-src="/images/greenflash_800.jpg">

Standard image, src=""

  <img src="" data-src="/images/greenflash_800.jpg">

Image on different domain

  <img data-src="http://slider.dev.area17.com/images/960/02.jpg" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==">

Standard image with LQIP

  <img data-src="/images/greenflash_800.jpg" src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAABAAD/4QN8aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAxNCA3OS4xNTE0ODEsIDIwMTMvMDMvMTMtMTI6MDk6MTUgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6Y2QwNDkwYzktYTE1Yy00OTY2LTg2MWMtM2QwMWNjODk2MjhkIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkFDMjExNDdBQjRERDExRTY5M0NCRTVBNkJEODRGOTJCIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkFDMjExNDc5QjRERDExRTY5M0NCRTVBNkJEODRGOTJCIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjcyMzI0NTQ5LWFlMGEtNDlmZi1iNjBkLTdhYjZjNzUzOTUxMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpjZDA0OTBjOS1hMTVjLTQ5NjYtODYxYy0zZDAxY2M4OTYyOGQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAaGRknHCc+JSU+Qi8vL0JHPTs7PUdHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHARwnJzMmMz0mJj1HPTI9R0dHRERHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAA4AGQDASIAAhEBAxEB/8QBGwAAAwEBAQEBAQEBAQAAAAAAAQACAwQFBgcICQoLAQEBAQEBAQEBAQEBAQAAAAAAAQIDBAUGBwgJCgsQAAICAQMCAwQHBgMDBgIBNQEAAhEDIRIxBEFRIhNhcTKBkbFCoQXRwRTwUiNyM2LhgvFDNJKishXSUyRzwmMGg5Pi8qNEVGQlNUUWJnQ2VWWzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2EQACAgAFAQYGAQMBAwUDBi8AARECIQMxQRJRYXGBkSITMvChsQTB0eHxQlIjYnIUkjOCQySisjRTRGNzwtKDk6NU4vIFFSUGFiY1ZEVVNnRls4TD03Xj80aUpIW0lcTU5PSltcXV5fVWZnaG/9oADAMBAAIRAxEAPwDipNOm1O10QgBoBsRaEWAik7XURaEWFMNq7Xo2J2KSwcpizT1GDmYqSGFIp2MWCGgzpW6VA6BBoQdQHQRcSagwGNrY9Ii1tZIg5drQi77EiCkQZCK7Xfau1klOYxczB7DFgxbIOMwczF7TBzMGySDk2q9GxVJINQ6B5hN0E3nJuDpDYeYTdBNzJqDamqcRNreySwaIY3oM1JILZLG9kzbIgosFBm5mbSQVSue9UDhGRsZFV0CxkbGRVclKGRr1FVhofUQciqsAScjJyKrcDJByMnIqtIT6iqrQf//Z">

Simple responsive image

  <img src="" data-srcset="/images/greenflash_800.jpg 800w, /images/greenflash_400.jpg 400w" sizes="500px">

Responsive image with LQIP

  <img data-srcset="/images/greenflash_800.jpg 800w, /images/greenflash_400.jpg 400w" sizes="500px" src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAABAAD/4QN8aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAxNCA3OS4xNTE0ODEsIDIwMTMvMDMvMTMtMTI6MDk6MTUgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6Y2QwNDkwYzktYTE1Yy00OTY2LTg2MWMtM2QwMWNjODk2MjhkIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkFDMjExNDdBQjRERDExRTY5M0NCRTVBNkJEODRGOTJCIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkFDMjExNDc5QjRERDExRTY5M0NCRTVBNkJEODRGOTJCIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjcyMzI0NTQ5LWFlMGEtNDlmZi1iNjBkLTdhYjZjNzUzOTUxMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpjZDA0OTBjOS1hMTVjLTQ5NjYtODYxYy0zZDAxY2M4OTYyOGQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAaGRknHCc+JSU+Qi8vL0JHPTs7PUdHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHARwnJzMmMz0mJj1HPTI9R0dHRERHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAA4AGQDASIAAhEBAxEB/8QBGwAAAwEBAQEBAQEBAQAAAAAAAQACAwQFBgcICQoLAQEBAQEBAQEBAQEBAQAAAAAAAQIDBAUGBwgJCgsQAAICAQMCAwQHBgMDBgIBNQEAAhEDIRIxBEFRIhNhcTKBkbFCoQXRwRTwUiNyM2LhgvFDNJKishXSUyRzwmMGg5Pi8qNEVGQlNUUWJnQ2VWWzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2EQACAgAFAQYGAQMBAwUDBi8AARECIQMxQRJRYXGBkSITMvChsQTB0eHxQlIjYnIUkjOCQySisjRTRGNzwtKDk6NU4vIFFSUGFiY1ZEVVNnRls4TD03Xj80aUpIW0lcTU5PSltcXV5fVWZnaG/9oADAMBAAIRAxEAPwDipNOm1O10QgBoBsRaEWAik7XURaEWFMNq7Xo2J2KSwcpizT1GDmYqSGFIp2MWCGgzpW6VA6BBoQdQHQRcSagwGNrY9Ii1tZIg5drQi77EiCkQZCK7Xfau1klOYxczB7DFgxbIOMwczF7TBzMGySDk2q9GxVJINQ6B5hN0E3nJuDpDYeYTdBNzJqDamqcRNreySwaIY3oM1JILZLG9kzbIgosFBm5mbSQVSue9UDhGRsZFV0CxkbGRVclKGRr1FVhofUQciqsAScjJyKrcDJByMnIqtIT6iqrQf//Z">

Simple <picture>, no src

  <picture>
    <source data-srcset="/images/greenflash_400.jpg, /images/greenflash_800.jpg 2x" media="(min-width: 800px)">
    <source data-srcset="/images/greenflash_400.jpg, /images/greenflash_800.jpg 2x" media="(max-width: 799px)">
    <img>
  </picture>

Simple <picture>, src="/images/greenflash_100.jpg" (LQIP)

  <picture>
    <source data-srcset="/images/greenflash_400.jpg, /images/greenflash_800.jpg 2x" media="(min-width: 800px)">
    <source data-srcset="/images/greenflash_400.jpg, /images/greenflash_800.jpg 2x" media="(max-width: 799px)">
    <img src="/images/greenflash_100.jpg">
  </picture>

<picture> with inline LQIP

  <picture>
    <source data-srcset="/images/greenflash_400.jpg, /images/greenflash_800.jpg 2x" media="(min-width: 800px)">
    <source data-srcset="/images/greenflash_400.jpg, /images/greenflash_800.jpg 2x" media="(max-width: 799px)">
    <img src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAABAAD/4QN8aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAxNCA3OS4xNTE0ODEsIDIwMTMvMDMvMTMtMTI6MDk6MTUgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6Y2QwNDkwYzktYTE1Yy00OTY2LTg2MWMtM2QwMWNjODk2MjhkIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkFDMjExNDdBQjRERDExRTY5M0NCRTVBNkJEODRGOTJCIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkFDMjExNDc5QjRERDExRTY5M0NCRTVBNkJEODRGOTJCIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjcyMzI0NTQ5LWFlMGEtNDlmZi1iNjBkLTdhYjZjNzUzOTUxMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpjZDA0OTBjOS1hMTVjLTQ5NjYtODYxYy0zZDAxY2M4OTYyOGQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAaGRknHCc+JSU+Qi8vL0JHPTs7PUdHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHARwnJzMmMz0mJj1HPTI9R0dHRERHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAA4AGQDASIAAhEBAxEB/8QBGwAAAwEBAQEBAQEBAQAAAAAAAQACAwQFBgcICQoLAQEBAQEBAQEBAQEBAQAAAAAAAQIDBAUGBwgJCgsQAAICAQMCAwQHBgMDBgIBNQEAAhEDIRIxBEFRIhNhcTKBkbFCoQXRwRTwUiNyM2LhgvFDNJKishXSUyRzwmMGg5Pi8qNEVGQlNUUWJnQ2VWWzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2EQACAgAFAQYGAQMBAwUDBi8AARECIQMxQRJRYXGBkSITMvChsQTB0eHxQlIjYnIUkjOCQySisjRTRGNzwtKDk6NU4vIFFSUGFiY1ZEVVNnRls4TD03Xj80aUpIW0lcTU5PSltcXV5fVWZnaG/9oADAMBAAIRAxEAPwDipNOm1O10QgBoBsRaEWAik7XURaEWFMNq7Xo2J2KSwcpizT1GDmYqSGFIp2MWCGgzpW6VA6BBoQdQHQRcSagwGNrY9Ii1tZIg5drQi77EiCkQZCK7Xfau1klOYxczB7DFgxbIOMwczF7TBzMGySDk2q9GxVJINQ6B5hN0E3nJuDpDYeYTdBNzJqDamqcRNreySwaIY3oM1JILZLG9kzbIgosFBm5mbSQVSue9UDhGRsZFV0CxkbGRVclKGRr1FVhofUQciqsAScjJyKrcDJByMnIqtIT6iqrQf//Z">
  </picture>

iFrame

  <iframe data-src="https://www.youtube.com/embed/Wji-BZ0oCwg?list=PL148kCvXk8pDWYE9q9cNdux5JFmu80kN8" width="560" height="315" frameborder="0" allowfullscreen></iframe>

Dynamically added content