Skip to content

Image Optimization — WebP, AVIF, Lazy Loading & Responsive Images Guide

DodaTech Updated 2026-06-24 5 min read

In this tutorial, you'll learn about Image Optimization. We cover key concepts, practical examples, and best practices.

Images account for over 50% of the average web page's weight — optimizing them is the single highest-impact performance improvement you can make.

What You'll Learn

Modern image formats (WebP, AVIF), responsive images with srcset and sizes, lazy loading with loading="lazy", native lazy loading vs Intersection Observer, CDN-based optimization, and automated image pipelines.

Why It Matters

A 1MB hero image takes 3 seconds on a 3G connection. Optimized WebP at 200KB with the same visual quality loads in 0.6 seconds — a 5x improvement. Doda Browser's image rendering pipeline supports WebP and AVIF natively with hardware decoding, making optimized images render even faster than JPEG.

Real-world use: Durga Antivirus Pro's web dashboard uses responsive images for screenshot previews — a 1920px WebP for desktop, 768px for tablet, and 480px for mobile. The loading="lazy" attribute defers off-screen images until the user scrolls, saving 3.2MB on initial page load.

flowchart LR
  A[Image Optimization] --> B[Modern Formats]
  A --> C[Responsive Images]
  A --> D[Lazy Loading]
  A --> E[CDN Optimization]
  B --> F[WebP]
  B --> G[AVIF]
  C --> H[srcset]
  C --> I[sizes]
  D --> J[lazy attribute]
  D --> K[IntersectionObserver]
  style A fill:#4af,color:#fff

Modern Image Formats

WebP

WebP provides 25-35% smaller files than JPEG with the same quality:

<picture>
  <source srcset="photo.webp" type="image/webp">
  <source srcset="photo.jpg" type="image/jpeg">
  <img src="photo.jpg" alt="DodaTech office"
       width="800" height="600" loading="lazy">
</picture>

AVIF

AVIF offers 50% smaller files than JPEG — the best compression available:

<picture>
  <source srcset="photo.avif" type="image/avif">
  <source srcset="photo.webp" type="image/webp">
  <source srcset="photo.jpg" type="image/jpeg">
  <img src="photo.jpg" alt="DodaTech office"
       width="800" height="600" loading="lazy">
</picture>

Format Comparison

Format Size (vs JPEG) Browser Support Features
JPEG Baseline Universal No transparency
PNG 2-3x larger Universal Lossless, transparency
WebP 25-35% smaller All modern Lossy/lossless, transparency, animation
AVIF 50% smaller Chrome, Firefox HDR, transparency, animation

Responsive Images

Serve the right size for each device:

<!-- Simple srcset with density descriptors -->
<img src="photo-1x.jpg"
     srcset="photo-2x.jpg 2x, photo-3x.jpg 3x"
     alt="Photo"
     width="800" height="600">

<!-- Width descriptors with sizes attribute -->
<img src="hero-mobile.jpg"
     srcset="
       hero-480.jpg 480w,
       hero-768.jpg 768w,
       hero-1024.jpg 1024w,
       hero-1920.jpg 1920w
     "
     sizes="
       (max-width: 480px) 100vw,
       (max-width: 768px) 100vw,
       (max-width: 1024px) 80vw,
       1200px
     "
     alt="Hero banner"
     width="1920" height="1080"
     loading="lazy"
     decoding="async">

<!-- Art direction with picture element -->
<picture>
  <source media="(min-width: 1024px)" srcset="hero-wide.webp" type="image/webp">
  <source media="(min-width: 768px)" srcset="hero-tablet.webp" type="image/webp">
  <source media="(max-width: 767px)" srcset="hero-mobile.webp" type="image/webp">
  <img src="hero-wide.jpg" alt="Hero banner"
       width="1920" height="600" loading="lazy">
</picture>

Lazy Loading

Native Lazy Loading

<img src="photo.webp" loading="lazy" alt="Description" width="800" height="600">
<iframe src="widget.html" loading="lazy" title="Widget"></iframe>

loading values: lazy (defer off-screen), eager (load immediately), auto (browser default).

Intersection Observer Fallback

const lazyImages = document.querySelectorAll('img[data-src]');

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (!entry.isIntersecting) return;

    const img = entry.target;
    img.src = img.dataset.src;
    img.srcset = img.dataset.srcset;
    img.removeAttribute('data-src');
    img.removeAttribute('data-srcset');
    observer.unobserve(img);
  });
}, {
  rootMargin: '200px 0px',
  threshold: 0.01
});

lazyImages.forEach(img => imageObserver.observe(img));
<img data-src="photo.webp" data-srcset="photo-480.webp 480w, photo-768.webp 768w"
     alt="Description" width="800" height="600"
     class="lazy">

Performance Best Practices

/* Prevent layout shift — always set dimensions */
img, video, iframe {
  max-width: 100%;
  height: auto;
  aspect-ratio: attr(width) / attr(height);
}

/* Fade in images on load */
img {
  opacity: 0;
  transition: opacity 0.3s ease;
}
img.loaded {
  opacity: 1;
}
// Detect image load for fade-in
document.querySelectorAll('img').forEach(img => {
  if (img.complete) {
    img.classList.add('loaded');
  } else {
    img.addEventListener('load', () => img.classList.add('loaded'));
    img.addEventListener('error', () => img.classList.add('loaded'));
  }
});

Common Errors

  1. Missing width and height attributes — Without explicit dimensions, the browser reserves 0x0 space, causing Cumulative Layout Shift (CLS) when the image loads.
  2. Serving JPEG when WebP is supported — Over 95% of browsers support WebP. Always serve WebP with JPEG/PNG fallback via <picture>.
  3. Not using srcset for responsive images — Without it, mobile users download the same 2MB hero image as desktop users. Always provide multiple resolutions.
  4. Lazy loading the hero image — The hero image is above the fold and should NOT be lazy loaded. Use loading="eager" or omit loading.
  5. Forgetting decoding="async" — This hint tells the browser to decode the image off the main thread, improving interaction readiness during page load.

Practice Questions

  1. What is the difference between WebP and AVIF? AVIF offers ~50% better compression than WebP but has slightly less browser support. Both support transparency and animation.
  2. How does srcset with sizes work? The browser uses sizes to determine the image display width, then picks the smallest srcset entry that meets or exceeds that width at the device's pixel ratio.
  3. What does loading="lazy" do? Defers loading off-screen images until the user scrolls near them. The browser decides the threshold based on connection speed and device capabilities.
  4. Why should hero images NOT be lazy loaded? The hero image is visible in the initial viewport. Lazy loading it delays the Largest Contentful Paint (LCP) metric unnecessarily.
  5. How do you prevent layout shift from images? Always set explicit width and height attributes. Use aspect-ratio in CSS as a fallback. Reserve space in the layout.

Challenge

Build a responsive image gallery with 20 photos. Use WebP with JPEG fallback, three breakpoint sizes (480w, 768w, 1200w), native lazy loading, fade-in on load, and a lightbox viewer. Verify no layout shift occurs by testing with Lighthouse.

Real-World Task

Optimize all images for the DodaTech tutorials site. Create a build script that converts PNG/JPEG images to WebP and AVIF, generates three responsive sizes per image (thumb, medium, full), adds proper width/height attributes, and updates all <img> tags to use <picture> with proper fallback chains. Use loading="lazy" for all below-fold images.


Previous: Web Fonts Guide | Next: HTTP Caching Strategies | Related: Web Performance Optimization

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro