Image Optimization — WebP, AVIF, Lazy Loading & Responsive Images Guide
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
- Missing
widthandheightattributes — Without explicit dimensions, the browser reserves 0x0 space, causing Cumulative Layout Shift (CLS) when the image loads. - Serving JPEG when WebP is supported — Over 95% of browsers support WebP. Always serve WebP with JPEG/PNG fallback via
<picture>. - Not using
srcsetfor responsive images — Without it, mobile users download the same 2MB hero image as desktop users. Always provide multiple resolutions. - Lazy loading the hero image — The hero image is above the fold and should NOT be lazy loaded. Use
loading="eager"or omitloading. - Forgetting
decoding="async"— This hint tells the browser to decode the image off the main thread, improving interaction readiness during page load.
Practice Questions
- 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.
- How does
srcsetwithsizeswork? The browser usessizesto determine the image display width, then picks the smallestsrcsetentry that meets or exceeds that width at the device's pixel ratio. - 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. - 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.
- How do you prevent layout shift from images? Always set explicit
widthandheightattributes. Useaspect-ratioin 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