npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

browserux-lazy-img

v1.1.0

Published

**EN** | [FR](./fr/README.md)

Readme

EN | FR

BrowserUX Lazy Img

A lightweight Web Component for lazy-loading images with built-in loader, error fallback, and responsive layout support, independent of any framework or bundler.

It handles the full image loading lifecycle: placeholder background, animated spinner, smooth fade-in on load, and a graceful fallback on error, all encapsulated in a single custom element.

npm version

Table of Contents

Features

  • 🖼️ Lazy-loads images using the native IntersectionObserver API
  • 🎯 Three loading trigger modes: immediate, fully visible, or pixel offset from viewport
  • ⏳ Built-in animated spinner with configurable color
  • 🔁 Smooth fade-in transition on image load
  • 🧩 Custom loader via <slot name="loader">
  • ❌ Error fallback UI with custom icon via <slot name="fallback">
  • 📐 Responsive layout via aspect-ratio (responsive attribute)
  • 🎨 Background color while loading (background attribute)
  • 🌑 Shadow DOM encapsulation (disableable via no-shadow)
  • ♿ Accessible: role="img", aria-label, role="status" on loader
  • fetchpriority and decoding="async" hints passed to the native <img>
  • 🧱 Framework-agnostic: works in plain HTML, React, Vue, Angular, and more

How It Works

Loading Lifecycle

  1. The component renders immediately with a background placeholder and an animated spinner.
  2. An IntersectionObserver watches the element's position relative to the viewport.
  3. When the trigger condition is met, the image src is set and loading begins.
  4. On successful load, the spinner is hidden and the image fades in.
  5. On error, the image is hidden and a fallback UI is displayed.

Load Trigger Modes

The load-trigger attribute controls when loading begins:

| Value | Behavior | | ----------- | --------------------------------------------------------------------- | | auto | Loads as soon as the component is connected to the DOM (default) | | visible | Loads only when the component is fully visible in the viewport | | 200 | Loads when the component comes within 200px of the viewport edge |

Responsive Layout

When the responsive attribute is present, the component uses a padding-bottom ratio trick to maintain the aspect ratio defined by width and height. If no dimensions are provided, it falls back to a 16:9 ratio (56.25%).

Shadow DOM

By default, styles are encapsulated in a Shadow DOM. Add no-shadow to render in the light DOM, allowing global CSS to style the component directly.

Installation

npm install browserux-lazy-img

Or via CDN:

<script type="module" src="https://unpkg.com/browserux-lazy-img/dist/browserux-lazy-img.min.js"></script>

Use the .esm.js version when integrating with a bundler (React, Vue, etc.), and the .min.js version for direct HTML integration via CDN.

Usage

Modern project with a bundler (Vite, Webpack, etc.)

  1. Import the component in your entry file:
import 'browserux-lazy-img';
  1. Then use it in your HTML:
<browserux-lazy-img
  src="/img/photo.jpg"
  alt="A beautiful landscape"
  width="1200"
  height="630"
  responsive
></browserux-lazy-img>

React / Next.js

  1. In your component, load it dynamically:
import { useEffect } from 'react';

useEffect(() => {
  import('browserux-lazy-img');
}, []);
  1. Then in your JSX:
<browserux-lazy-img
  src="/img/photo.jpg"
  alt="A beautiful landscape"
  width="1200"
  height="630"
  responsive
></browserux-lazy-img>

Vue 3

  1. In main.js or main.ts:
import 'browserux-lazy-img';
  1. Use it as a custom HTML tag:
<browserux-lazy-img
  src="/img/photo.jpg"
  alt="A beautiful landscape"
  width="1200"
  height="630"
  responsive
></browserux-lazy-img>

Angular

  1. In main.ts:
import 'browserux-lazy-img';
  1. Add CUSTOM_ELEMENTS_SCHEMA to your AppModule:
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}

Integration without bundler / global script

  1. Directly include the component via CDN:
<script type="module" src="https://unpkg.com/browserux-lazy-img/dist/browserux-lazy-img.min.js"></script>
  1. Then add the tag:
<browserux-lazy-img
  src="/img/photo.jpg"
  alt="A beautiful landscape"
  width="1200"
  height="630"
></browserux-lazy-img>

Parameters of <browserux-lazy-img>

Attributes

| Attribute | Type | Default | Description | | ---------------- | --------- | ------------- | ---------------------------------------------------------------------------- | | src | string | — | Image source URL | | alt | string | "" | Alternative text for the image (accessibility) | | width | number | — | Image width in pixels (used for ratio calculation) | | height | number | — | Image height in pixels (used for ratio calculation) | | responsive | boolean | false | Enables aspect-ratio responsive layout (requires width and height) | | background | string | transparent | Background color shown while the image is loading (e.g. #f0f0f0) | | loader-color | string | — | Color of the default spinner SVG (e.g. #ffffff) | | loader-delay | number | 0 | Minimum time in ms the loader is shown before revealing the image | | load-trigger | string | auto | Loading trigger: auto, visible, or a pixel offset (e.g. 200) | | fetch-priority | string | auto | Native fetchpriority hint: auto, high, or low | | no-shadow | boolean | false | Disables Shadow DOM encapsulation (renders in light DOM) |

Example

<browserux-lazy-img
  src="/img/hero.jpg"
  alt="Hero image"
  width="1600"
  height="900"
  responsive
  background="#1a1a1a"
  loader-color="#ffffff"
  loader-delay="500"
  load-trigger="200"
  fetch-priority="high"
></browserux-lazy-img>

Load Trigger (load-trigger)

The load-trigger attribute accepts three types of value:

auto (default)

The image loads immediately when the component is connected to the DOM, regardless of its position in the viewport.

<browserux-lazy-img src="/img/photo.jpg" alt="Photo" load-trigger="auto"></browserux-lazy-img>

visible

The image loads only when the component is fully visible in the viewport (IntersectionObserver threshold of 1.0).

<browserux-lazy-img src="/img/photo.jpg" alt="Photo" load-trigger="visible"></browserux-lazy-img>

Pixel offset (e.g. 200)

The image starts loading when the component comes within the specified number of pixels from the viewport edge. Accepts positive (pre-load before visible) or negative (load after partially visible) values.

<browserux-lazy-img src="/img/photo.jpg" alt="Photo" load-trigger="200"></browserux-lazy-img>

Loader Delay (loader-delay)

By default, the spinner disappears as soon as the image is ready. If the image loads very fast (e.g. from cache), the spinner may flash briefly in an unpleasant way. loader-delay sets a minimum display time for the loader in milliseconds.

<browserux-lazy-img
  src="/img/photo.jpg"
  alt="Photo"
  loader-delay="800"
></browserux-lazy-img>

In this example, even if the image loads in 50ms, the spinner will remain visible for at least 800ms before fading in the image.

Responsive Layout (responsive)

Add the responsive attribute to make the component fill its container width while maintaining the correct aspect ratio. The ratio is calculated from width and height. If those are not provided, a 16:9 ratio is used as fallback.

<browserux-lazy-img
  src="/img/banner.jpg"
  alt="Banner"
  width="1200"
  height="400"
  responsive
></browserux-lazy-img>

Without responsive, the component renders at the fixed pixel dimensions defined by width and height.

Slots

loader

Replace the default animated SVG spinner with your own loader content.

<browserux-lazy-img src="/img/photo.jpg" alt="Photo" width="800" height="600">
  <div slot="loader">Loading…</div>
</browserux-lazy-img>

Or with a custom SVG:

<browserux-lazy-img src="/img/photo.jpg" alt="Photo" width="800" height="600">
  <svg slot="loader" viewBox="0 0 24 24" width="32" height="32">
    <circle cx="12" cy="12" r="10" stroke="orange" stroke-width="2" fill="none"/>
  </svg>
</browserux-lazy-img>

fallback

Override the default "Image not found" fallback displayed when the image fails to load.

<browserux-lazy-img src="/img/missing.jpg" alt="Photo" width="800" height="600">
  <svg slot="fallback" viewBox="0 0 24 24" width="48" height="48">
    <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
  </svg>
</browserux-lazy-img>

CSS Parts

When Shadow DOM is enabled (default), you can style internal elements from outside using ::part().

| Part name | Element | | --------- | -------------------- | | wrapper | Outer container div | | ratio | Inner ratio div | | img | The <img> element |

Example

browserux-lazy-img::part(img) {
  border-radius: 12px;
}

browserux-lazy-img::part(wrapper) {
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
}

::part() is not available when no-shadow is used. In that case, target the internal classes directly: .bux-lazy-images, .ratio-wrapper, .loader, .fallback.

Build & Development

npm install
npm run build

Uses TypeScript + Rollup to build:

  • dist/browserux-lazy-img.esm.js
  • dist/browserux-lazy-img.umd.js
  • dist/browserux-lazy-img.min.js
  • dist/browserux-lazy-img.d.ts

License

MIT License. Free to use, modify and distribute.