← home

Using the Enhanced Lazy Image Component

Using the Enhanced Lazy Image Component

Overview

The enhanced lazy-image.html component now supports WebP format with responsive images, providing optimal performance across all devices and browsers.

Basic Usage

In your blog posts or pages, include images like this:

















<div class="lazy-image-wrapper">
  <picture>
    
    <source
      type="image/webp"
      data-srcset="/assets/images/posts/boracay-beach-640w.webp 640w,
                   /assets/images/posts/boracay-beach-1280w.webp 1280w,
                   /assets/images/posts/boracay-beach-1920w.webp 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <source
      type="image/jpeg"
      data-srcset="/assets/images/posts/boracay-beach-640w.jpg 640w,
                   /assets/images/posts/boracay-beach-1280w.jpg 1280w,
                   /assets/images/posts/boracay-beach-1920w.jpg 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <img
      data-src="/assets/images/posts/boracay-beach./assets/images/posts/boracay-beach"
      alt="White sand beach in Boracay with crystal clear turquoise water"
      class="lazy-image "
      loading="lazy"
      width="1920"
      height="1440"
    />
  </picture>

  <noscript>
    <picture>
      <source
        type="image/webp"
        srcset="/assets/images/posts/boracay-beach-640w.webp 640w,
                /assets/images/posts/boracay-beach-1280w.webp 1280w,
                /assets/images/posts/boracay-beach-1920w.webp 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
      />
      <img
        src="/assets/images/posts/boracay-beach./assets/images/posts/boracay-beach"
        srcset="/assets/images/posts/boracay-beach-640w.jpg 640w,
                /assets/images/posts/boracay-beach-1280w.jpg 1280w,
                /assets/images/posts/boracay-beach-1920w.jpg 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
        alt="White sand beach in Boracay with crystal clear turquoise water"
        class=""
        width="1920"
        height="1440"
      />
    </picture>
  </noscript>
</div>

<script>
(function() {
  if (!window.lazyImageObserver) {
    window.lazyImageObserver = {
      init: function() {
        var images = document.querySelectorAll('img.lazy-image[data-src]');
        var sources = document.querySelectorAll('source[data-srcset]');

        // Function to load an image and its sources
        var loadImage = function(img) {
          // Load source elements first
          var picture = img.closest('picture');
          if (picture) {
            var pictureSources = picture.querySelectorAll('source[data-srcset]');
            pictureSources.forEach(function(source) {
              if (source.dataset.srcset) {
                source.srcset = source.dataset.srcset;
                source.removeAttribute('data-srcset');
              }
            });
          }

          // Load img element
          if (img.dataset.src) {
            img.src = img.dataset.src;
            img.removeAttribute('data-src');
            img.classList.add('lazy-loaded');

            if (window.performance && window.performance.mark) {
              performance.mark('image-loaded');
            }
          }
        };

        if ('loading' in HTMLImageElement.prototype) {
          // Native lazy loading support
          images.forEach(function(img) {
            loadImage(img);
          });
        } else if ('IntersectionObserver' in window) {
          // IntersectionObserver fallback
          var imageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
              if (entry.isIntersecting) {
                var img = entry.target;
                loadImage(img);
                observer.unobserve(img);
              }
            });
          }, {
            rootMargin: '50px'
          });

          images.forEach(function(img) {
            imageObserver.observe(img);
          });
        } else {
          // Fallback for older browsers - load everything
          images.forEach(function(img) {
            loadImage(img);
          });
        }
      }
    };

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', window.lazyImageObserver.init);
    } else {
      window.lazyImageObserver.init();
    }
  }
})();
</script>

Important Notes:

How It Works

The component automatically generates this HTML:

<picture>
  <!-- WebP sources for modern browsers -->
  <source type="image/webp"
          srcset="/assets/images/posts/boracay-beach-640w.webp 640w,
                  /assets/images/posts/boracay-beach-1280w.webp 1280w,
                  /assets/images/posts/boracay-beach-1920w.webp 1920w"
          sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px" />

  <!-- JPEG fallback for older browsers -->
  <source type="image/jpeg"
          srcset="/assets/images/posts/boracay-beach-640w.jpg 640w,
                  /assets/images/posts/boracay-beach-1280w.jpg 1280w,
                  /assets/images/posts/boracay-beach-1920w.jpg 1920w"
          sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px" />

  <!-- Main image tag -->
  <img src="/assets/images/posts/boracay-beach.jpg"
       alt="White sand beach in Boracay with crystal clear turquoise water"
       loading="lazy"
       width="1920"
       height="1440" />
</picture>

What Gets Loaded

On a Modern Desktop (Chrome, Firefox, Edge, Safari)

On Mobile (iPhone, Android)

On Older Browsers (IE11, older Safari)

Custom Sizes

You can override the default sizes attribute:

















<div class="lazy-image-wrapper">
  <picture>
    
    <source
      type="image/webp"
      data-srcset="/assets/images/posts/photo-640w.webp 640w,
                   /assets/images/posts/photo-1280w.webp 1280w,
                   /assets/images/posts/photo-1920w.webp 1920w"
      sizes="(max-width: 768px) 100vw, 50vw"
    />

    
    <source
      type="image/jpeg"
      data-srcset="/assets/images/posts/photo-640w.jpg 640w,
                   /assets/images/posts/photo-1280w.jpg 1280w,
                   /assets/images/posts/photo-1920w.jpg 1920w"
      sizes="(max-width: 768px) 100vw, 50vw"
    />

    
    <img
      data-src="/assets/images/posts/photo./assets/images/posts/photo"
      alt="Description"
      class="lazy-image "
      loading="lazy"
      width="1920"
      height="1080"
    />
  </picture>

  <noscript>
    <picture>
      <source
        type="image/webp"
        srcset="/assets/images/posts/photo-640w.webp 640w,
                /assets/images/posts/photo-1280w.webp 1280w,
                /assets/images/posts/photo-1920w.webp 1920w"
        sizes="(max-width: 768px) 100vw, 50vw"
      />
      <img
        src="/assets/images/posts/photo./assets/images/posts/photo"
        srcset="/assets/images/posts/photo-640w.jpg 640w,
                /assets/images/posts/photo-1280w.jpg 1280w,
                /assets/images/posts/photo-1920w.jpg 1920w"
        sizes="(max-width: 768px) 100vw, 50vw"
        alt="Description"
        class=""
        width="1920"
        height="1080"
      />
    </picture>
  </noscript>
</div>

<script>
(function() {
  if (!window.lazyImageObserver) {
    window.lazyImageObserver = {
      init: function() {
        var images = document.querySelectorAll('img.lazy-image[data-src]');
        var sources = document.querySelectorAll('source[data-srcset]');

        // Function to load an image and its sources
        var loadImage = function(img) {
          // Load source elements first
          var picture = img.closest('picture');
          if (picture) {
            var pictureSources = picture.querySelectorAll('source[data-srcset]');
            pictureSources.forEach(function(source) {
              if (source.dataset.srcset) {
                source.srcset = source.dataset.srcset;
                source.removeAttribute('data-srcset');
              }
            });
          }

          // Load img element
          if (img.dataset.src) {
            img.src = img.dataset.src;
            img.removeAttribute('data-src');
            img.classList.add('lazy-loaded');

            if (window.performance && window.performance.mark) {
              performance.mark('image-loaded');
            }
          }
        };

        if ('loading' in HTMLImageElement.prototype) {
          // Native lazy loading support
          images.forEach(function(img) {
            loadImage(img);
          });
        } else if ('IntersectionObserver' in window) {
          // IntersectionObserver fallback
          var imageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
              if (entry.isIntersecting) {
                var img = entry.target;
                loadImage(img);
                observer.unobserve(img);
              }
            });
          }, {
            rootMargin: '50px'
          });

          images.forEach(function(img) {
            imageObserver.observe(img);
          });
        } else {
          // Fallback for older browsers - load everything
          images.forEach(function(img) {
            loadImage(img);
          });
        }
      }
    };

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', window.lazyImageObserver.init);
    } else {
      window.lazyImageObserver.init();
    }
  }
})();
</script>

This tells the browser:

Additional CSS Classes

Add custom classes for styling:

















<div class="lazy-image-wrapper">
  <picture>
    
    <source
      type="image/webp"
      data-srcset="/assets/images/posts/photo-640w.webp 640w,
                   /assets/images/posts/photo-1280w.webp 1280w,
                   /assets/images/posts/photo-1920w.webp 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <source
      type="image/jpeg"
      data-srcset="/assets/images/posts/photo-640w.jpg 640w,
                   /assets/images/posts/photo-1280w.jpg 1280w,
                   /assets/images/posts/photo-1920w.jpg 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <img
      data-src="/assets/images/posts/photo./assets/images/posts/photo"
      alt="Description"
      class="lazy-image rounded shadow-lg"
      loading="lazy"
      width="1920"
      height="1080"
    />
  </picture>

  <noscript>
    <picture>
      <source
        type="image/webp"
        srcset="/assets/images/posts/photo-640w.webp 640w,
                /assets/images/posts/photo-1280w.webp 1280w,
                /assets/images/posts/photo-1920w.webp 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
      />
      <img
        src="/assets/images/posts/photo./assets/images/posts/photo"
        srcset="/assets/images/posts/photo-640w.jpg 640w,
                /assets/images/posts/photo-1280w.jpg 1280w,
                /assets/images/posts/photo-1920w.jpg 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
        alt="Description"
        class="rounded shadow-lg"
        width="1920"
        height="1080"
      />
    </picture>
  </noscript>
</div>

<script>
(function() {
  if (!window.lazyImageObserver) {
    window.lazyImageObserver = {
      init: function() {
        var images = document.querySelectorAll('img.lazy-image[data-src]');
        var sources = document.querySelectorAll('source[data-srcset]');

        // Function to load an image and its sources
        var loadImage = function(img) {
          // Load source elements first
          var picture = img.closest('picture');
          if (picture) {
            var pictureSources = picture.querySelectorAll('source[data-srcset]');
            pictureSources.forEach(function(source) {
              if (source.dataset.srcset) {
                source.srcset = source.dataset.srcset;
                source.removeAttribute('data-srcset');
              }
            });
          }

          // Load img element
          if (img.dataset.src) {
            img.src = img.dataset.src;
            img.removeAttribute('data-src');
            img.classList.add('lazy-loaded');

            if (window.performance && window.performance.mark) {
              performance.mark('image-loaded');
            }
          }
        };

        if ('loading' in HTMLImageElement.prototype) {
          // Native lazy loading support
          images.forEach(function(img) {
            loadImage(img);
          });
        } else if ('IntersectionObserver' in window) {
          // IntersectionObserver fallback
          var imageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
              if (entry.isIntersecting) {
                var img = entry.target;
                loadImage(img);
                observer.unobserve(img);
              }
            });
          }, {
            rootMargin: '50px'
          });

          images.forEach(function(img) {
            imageObserver.observe(img);
          });
        } else {
          // Fallback for older browsers - load everything
          images.forEach(function(img) {
            loadImage(img);
          });
        }
      }
    };

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', window.lazyImageObserver.init);
    } else {
      window.lazyImageObserver.init();
    }
  }
})();
</script>

Finding Image Dimensions

If you don’t know the width and height of your image:

# On macOS
sips -g pixelWidth -g pixelHeight assets/images/posts/photo.jpg

# Or use ImageMagick
identify -format "%wx%h" assets/images/posts/photo.jpg

Optimization Workflow

  1. Add new image to repository
    cp ~/Downloads/new-photo.jpg assets/images/posts/
    
  2. Optimize the image
    bash tools/optimize-images.sh assets/images/posts/new-photo.jpg
    
  3. Check what was generated
    ls -lh assets/images/posts/new-photo*
    
  4. Get dimensions
    sips -g pixelWidth -g pixelHeight assets/images/posts/new-photo.jpg
    
  5. Use in your post ```liquid
Descriptive text about the image

## Accessibility Best Practices

### Good Alt Text Examples

✅ **Good**: "Aerial view of Manhattan skyline at dusk with illuminated buildings"
✅ **Good**: "Developer typing code on laptop with multiple monitors"
✅ **Good**: "Graph showing 40% increase in website performance"

### Bad Alt Text Examples

❌ **Bad**: "Image of city"
❌ **Bad**: "Picture of someone coding"
❌ **Bad**: "DSC_1234.jpg"
❌ **Bad**: "" (empty - should only be used for purely decorative images)

### Complex Images

For charts, diagrams, or complex images, provide context in surrounding text:

```markdown
The chart below shows the dramatic improvement in page load times after
implementing image optimization...

















<div class="lazy-image-wrapper">
  <picture>
    
    <source
      type="image/webp"
      data-srcset="/assets/images/posts/performance-chart-640w.webp 640w,
                   /assets/images/posts/performance-chart-1280w.webp 1280w,
                   /assets/images/posts/performance-chart-1920w.webp 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <source
      type="image/jpeg"
      data-srcset="/assets/images/posts/performance-chart-640w.jpg 640w,
                   /assets/images/posts/performance-chart-1280w.jpg 1280w,
                   /assets/images/posts/performance-chart-1920w.jpg 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <img
      data-src="/assets/images/posts/performance-chart./assets/images/posts/performance-chart"
      alt="Bar chart comparing page load times before and after optimization"
      class="lazy-image "
      loading="lazy"
      width="1920"
      height="1080"
    />
  </picture>

  <noscript>
    <picture>
      <source
        type="image/webp"
        srcset="/assets/images/posts/performance-chart-640w.webp 640w,
                /assets/images/posts/performance-chart-1280w.webp 1280w,
                /assets/images/posts/performance-chart-1920w.webp 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
      />
      <img
        src="/assets/images/posts/performance-chart./assets/images/posts/performance-chart"
        srcset="/assets/images/posts/performance-chart-640w.jpg 640w,
                /assets/images/posts/performance-chart-1280w.jpg 1280w,
                /assets/images/posts/performance-chart-1920w.jpg 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
        alt="Bar chart comparing page load times before and after optimization"
        class=""
        width="1920"
        height="1080"
      />
    </picture>
  </noscript>
</div>

<script>
(function() {
  if (!window.lazyImageObserver) {
    window.lazyImageObserver = {
      init: function() {
        var images = document.querySelectorAll('img.lazy-image[data-src]');
        var sources = document.querySelectorAll('source[data-srcset]');

        // Function to load an image and its sources
        var loadImage = function(img) {
          // Load source elements first
          var picture = img.closest('picture');
          if (picture) {
            var pictureSources = picture.querySelectorAll('source[data-srcset]');
            pictureSources.forEach(function(source) {
              if (source.dataset.srcset) {
                source.srcset = source.dataset.srcset;
                source.removeAttribute('data-srcset');
              }
            });
          }

          // Load img element
          if (img.dataset.src) {
            img.src = img.dataset.src;
            img.removeAttribute('data-src');
            img.classList.add('lazy-loaded');

            if (window.performance && window.performance.mark) {
              performance.mark('image-loaded');
            }
          }
        };

        if ('loading' in HTMLImageElement.prototype) {
          // Native lazy loading support
          images.forEach(function(img) {
            loadImage(img);
          });
        } else if ('IntersectionObserver' in window) {
          // IntersectionObserver fallback
          var imageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
              if (entry.isIntersecting) {
                var img = entry.target;
                loadImage(img);
                observer.unobserve(img);
              }
            });
          }, {
            rootMargin: '50px'
          });

          images.forEach(function(img) {
            imageObserver.observe(img);
          });
        } else {
          // Fallback for older browsers - load everything
          images.forEach(function(img) {
            loadImage(img);
          });
        }
      }
    };

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', window.lazyImageObserver.init);
    } else {
      window.lazyImageObserver.init();
    }
  }
})();
</script>


As shown, the optimized pages load 73% faster on average.

Verifying It Works

After adding images to your site:

  1. Build locally
    bundle exec jekyll serve
    
  2. Open DevTools (Network tab)
    • On desktop: Look for .webp files being loaded
    • On mobile: Check that smaller sizes are loaded
  3. Test responsive behavior
    • Resize browser window
    • Check which image sizes are loaded at different breakpoints
  4. Validate Core Web Vitals
    • Use Lighthouse in Chrome DevTools
    • Check for CLS (Cumulative Layout Shift) - should be < 0.1
    • Check for LCP (Largest Contentful Paint) - should be < 2.5s

Performance Impact

Before Optimization

After Optimization

Troubleshooting

Images not lazy-loading

WebP not loading

Wrong image size loading

Layout shift occurring

Examples from Real Posts

<!-- Hero image at top of post -->
















<div class="lazy-image-wrapper">
  <picture>
    
    <source
      type="image/webp"
      data-srcset="/assets/images/posts/manila-night-640w.webp 640w,
                   /assets/images/posts/manila-night-1280w.webp 1280w,
                   /assets/images/posts/manila-night-1920w.webp 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <source
      type="image/jpeg"
      data-srcset="/assets/images/posts/manila-night-640w.jpg 640w,
                   /assets/images/posts/manila-night-1280w.jpg 1280w,
                   /assets/images/posts/manila-night-1920w.jpg 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <img
      data-src="/assets/images/posts/manila-night./assets/images/posts/manila-night"
      alt="Manila skyline at night with illuminated skyscrapers reflected in water"
      class="lazy-image "
      loading="lazy"
      width="1920"
      height="1436"
    />
  </picture>

  <noscript>
    <picture>
      <source
        type="image/webp"
        srcset="/assets/images/posts/manila-night-640w.webp 640w,
                /assets/images/posts/manila-night-1280w.webp 1280w,
                /assets/images/posts/manila-night-1920w.webp 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
      />
      <img
        src="/assets/images/posts/manila-night./assets/images/posts/manila-night"
        srcset="/assets/images/posts/manila-night-640w.jpg 640w,
                /assets/images/posts/manila-night-1280w.jpg 1280w,
                /assets/images/posts/manila-night-1920w.jpg 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
        alt="Manila skyline at night with illuminated skyscrapers reflected in water"
        class=""
        width="1920"
        height="1436"
      />
    </picture>
  </noscript>
</div>

<script>
(function() {
  if (!window.lazyImageObserver) {
    window.lazyImageObserver = {
      init: function() {
        var images = document.querySelectorAll('img.lazy-image[data-src]');
        var sources = document.querySelectorAll('source[data-srcset]');

        // Function to load an image and its sources
        var loadImage = function(img) {
          // Load source elements first
          var picture = img.closest('picture');
          if (picture) {
            var pictureSources = picture.querySelectorAll('source[data-srcset]');
            pictureSources.forEach(function(source) {
              if (source.dataset.srcset) {
                source.srcset = source.dataset.srcset;
                source.removeAttribute('data-srcset');
              }
            });
          }

          // Load img element
          if (img.dataset.src) {
            img.src = img.dataset.src;
            img.removeAttribute('data-src');
            img.classList.add('lazy-loaded');

            if (window.performance && window.performance.mark) {
              performance.mark('image-loaded');
            }
          }
        };

        if ('loading' in HTMLImageElement.prototype) {
          // Native lazy loading support
          images.forEach(function(img) {
            loadImage(img);
          });
        } else if ('IntersectionObserver' in window) {
          // IntersectionObserver fallback
          var imageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
              if (entry.isIntersecting) {
                var img = entry.target;
                loadImage(img);
                observer.unobserve(img);
              }
            });
          }, {
            rootMargin: '50px'
          });

          images.forEach(function(img) {
            imageObserver.observe(img);
          });
        } else {
          // Fallback for older browsers - load everything
          images.forEach(function(img) {
            loadImage(img);
          });
        }
      }
    };

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', window.lazyImageObserver.init);
    } else {
      window.lazyImageObserver.init();
    }
  }
})();
</script>


<!-- Smaller inline image -->
















<div class="lazy-image-wrapper">
  <picture>
    
    <source
      type="image/webp"
      data-srcset="/assets/images/posts/school-songs-640w.webp 640w,
                   /assets/images/posts/school-songs-1280w.webp 1280w,
                   /assets/images/posts/school-songs-1920w.webp 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <source
      type="image/jpeg"
      data-srcset="/assets/images/posts/school-songs-640w.jpg 640w,
                   /assets/images/posts/school-songs-1280w.jpg 1280w,
                   /assets/images/posts/school-songs-1920w.jpg 1920w"
      sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
    />

    
    <img
      data-src="/assets/images/posts/school-songs./assets/images/posts/school-songs"
      alt="Sheet music showing traditional school songs"
      class="lazy-image border"
      loading="lazy"
      width="1500"
      height="1000"
    />
  </picture>

  <noscript>
    <picture>
      <source
        type="image/webp"
        srcset="/assets/images/posts/school-songs-640w.webp 640w,
                /assets/images/posts/school-songs-1280w.webp 1280w,
                /assets/images/posts/school-songs-1920w.webp 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
      />
      <img
        src="/assets/images/posts/school-songs./assets/images/posts/school-songs"
        srcset="/assets/images/posts/school-songs-640w.jpg 640w,
                /assets/images/posts/school-songs-1280w.jpg 1280w,
                /assets/images/posts/school-songs-1920w.jpg 1920w"
        sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
        alt="Sheet music showing traditional school songs"
        class="border"
        width="1500"
        height="1000"
      />
    </picture>
  </noscript>
</div>

<script>
(function() {
  if (!window.lazyImageObserver) {
    window.lazyImageObserver = {
      init: function() {
        var images = document.querySelectorAll('img.lazy-image[data-src]');
        var sources = document.querySelectorAll('source[data-srcset]');

        // Function to load an image and its sources
        var loadImage = function(img) {
          // Load source elements first
          var picture = img.closest('picture');
          if (picture) {
            var pictureSources = picture.querySelectorAll('source[data-srcset]');
            pictureSources.forEach(function(source) {
              if (source.dataset.srcset) {
                source.srcset = source.dataset.srcset;
                source.removeAttribute('data-srcset');
              }
            });
          }

          // Load img element
          if (img.dataset.src) {
            img.src = img.dataset.src;
            img.removeAttribute('data-src');
            img.classList.add('lazy-loaded');

            if (window.performance && window.performance.mark) {
              performance.mark('image-loaded');
            }
          }
        };

        if ('loading' in HTMLImageElement.prototype) {
          // Native lazy loading support
          images.forEach(function(img) {
            loadImage(img);
          });
        } else if ('IntersectionObserver' in window) {
          // IntersectionObserver fallback
          var imageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
              if (entry.isIntersecting) {
                var img = entry.target;
                loadImage(img);
                observer.unobserve(img);
              }
            });
          }, {
            rootMargin: '50px'
          });

          images.forEach(function(img) {
            imageObserver.observe(img);
          });
        } else {
          // Fallback for older browsers - load everything
          images.forEach(function(img) {
            loadImage(img);
          });
        }
      }
    };

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', window.lazyImageObserver.init);
    } else {
      window.lazyImageObserver.init();
    }
  }
})();
</script>

Further Reading