Enhancement

Lazysrc

Lazy image loading built on the browser's own loading="lazy"

Import
@parallelogram-js/core/components/Lazysrc
Selector
[data-lazysrc]:not([data-lazysrc-complete])
Stylesheet
@parallelogram-js/core/styles/lazysrc.css

Playground

Real sources work without JavaScript; Lazysrc adds native lazy loading.

Harbour at dawn
<img data-lazysrc
     src="images/harbour-640.jpg"
     srcset="images/harbour-640.jpg 640w, images/harbour-1280.jpg 1280w"
     sizes="(max-width: 640px) 100vw, 640px"
     width="640" height="400" alt="Harbour at dawn">

Loads through an observer shortly before it scrolls into view.

Book a table by the water

<div class="hero" data-lazysrc data-lazysrc-bg="images/harbour-1280.jpg">
  <p>Book a table by the water</p>
</div>

About

The browser decides when images load. Markup that already has a real src or srcset is never stripped, so it works without JavaScript; Lazysrc only adds loading="lazy" and decoding="async" when they are missing. Sources held in data attributes, on the image or on the <source> elements of its <picture>, are copied onto the elements as soon as the image mounts. Without JavaScript those images stay empty, so follow them with a <noscript> copy that has real sources, such as <noscript><img src="harbour.jpg" alt="Harbour at dawn"></noscript>. Background images have no native lazy loading, so they load through an IntersectionObserver shortly before they scroll into view.

Failed loads are retried with a growing delay. Don't lazy load the largest image above the fold: give it a plain src and fetchpriority="high" instead.

Usage

Register the component and the framework loads it the first time a page contains [data-lazysrc]:not([data-lazysrc-complete]).

import { Parallelogram } from '@parallelogram-js/core';

const app = Parallelogram.create();
app.components.add('[data-lazysrc]:not([data-lazysrc-complete])', () => import('@parallelogram-js/core/components/Lazysrc'));
app.run();

The framework is optional. Lazysrc.enhanceAll() mounts the component on every matching element by itself, for a page that wants one enhancement rather than a framework:

import Lazysrc from '@parallelogram-js/core/components/Lazysrc';

Lazysrc.enhanceAll();

It takes a selector and the component's options, and returns the instance it mounted them with. What it leaves out is what the framework adds: loading a component only on the pages that use it, mounting again after a page swap, and ordering with dependsOn.

Its styles come from the package stylesheet:

@import '@parallelogram-js/core/styles/lazysrc.css';

Attributes

NameTypeDefaultDescription
data-lazysrcflagnoneMarks an image, or an element with a background image
data-lazysrc-srcurlnoneSource copied onto the image
data-lazysrc-srcsetstringnoneSource set copied onto the image or a <source>
data-lazysrc-sizesstringnoneSizes copied onto the image or a <source>
data-lazysrc-bgurlnoneBackground image, loaded shortly before the element scrolls into view
data-lazysrc-fetchpriorityhigh | low | autononeCopied to the image's fetchpriority
data-lazysrc-root-marginstring600px 0pxHow far outside the viewport background images start loading For every instance, Lazysrc.defaults.rootMargin.
data-lazysrc-thresholdnumber0Visible fraction at which a background image starts loading For every instance, Lazysrc.defaults.threshold.
data-lazysrc-retry-attemptsnumber3Retries after a failed load For every instance, Lazysrc.defaults.retryAttempts.
data-lazysrc-retry-delaynumber1000Milliseconds before the first retry, multiplied by the attempt number after For every instance, Lazysrc.defaults.retryDelay.
data-lazysrc-fade-durationnumber300Fade length in milliseconds, set as --lazy-transition-duration For every instance, Lazysrc.defaults.fadeInDuration.
data-lazysrc-loading-classstringlazysrc--loadingClass added while loading For every instance, Lazysrc.defaults.loadingClass.
data-lazysrc-loaded-classstringlazysrc--loadedClass added once loaded For every instance, Lazysrc.defaults.loadedClass.
data-lazysrc-error-classstringlazysrc--errorClass added when every retry failed For every instance, Lazysrc.defaults.errorClass.
data-lazysrc-stateset by the componentloading | loaded | errornoneLoading progress

Events

NameDetailDescription
lazysrc:mounted{ element: HTMLElement; config: object; timestamp: number }The element was set up DOM event and event bus
lazysrc:loading-start{ element: HTMLElement; timestamp: number }Sources were handed to the browser, or a background image started loading DOM event and event bus
lazysrc:loaded{ element: HTMLElement; loadTime: number | null; timestamp: number }The image loaded; loadTime is the download time from Resource Timing, or null when the browser has no entry for it DOM event and event bus
lazysrc:error{ element: HTMLElement; error: string; timestamp: number }Every retry failed, or the element has no source to load DOM event and event bus
lazysrc:detached{ element: HTMLElement; timestamp: number }A loaded element's listeners were released DOM event and event bus
lazysrc:forceLoadnoneDispatch this on an element to load it straight away You dispatch this.

CSS custom properties

NameDefaultDescription
--lazy-transition-duration0.3sLength of the fade
--lazy-transition-easingnoneEasing of the fade
--lazy-loading-opacity0.7Opacity while loading
--lazy-placeholder-bgvar(--color-surface-muted)Background while loading: the muted surface
--lazy-error-bgvar(--color-danger-tint)Background after an error: the danger tint
--lazy-error-colorvar(--color-danger)Outline colour after an error: the danger colour