HomeCSS Tailwind
Chapter 13

13 β€” Responsive Images & Media

Image Formats

FormatUse CaseBrowser Support
WebPModern web, 25-35% smaller than JPEGChrome, Firefox, Safari, Edge
AVIFBest compression, 50% smaller than JPEGChrome, Firefox, Opera
JPEGPhotos, gradientsAll
PNGTransparency, graphicsAll
SVGIcons, logos, illustrationsAll
GIFSimple animations (use video instead)All

<picture> Element

<picture>
  <!-- AVIF for modern browsers -->
  <source 
    type="image/avif" 
    srcset="image-300.avif 300w,
            image-600.avif 600w,
            image-1200.avif 1200w">
  
  <!-- WebP fallback -->
  <source 
    type="image/webp" 
    srcset="image-300.webp 300w,
            image-600.webp 600w,
            image-1200.webp 1200w">
  
  <!-- JPEG fallback -->
  <source 
    type="image/jpeg" 
    srcset="image-300.jpg 300w,
            image-600.jpg 600w,
            image-1200.jpg 1200w">
  
  <!-- Fallback img -->
  <img 
    src="image-600.jpg" 
    alt="Description"
    loading="lazy"
    width="600"
    height="400">
</picture>

srcset and sizes

Resolution Switching

<!-- Serve different resolutions -->
<img 
  src="image-800.jpg" 
  srcset="image-400.jpg 400w,
          image-800.jpg 800w,
          image-1200.jpg 1200w"
  sizes="(max-width: 600px) 100vw,
         (max-width: 1200px) 50vw,
         33vw"
  alt="Description">

How it works:

  • Viewport ≀ 600px: Use 100% of viewport width β†’ picks 400w or 800w
  • Viewport ≀ 1200px: Use 50% of viewport width β†’ picks 800w
  • Viewport > 1200px: Use 33% of viewport width β†’ picks 1200w

Art Direction

<picture>
  <!-- Wide image for desktop -->
  <source 
    media="(min-width: 1024px)" 
    srcset="hero-wide.jpg">
  
  <!-- Tall image for mobile -->
  <source 
    media="(max-width: 767px)" 
    srcset="hero-tall.jpg">
  
  <!-- Default -->
  <img src="hero-default.jpg" alt="Hero">
</picture>

Lazy Loading

Native Lazy Loading

<!-- Lazy load images -->
<img src="image.jpg" loading="lazy" alt="...">
 
<!-- Eager load (above the fold) -->
<img src="hero.jpg" loading="eager" alt="...">
 
<!-- Lazy load iframes -->
<iframe src="video.html" loading="lazy"></iframe>

JavaScript Lazy Loading

// Intersection Observer API
const images = document.querySelectorAll('img[data-src]');
 
const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.add('loaded');
      observer.unobserve(img);
    }
  });
});
 
images.forEach(img => imageObserver.observe(img));
<!-- Markup -->
<img 
  data-src="image.jpg" 
  src="placeholder.jpg" 
  alt="..."
  class="lazyload">

Image Optimization

Compression Tools

# Squoosh (Web-based)
https://squoosh.app
 
# ImageMagick
convert input.jpg -quality 80 output.jpg
 
# Sharp (Node.js)
npm install sharp
// Sharp example
const sharp = require('sharp');
 
sharp('input.jpg')
  .resize(800, 600)
  .webp({ quality: 80 })
  .toFile('output.webp');

CDN Optimization

<!-- Cloudinary -->
<img src="https://res.cloudinary.com/demo/image/upload/w_800,h_600,c_fill,q_auto/photo.jpg">
 
<!-- Imgix -->
<img src="https://example.imgix.net/photo.jpg?w=800&h=600&fit=crop&auto=format">
 
<!-- Cloudflare Images -->
<img src="https://imagedelivery.net/abc123/photo/public">

Video Optimization

<!-- Preload metadata only -->
<video 
  src="video.mp4" 
  preload="metadata"
  poster="poster.jpg"
  controls>
</video>
 
<!-- Multiple formats -->
<video controls>
  <source src="video.webm" type="video/webm">
  <source src="video.mp4" type="video/mp4">
  Your browser doesn't support HTML5 video.
</video>
 
<!-- Lazy load video -->
<video 
  src="video.mp4" 
  preload="none"
  poster="poster.jpg"
  controls></video>

Autoplay (Muted)

<video 
  autoplay 
  muted 
  loop 
  playsinline
  poster="poster.jpg">
  <source src="background.mp4" type="video/mp4">
</video>

Background Images

/* Responsive background */
.hero {
  background-image: url('small.jpg');
  background-size: cover;
  background-position: center;
}
 
@media (min-width: 768px) {
  .hero {
    background-image: url('large.jpg');
  }
}
<!-- Inline styles for dynamic images -->
<div 
  class="hero"
  style="background-image: url('image.jpg')">
</div>

Aspect Ratio

<!-- Modern CSS -->
<div class="aspect-video">
  <img src="video-thumbnail.jpg" alt="...">
</div>
 
<div class="aspect-square">
  <img src="image.jpg" alt="...">
</div>
 
<!-- Custom aspect ratio -->
<div class="aspect-[4/3]">
  Content
</div>
/* Manual aspect ratio */
.aspect-ratio-box {
  position: relative;
  width: 100%;
  padding-top: 56.25%; /* 16:9 Aspect Ratio */
}
 
.aspect-ratio-content {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

Blur Placeholders

<div class="relative">
  <!-- Blurred placeholder -->
  <img 
    src="placeholder-blur.jpg" 
    class="absolute inset-0 w-full h-full object-cover blur-sm">
  
  <!-- Actual image -->
  <img 
    src="actual-image.jpg" 
    loading="lazy"
    class="relative w-full h-full object-cover">
</div>
/* LQIP (Low Quality Image Placeholder) */
.image-container {
  background: url('lqip.jpg') no-repeat center;
  background-size: cover;
}
 
.image-container img {
  opacity: 0;
  transition: opacity 0.3s;
}
 
.image-container img.loaded {
  opacity: 1;
}

Responsive <figure> and <figcaption>

<figure>
  <picture>
    <source media="(min-width: 1024px)" srcset="image-large.jpg">
    <source media="(min-width: 768px)" srcset="image-medium.jpg">
    <img src="image-small.jpg" alt="Description" loading="lazy">
  </picture>
  
  <figcaption>
    <strong>Figure 1:</strong> Description of the image
  </figcaption>
</figure>

Performance Tips

βœ… Use modern formats (WebP, AVIF)
βœ… Compress images (aim for <100KB per image)
βœ… Specify width and height (prevents layout shift)
βœ… Lazy load below-the-fold images
βœ… Use CDN for automatic optimization
βœ… Serve responsive images with srcset
βœ… Preload critical images
βœ… Use blur placeholders for better UX
βœ… Optimize video (compress, use poster)
βœ… Set proper caching headers

Key Takeaways

  • Use <picture> for art direction, srcset for resolution switching
  • WebP/AVIF = smaller file sizes, JPEG/PNG = fallback
  • Lazy loading = loading="lazy" or Intersection Observer
  • CDN optimization = automatic resizing, compression, format conversion
  • Aspect ratio = prevent layout shift with aspect-ratio or padding hack
  • Blur placeholders = better perceived performance
  • Always specify width and height to prevent CLS
  • Preload critical above-the-fold images