lazy-img Web Component Demo

Web component that enables you tolazy load images based on container size, viewport size, or scroll position. See the README for installation and API documentation.

Performance Benefit: Unlike picture or srcset which always load some image, lazy-img can completely skip loading images on screens below your threshold. This saves bandwidth and improves performance for users on smaller devices or slower connections.

Note on Resize Behavior: Once an image is loaded, it remains loaded even if the viewport or container is resized below the threshold. This is intentional for performance — the component prevents unnecessary downloads, but doesn't unload images that are already in memory. Use the loaded and qualifies attributes to control visibility with CSS if needed.

Basic Example

Simple image that loads immediately (no conditions).

💡 Performance Note: While this pattern loads the image immediately (like a standard img), it still provides a benefit: if JavaScript fails to load or execute, the image won't load at all. This can be desirable for non-critical, optional images that enhance but aren't essential to the content (e.g., decorative images, supplementary graphics, or marketing banners).

⚠️ Important: Only use this pattern for non-critical images that aren't referenced in your content. Critical images that are part of your content should use standard img tags to ensure they load even when JavaScript is unavailable.

Basic Usage

<lazy-img
  src="https://picsum.photos/800/400"
  alt="Placeholder image">
</lazy-img>

Container Query Example (Default)

💡 Key Feature: Images only load when the container reaches the minimum size. If the container never gets that big, the image never loads — saving bandwidth! The default query type is "container".

Try it: Each container below starts smaller than needed. Drag the resize handle (bottom-right corner) to widen the container and watch the image lazy load when it hits the threshold.

Small Container (min-inline-size: 300px)

👉 Widen this container to 300px to see the image load. It starts at 200px (too small), so the image won't load until you resize it.

<lazy-img
  src="https://picsum.photos/600/300?random=1"
  alt="Landscape photo"
  min-inline-size="300">
</lazy-img>
📏 Current width: 200px / Min: 300px

Medium Container (min-inline-size: 500px)

👉 Widen this container to 500px to trigger the image load.

<lazy-img
  src="https://picsum.photos/800/400?random=2"
  alt="Nature photo"
  min-inline-size="500">
</lazy-img>
📏 Current width: 350px / Min: 500px

Media Query Example

Image loads based on viewport width. Uses query="media" to check window width instead of container width. This is perfect for saving bandwidth on mobile devices — if the viewport is below 768px, this image never loads.

Try it: Resize your browser window to see the effect. The image loads when the viewport reaches 768px wide.

Viewport Width Check (min-inline-size: 768px)

<lazy-img
  src="https://picsum.photos/1000/500?random=3"
  alt="Wide landscape photo"
  min-inline-size="768"
  query="media">
</lazy-img>

Current viewport width: px

View Mode (Scroll-Based Loading)

Image loads when scrolled into view using IntersectionObserver. Uses query="view" for scroll-based lazy loading. This is the classic "lazy load as you scroll" pattern — images only load when they become visible or are about to become visible.

Try it: Scroll down to see images load as they enter the viewport. The timing can be controlled with view-range-start.

Basic View Mode (default: entry 0%)

Loads as soon as any part of the image enters the viewport.

<lazy-img
  src="https://picsum.photos/800/400?random=8"
  alt="Scroll-based photo"
  query="view">
</lazy-img>

👇 Scroll down to see the image below load

Image not loaded yet

Threshold-Based (entry 50%)

Loads when 50% of the image is visible.

<lazy-img
  src="https://picsum.photos/800/400?random=9"
  alt="Half-visible trigger"
  query="view"
  view-range-start="entry 50%">
</lazy-img>

👇 Scroll until 50% of the image is visible

Image not loaded yet

Preload Before Visible (entry -200px)

Starts loading 200px before the image enters the viewport (smooth UX).

<lazy-img
  src="https://picsum.photos/800/400?random=10"
  alt="Preloaded photo"
  query="view"
  view-range-start="entry -200px">
</lazy-img>

👇 Image loads 200px before you see it

Image not loaded yet

Responsive Images with srcset and sizes

Combine lazy loading with responsive images using srcset and sizes attributes.

srcset with sizes

👉 Widen this container to 400px to load the responsive image. Once loaded, the browser chooses the appropriate size from srcset.

<lazy-img
  src="https://picsum.photos/800/400?random=4"
  srcset="https://picsum.photos/400/200?random=4 400w,
          https://picsum.photos/800/400?random=4 800w,
          https://picsum.photos/1200/600?random=4 1200w"
  sizes="(max-width: 600px) 400px,
         (max-width: 1000px) 800px,
         1200px"
  alt="Responsive photo"
  min-inline-size="400">
</lazy-img>
📏 Current width: 300px / Min: 400px

Named Breakpoints (CSS Custom Property)

Use the --lazy-img-mq CSS custom property on :root to define named breakpoints. Set different values at different media queries, and images will load when the breakpoint name matches.

Example with Named Breakpoints

This demo sets --lazy-img-mq via CSS media queries. Try resizing your browser window to see different breakpoint values.

<style>
  :root {
    --lazy-img-mq: small;
  }
  @media (min-width: 768px) {
    :root {
      --lazy-img-mq: medium;
    }
  }
  @media (min-width: 1024px) {
    :root {
      --lazy-img-mq: large;
    }
  }
</style>

<lazy-img
  src="https://picsum.photos/1000/500?random=5"
  alt="Scenic photo"
  named-breakpoints="medium, large"
  query="media">
</lazy-img>

Current breakpoint: small

Image loads when breakpoint is "medium" or "large" (viewport ≥ 768px)

Events

The component dispatches a lazy-img:loaded event when the image loads.

Event Example

👉 Widen this container to 350px to trigger the load event.

<lazy-img
  id="event-demo"
  src="https://picsum.photos/600/300?random=6"
  alt="Event demo photo"
  min-inline-size="350">
</lazy-img>

<script>
document.getElementById('event-demo')
  .addEventListener('lazy-img:loaded', (e) => {
    console.log('Image loaded:', e.detail.src);
  });
</script>
📏 Current width: 250px / Min: 350px

Event status: waiting for load...

State Attributes

The component provides two boolean attributes to track state: loaded (has the image been loaded?) and qualifies (does it currently meet conditions to show?). These allow you to control visibility with CSS.

Controlling Visibility

This example hides images that loaded but no longer qualify (e.g., after device rotation or resize).

<style>
  /* Hide loaded images that don't qualify */
  lazy-img[loaded]:not([qualifies]) {
    display: none;
  }

  /* Show placeholder when qualifies but not loaded */
  lazy-img[qualifies]:not([loaded])::before {
    content: '⏳ Waiting to load...';
    display: block;
    padding: 2em;
    background: #f0f0f0;
  }
</style>

<lazy-img
  src="https://picsum.photos/600/300"
  alt="State demo"
  min-inline-size="400">
</lazy-img>
📏 Current width: 300px / Min: 400px
State: Not loaded, doesn't qualify

API Reference

For complete documentation of attributes, events, and browser support, see the README.