Skip to content
A lightweight lazy loader based on `window.IntersectionObserver` with tiny fallback for old browsers.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

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.

   selector, /* string, Node, NodeList or observer config object */
   callback /* optional: custom in-view callback for manual lazy loading */

Documentation is available on

Install via npm

npm install --save

Install via PHP Composer

composer require styletools/lazy


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

Simple config

   "selector": "[data-src]",
   "threshold": 0.006,
   "rootMargin": "0px"

Custom observer config

   "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

["[data-src]", 0.006, "0px"]

Custom observer config

["[data-src]", {
   "threshold": 0.006,
   "rootMargin": "0px",
   "trackVisibility": true,
   "delay": 100


When an element enters the viewport the event $lazy is fired on the element. The event contains a reference to the HTML node and the IntersectionObserverEntry.

// listen for $lazy event on any element
$(document)[0].addEventListener('$lazy', console.log);

// selectively listen for $lazy event on images using jQuery
jQuery('img').on('$lazy', console.log);

The event data is available via event.detail.

$lazy event

Custom callback

When using a custom callback the $lazy script essentially functions as a simple in-view solution that can be used for many purposes.

$lazy('div#id', 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

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;;]", 
   "observer": { 
      "threshold": [1.0],
      "trackVisibility": true,
      "delay": 100
// dist/lazy-data-attr.js (source)

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...}


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

Example using $async with data-c based config

<script data-c='[
    "ref": "lazy",
    "src": "dist/lazy-data-attr+polyfill.js",
    "attributes": {
     "data-l": "[\"selector\", 0.006, \"0px\"]"
    "load_timing": "domReady",
    "cache": "localstorage"
    "ref": "lazy-polyfill",
    "src": "dist/intersectionobserver-polyfill.js",
    "load_timing": {
      "type": "method",
      "method": "$lazypoly"
    "cache": "localstorage"
/* $async IIFE with timing and API module */

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


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

Example Performance API timings

$lazy polyfill from localStorage

You can’t perform that action at this time.