# verlok/vanilla-lazyload: A 2.4 kB Lazy Loading Script for Images, Iframes, Videos and Scripts

> LazyLoad defers below-the-fold images, backgrounds, videos, iframes and animated SVGs until they enter the viewport. It is a small, dependency-free script for teams that want explicit control over what loads when, without adopting a framework.

**verlok/vanilla-lazyload** — LazyLoad is a lightweight, flexible script that speeds up your website by deferring the loading of your below-the-fold images, backgrounds, videos, iframes and scripts to when they will enter the viewport. Written in plain "vanilla" JavaScript, it leverages IntersectionObserver, supports responsive images and enables native lazy loading.

- Repository: https://github.com/verlok/vanilla-lazyload
- Website: https://www.andreaverlicchi.eu/vanilla-lazyload/
- Stars: 7,854 · Forks: 673
- Language: JavaScript
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/verlok-vanilla-lazyload

## What vanilla-lazyload actually defers

The README describes LazyLoad as a lightweight script, 2.4 kB, that speeds up a web application by deferring the loading of below-the-fold images, animated SVGs, videos and iframes until they enter the viewport. The package description adds backgrounds to that list. The target audience is a developer maintaining a server-rendered or statically generated site who wants lazy loading without pulling in a framework runtime.

The mechanism is opt-in per element. You do not add LazyLoad and get lazy behaviour everywhere. You mark each element with a class, conventionally lazy, and move the real URL into a data attribute. An image whose source sits in data-src loads nothing until LazyLoad swaps the attribute. That is the central design decision, and it explains both the small size and the main friction: existing markup has to be edited, or generated with the data attributes in place.

Because the script is plain JavaScript with no runtime dependencies, it drops into a page that has no build step. It also ships an ES module build and TypeScript typings, so it fits a bundler workflow. Both paths lead to the same constructor.

## How IntersectionObserver drives the swap

The README states that LazyLoad leverages the IntersectionObserver API. That is the whole engine. The browser reports when a watched element crosses into the viewport, and LazyLoad then moves the URL from the data attribute to the real attribute, which triggers the network request.

This matters for how you reason about page load. The script itself is not polling scroll position, so there is no scroll handler running on every frame. The cost is paid by the browser's observer machinery instead. The trade-off is that anything the observer does not see, it does not load. Elements inside a container that never intersects, a hidden tab panel, a collapsed accordion, stay unloaded. That is usually the intent, but it surprises people who expect the full DOM to be populated after load.

The second consequence is the update call. The README says that if more DOM arrives later, for example via an AJAX call, you need to call lazyLoadInstance.update() so LazyLoad checks the DOM again. Newly inserted elements are invisible to the existing observer until you do. This is the most common integration mistake with any observer-based loader, and the documentation addresses it directly.

The README also notes that LazyLoad can enable native lazy loading and supports responsive images. Responsive handling is expressed in markup: data-srcset and data-sizes mirror the standard srcset and sizes attributes, and the picture element is supported with per-source data-srcset. LazyLoad reads those attributes and transfers them. It does not generate candidate URLs for you.

## Installing vanilla-lazyload and loading a first image

The README gives two distribution paths. The simplest is a script tag pointing at the jsDelivr CDN, with the version pinned in the URL. The README names 19.1.3 as the latest recommended version.

```html
<script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@19.1.3/dist/lazyload.min.js"></script>
```

If you prefer an ES module, the README shows the same CDN with the +esm suffix.

```html
<script type="module">
  import LazyLoad from "https://cdn.jsdelivr.net/npm/vanilla-lazyload@19.0.3/+esm";
</script>
```

After the script is present, you instantiate it. The README shows an empty options object as the starting point.

```js
var lazyLoadInstance = new LazyLoad({
  // Your custom settings go here
});
```

The README is explicit about placement: put the script tag right before the closing body tag so the DOM for your lazy content is ready when you instantiate LazyLoad. Then change your markup. A lazy image moves its URL into data-src and carries the lazy class.

```html
<img alt="A lazy image" class="lazy" data-src="lazy.jpg" />
```

For a responsive image, add data-srcset and data-sizes alongside data-src. For a low quality placeholder, keep a small file in the ordinary src attribute, which the README shows as src="lazy-lowQuality.jpg". For a lazy iframe, the README shows the same pattern with data-src on the iframe element. The README also documents an async script pattern, where you must define the options before including the script, passing either a single object or an array of objects for multiple instances.

## Where vanilla-lazyload is the wrong tool

The clearest boundary is Internet Explorer 11. The README states that if you need to support IE11, you need version 17.9.0 or below. Anyone on the current 19.x line has already given up that support. If your audience still includes IE11, you are maintaining an old release, not adopting this one.

The second boundary is markup ownership. LazyLoad works by rewriting attributes. If your images come from a CMS plugin, a rich text field, or a third-party embed that emits standard src attributes, you cannot make them lazy without a transformation step. There is no documented server-side filter or automatic DOM scan that converts src to data-src for you. You either control the template or you write the conversion yourself.

The third is the hidden-element case already described. Content inside a display:none container, a tab that is never opened, or a carousel slide that never becomes visible will not load. For a print stylesheet, or for any workflow that expects the full DOM to be present for scraping or for a headless render, that is a real failure mode rather than a performance win.

Finally, the README's own note about background images is worth reading before you reach for data-bg. It advises using the img tag for content images and reserving backgrounds for decorative ones, framing the question as whether a user would want to see the image when printing the page. If your images are content, the background path is the wrong one even though the API supports it.

## How it differs from native loading="lazy" and framework plugins

The obvious alternative is the browser's own loading="lazy" attribute on img and iframe. The difference in approach is that native lazy loading is a single attribute the browser interprets, with the threshold decided by the browser vendor and no JavaScript involved. LazyLoad is a script you configure, and the README says it can enable native lazy loading in addition to its own behaviour. The practical split: native covers plain images and iframes and needs no code, while LazyLoad covers background images, image-set backgrounds, multiple backgrounds with HiDPI variants, animated SVG via object, and per-source data-srcset inside picture. If your page is images and iframes only, native is less machinery. If you have background art or multi-source picture markup, native has no equivalent attribute and LazyLoad does.

A second alternative is a framework-level image component, for example the image component that ships with a JavaScript framework's meta-framework. Those components usually take over URL generation, resizing and format negotiation through a build-time pipeline. LazyLoad does none of that. It moves attributes you have already written. That is a smaller commitment and a smaller capability set. If you need automatic resizing and format conversion, a build-integrated image component does more; if you already have your assets and sizes settled, LazyLoad stays out of the way. Note that the README does not document a first-party React wrapper, so a React codebase uses the same constructor and calls update() after render.

## Version pinning, licence and upgrade cost

The project is MIT licensed, per the repository's LICENSE file. That permits commercial and closed-source use with the licence and copyright notice retained. This is a statement about the licence identifier, not legal advice; check the LICENSE file in the repository for the exact terms.

Upgrade cost is the part worth budgeting for. The README points to UPGRADE.md for a practical upgrade guide, and the release history shows a major version bump to 18.0.0 in March 2024, then 19.0.5 and 19.1.3 in the weeks after. Major versions in a project like this are where the attribute contract can change, so read UPGRADE.md before moving across a major boundary rather than assuming the data-src markup is untouched. The README also states the IE11 cutoff at 17.9.0, which is the one upgrade you cannot make if that support is still required.

The package is published with a main build, an ESM build, a browser field and TypeScript typings, and the published files list is limited to dist, typings and CHANGELOG.md. The repository's last push was on 2026-09-11, which is recent. The latest release listed is 19.1.3 from 2024-04-02, so the release cadence and the commit activity are not moving at the same rate; check the CHANGELOG before assuming a given fix has shipped in a tagged version.

## Conclusion

Adopt vanilla-lazyload if you serve content-heavy pages with below-the-fold images, iframes or videos and you want to control exactly which elements are deferred. Skip it if you need Internet Explorer 11 support, because the README states you must stay on version 17.9.0 or below, or if native loading="lazy" already covers your markup and you do not need background or multi-source handling. Before adopting, verify that your markup uses data-src rather than src, and confirm which version you are pinned to.

## FAQ

### What does vanilla-lazyload do?

It defers the loading of below-the-fold images, backgrounds, videos, iframes and animated SVGs until they enter the viewport. It does this by reading URLs from data attributes and moving them to the real attributes when an IntersectionObserver reports the element is visible.

### Is lazy loading good or bad?

The README frames it as a speed optimisation for slower connections and for content below the fold. The trade-off it implies is that elements which never intersect, such as content in a hidden container, are never loaded, so lazy loading is a poor fit when the full DOM must be present.

### What is Vanilla JS, and is vanilla-lazyload written in it?

The README describes LazyLoad as written in plain "vanilla" JavaScript, meaning it has no dependency on a framework or a runtime library. It also ships an ES module build and TypeScript typings for bundler workflows.

### What does lazy loading mean in React, and how does it relate to vanilla-lazyload?

The README does not document a React wrapper. The general pattern it gives is to instantiate LazyLoad once the DOM for the lazy content exists, and to call lazyLoadInstance.update() when more DOM arrives later, which is what a React render produces.

## Sources

- [License: MIT](https://github.com/verlok/vanilla-lazyload/blob/master/LICENSE)
- [Project website](https://www.andreaverlicchi.eu/vanilla-lazyload/)
- [README](https://github.com/verlok/vanilla-lazyload/blob/master/README.md)
- [Releases](https://github.com/verlok/vanilla-lazyload/releases)
- [verlok/vanilla-lazyload on GitHub](https://github.com/verlok/vanilla-lazyload)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/verlok-vanilla-lazyload
