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.
Table of Contents
Features
- 🖼️ Lazy-loads images using the native
IntersectionObserverAPI - 🎯 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 (
responsiveattribute) - 🎨 Background color while loading (
backgroundattribute) - 🌑 Shadow DOM encapsulation (disableable via
no-shadow) - ♿ Accessible:
role="img",aria-label,role="status"on loader - ⚡
fetchpriorityanddecoding="async"hints passed to the native<img> - 🧱 Framework-agnostic: works in plain HTML, React, Vue, Angular, and more
How It Works
Loading Lifecycle
- The component renders immediately with a background placeholder and an animated spinner.
- An
IntersectionObserverwatches the element's position relative to the viewport. - When the trigger condition is met, the image
srcis set and loading begins. - On successful load, the spinner is hidden and the image fades in.
- 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-imgOr via CDN:
<script type="module" src="https://unpkg.com/browserux-lazy-img/dist/browserux-lazy-img.min.js"></script>Use the
.esm.jsversion when integrating with a bundler (React, Vue, etc.), and the.min.jsversion for direct HTML integration via CDN.
Usage
Modern project with a bundler (Vite, Webpack, etc.)
- Import the component in your entry file:
import 'browserux-lazy-img';- 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
- In your component, load it dynamically:
import { useEffect } from 'react';
useEffect(() => {
import('browserux-lazy-img');
}, []);- 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
- In
main.jsormain.ts:
import 'browserux-lazy-img';- 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
- In
main.ts:
import 'browserux-lazy-img';- Add
CUSTOM_ELEMENTS_SCHEMAto yourAppModule:
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}Integration without bundler / global script
- Directly include the component via CDN:
<script type="module" src="https://unpkg.com/browserux-lazy-img/dist/browserux-lazy-img.min.js"></script>- 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 whenno-shadowis used. In that case, target the internal classes directly:.bux-lazy-images,.ratio-wrapper,.loader,.fallback.
Build & Development
npm install
npm run buildUses TypeScript + Rollup to build:
dist/browserux-lazy-img.esm.jsdist/browserux-lazy-img.umd.jsdist/browserux-lazy-img.min.jsdist/browserux-lazy-img.d.ts
License
MIT License. Free to use, modify and distribute.
