Skip to content

Lazy Load Images on Intersection

Problem

An image should not receive its full source until it approaches the viewport. createIntersection() provides normalized intersection state and explicit observer ownership.

Solution

Observe an image, assign its source once, then release the observer immediately.

ts
import { createIntersection, SentinelUnavailableError } from '@vielzeug/sentinel';

const image = document.createElement('img');
image.alt = 'Blue placeholder';
image.dataset.src =
  'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="640" height="360"%3E%3Crect width="100%25" height="100%25" fill="%23007acc"/%3E%3C/svg%3E';
document.body.append(image);

try {
  const intersection = createIntersection(image, { rootMargin: '200px' });
  const unsubscribe = intersection.subscribe(() => {
    if (!intersection.value?.isIntersecting || !image.dataset.src) return;

    image.src = image.dataset.src;
    delete image.dataset.src;
    unsubscribe();
    intersection.dispose();
  });
} catch (error) {
  if (!(error instanceof SentinelUnavailableError)) throw error;
  image.src = image.dataset.src ?? '';
}

Pitfalls

  • Use native loading="lazy" when it provides enough control.
  • Reserve image dimensions to prevent layout shift.
  • Dispose after the one-time load to avoid retaining the element.