@style.tools/lazy

A lightweight and high performance lazy loader and `element-in-view` callback based on [Intersection Observer V2](https://developers.google.com/web/updates/2019/02/intersectionobserver-v2) with an efficient polyfill for old browsers.


Keywords
lazy, images, image, iframe, loader, intersectionobserver, feo, frontend, optimization, in-view, inview, intersection-observer, lazy-load, lazy-loading
License
MIT
Install
npm install @style.tools/lazy@1.2.3

Documentation

Build Status Version npm version Latest Stable Version

Lazy Image and Iframe Loader

A lightweight lazy loader based on Intersection Observer V2 with a tiny fallback for old browsers.

$lazy(
   selector, /* string, Node, NodeList or observer config object */
   inview, /* optional: custom in-view callback */
   observer_callback, /* optional: custom observer callback */
   webp /* disable WebP rewrite (when using lazy+webp.js) */
);	

Lazy loading of background-image in stylesheets.

$lazybg(
  sheets, /* stylesheet element(s), default: document.styleSheets (all) */
  lazy_config, /* optional: config to pass to $lazy() */
  resolver, /* optional: JSON or javascript image resolver */
  webp /* disable WebP rewrite (when using lazybg+webp.js) */
);

Documentation is available on docs.style.tools/lazy.

Install via npm

npm install @style.tools/lazy --save

Install via PHP Composer

composer require styletools/lazy

New in v1.1.0

  • Added: .webp rewrite with fallback (WebP support for <img> tag)
  • Added: inview and out-of-view callback (persistent observer)
  • Removed: CustomEvent (can be manually added via inview callback)
  • $lazybg for lazy loading of background-image in stylesheets
  • Tiny lazy load script renamed to $z() (300 bytes).

Warning: the custom observer callback has been moved to the third argument and the dist/* file names have been changed.

See releases or documentation for more information.

Config

The selector entry accepts multiple configuration formats including a string, an Array, a Node, NodeList or a JSON object with observer configuration.

Simple config

$lazy({
   "selector": "[data-src]",
   "threshold": 0.006,
   "rootMargin": "0px"
});

Custom observer config

$lazy({
   "selector": "[data-src]",
   "observer": {
      "threshold": 0.006,
      "rootMargin": "0px",
      "trackVisibility": true,
      "delay": 100
   }
});

The array based index config is a compressed format to save size in the HTML document.

  • [0] = selector
  • [1] = threshold OR observer config when an object
  • [2] = rootMargin

Simple config

$lazy(["[data-src]", 0.006, "0px"]);

Custom observer config

$lazy(["[data-src]", {
   "threshold": 0.006,
   "rootMargin": "0px",
   "trackVisibility": true,
   "delay": 100
}]);

Inview & Out-of-view callback

The inview callback makes it possible to use $lazy as a simple inview script.

$lazy(".selector", function(target, observer, is_inview) {
  
  // element in view
  is_inview = boolean

  return false; // persist observer to enable out-of-view callback
});

By returning false from a custom inview callback, the observer will not be removed and will trigger the callback again when the element moves in or out of view.

The inview argument accepts an array with 3 index positions:

  1. inview: a function to call when the element moves into view.
  2. out-of-view a function to call when the element moves out of view
  3. after_inview a function to call with the default inview-method after src and srcset have been rewritten.

When out-of-view is null, the inview method is used as out-of-view method.

$lazy(".selector", [
  function inview(target, observer) {
  
    // element in view

    return false; // persist observer
    
  },
  function out_of_view(target, observer) {

    // element out of view

    return false; // persist observer

  }]);

To easily extend the original data-src based lazy loading of images, you can use an after_inview callback.

$lazy(".selector", [,,function after_inview(target) {
  
  // target has been lazy-loaded
  target.classList.add('custom-class');
}]);

Custom observer callback

The third argument enables to manually define the IntersectionObserver callback.

$lazy('div#id', 0, function(entries) {
	// entries is a Array of `IntersectionObserverEntry` or HTML Nodes
	// you need to manually verify if the browser supports Intersection Observer

	if (window.IntersectionObserver) {
		// entries[0] = IntersectionObserverEntry
		// entries[0].target = element
	} else {
		// entries[0] = element
	}
})

Lazy loading of background-image in stylesheets

dist/lazybg.js enables to lazy load background images in stylesheets. It makes use of CSS Variables with a fallback for old browsers.

There are four options to resolve images:

  1. manually define images via :root {} within the stylesheet
  2. base64 encode image URLs
  3. provide a JSON source list as resolver
  4. provide a javascript function as resolver
/* :root based pre-configured value */
:root {
  --z--lazy-img: url('/image.jpg');
}
footer {
  background-image: url('/image.jpg'); // old browsers
  background-image: var(--z-lazy-img, none);
}

/* base64 encoded value */
p#out-of-view {
  background-image: url('/image.jpg'); // old browsers
  background-image: var(--z-base64_value, none); /* note: requires character replacements, see documentation */
}

/* JSON object or javascript function based custom resolver */
div#out-of-view {
  background-image: url('/image.jpg'); // old browsers
  background-image: var(--z-custom-resolved, none); 
}
<script src="dist/lazybg.js"></script>
<script>
// default: document.styleSheets
$lazybg(); 

// custom $lazy config
$lazybg(
  document.querySelectorAll('link[rel=stylesheet], style#other'),
  {
    observer: {
      threshold: 0,
      rootMargin: '100px'
    }
  }
); 

// custom JSON based resolver
$lazybg(
  0,0, // default config

  // resolver
  {
    "custom-resolved": "url('/image.jpg')"
  }
); 

// custom javascript based resolver
$lazybg(
  0,0, // default config

  // resolver
  function(key) {

    // resolve key "custon-resolved"
    return "url('/"+key+".jpg');"
  }
); 
</script>

Base64 encoded image value

CSS variables are limited to DOMString. The following characters need to be replaced in a base64 encoded value:

/:

=:

Automatic .webp rewrite

It is possible to automatically load .webp images for browsers that support Google's WebP format. It saves a server-side redirect and it adds WebP support to the <img> tag.

Simply use the dist/lazy+webp... files to enable it.

The solution uses <img onerror> to fallback to the original image when the .webp image is 404.

To manually disable webp rewrites for an image add the HTML attribute data-webp="no". To disable it for a specific $lazy configuration, set the 4th argument to false.

$lazybg supports WebP rewrites by using dist/lazybg+webp.js.

data-l JSON config

To enable usage in combination with a strict Content-Security-Policy the script can be configured using a data-l attribute on the script source element.

<script data-l='{
   "selector": "[data-src*=&apos;cdn.domain.com&apos;]", 
   "observer": { 
      "threshold": [1.0],
      "trackVisibility": true,
      "delay": 100
   }
}'>
// dist/lazy-data-attr.js (source)
</script>

Multiple configurations are supported via the special multi-token ||. The token needs to be included at the begining and each configuration needs to be valid JSON.

||{config...}||{second config...}

Polyfill

$lazy includes a polyfill for IntersectionObserver. It can be automatically loaded when using $async.

Example using $async with data-c based config

<!-- data-c slot 5 to 8 for $async.js() -->
<script async src="dist/iife-async.js" data-c='[0,0,0,0,{
   "src": "/intersectionobserver-polyfill.js",
   "load_timing": {
      "type": "method",
      "method": "$lazypoly"
    },
    "cache": "localstorage"
},{
   "ref": "lazy",
   "src": "/lazy.js",
   "attributes": {
      "data-l": "[\".selector\", 0.006, \"0px\"]"
   },
    "load_timing": "domReady",
    "cache": "localstorage"
}]'></script>

In the example, the $async timing method method defines window.$lazypoly which will automatically load the polyfill for browsers that require the polyfill. It uses localStorage for instant loading.

Alternatively, when using $lazy without $async, you can manually define window.$lazypoly with a function that returns a Promise or a object containing a .then method.

window.$lazypoly = function() {

   // load polyfill
   // ...

   return {
      then: function(callback) {

         // wait until polyfill is loaded and resolve callback

         callback();
      }
   }
};

When using $async you can alternatively use window.$lazypoly with a string or a object to pass to $async.js which could load anything.

Alternatively, when including $lazy inline, the data-poly attribute enables to define a string to pass to $async.js.

<script data-l='... lazy config ...' data-poly='... config to pass to $async.js to load polyfill ...'>
// dist/lazy-data-attr+polyfill.js
</script>

Example Performance API timings

$lazy polyfill from localStorage