Episode 1 — Fundamentals / 1.9 — CSS Responsive Design

1.9.d — Responsive Images

In one sentence: Responsive images use object-fit, srcset, <picture>, and modern formats to deliver the right image at the right size — saving bandwidth, preventing layout shifts, and keeping visuals sharp on every screen.

Navigation: ← 1.9.c — Breakpoint Planning · 1.9.e — Fluid Typography →


1. The problem

A single 2400px-wide hero image:

  • Wastes bandwidth on a 375px phone (downloads 6× more pixels than needed).
  • Looks blurry on a 4K monitor if you only ship a small version.
  • Causes layout shifts if dimensions aren't reserved.

Responsive images solve all three.


2. object-fit

When an <img> or <video> is placed inside a container with fixed dimensions, object-fit controls how the content fills the box — like background-size but for replaced elements.

ValueBehavior
fillStretches to fill the box exactly (default — can distort)
containScales to fit inside the box, preserving aspect ratio (may show gaps)
coverScales to cover the entire box, preserving aspect ratio (may crop)
noneNo resizing — shows at natural size, may overflow
scale-downBehaves like contain or none, whichever is smaller
.card-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}
┌───────────────────┐   ┌───────────────────┐   ┌───────────────────┐
│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│   │   ▓▓▓▓▓▓▓▓▓▓▓▓▓  │   │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
│▓▓▓▓ fill ▓▓▓▓▓▓▓▓▓│   │   ▓ contain ▓▓▓  │   │▓▓▓▓▓ cover ▓▓▓▓▓▓▓│
│▓▓▓ (distorts) ▓▓▓▓│   │   ▓▓▓▓▓▓▓▓▓▓▓▓▓  │   │▓▓▓▓ (crops) ▓▓▓▓▓▓│
│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│   │                   │   │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
└───────────────────┘   └───────────────────┘   └───────────────────┘

3. object-position

Works with object-fit to control where the image anchors inside its box. Same syntax as background-position.

.avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  object-fit: cover;
  object-position: top center; /* keep face visible */
}
ValueEffect
center (default)Centered in both axes
topAnchored to top edge
50% 20%50% across, 20% down
right bottomAnchored to bottom-right

4. srcset and sizes (recap)

Covered in depth in 1.5.h — Responsive Images. Quick refresher:

Resolution switching

<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w,
          photo-800.jpg 800w,
          photo-1200.jpg 1200w"
  sizes="(min-width: 1024px) 50vw,
         100vw"
  alt="Mountain landscape"
/>
  • srcset — list of image files with their intrinsic widths (w descriptor).
  • sizes — tells the browser how wide the image will be displayed at each breakpoint.
  • The browser picks the best file based on viewport width and device pixel ratio.

Pixel-density switching

<img
  src="logo.png"
  srcset="logo.png 1x,
          logo@2x.png 2x,
          logo@3x.png 3x"
  alt="Company logo"
/>

5. <picture> for art direction

When you need different crops or images at different sizes (not just different resolutions), use <picture>:

<picture>
  <!-- Wide: panoramic crop -->
  <source
    media="(min-width: 1024px)"
    srcset="hero-wide.webp"
    type="image/webp"
  />
  <!-- Tablet: square crop -->
  <source
    media="(min-width: 640px)"
    srcset="hero-square.webp"
    type="image/webp"
  />
  <!-- Mobile: tall crop + fallback format -->
  <source srcset="hero-tall.webp" type="image/webp" />
  <img src="hero-tall.jpg" alt="Product showcase" />
</picture>

Key difference: srcset lets the browser choose the best resolution. <picture> lets the author control which image shows at which breakpoint.


6. Modern image formats

FormatCompressionTransparencyAnimationBrowser support
JPEGGood (lossy)NoNoUniversal
PNGFair (lossless)YesNoUniversal
WebPGreat (lossy + lossless)YesYes97%+
AVIFExcellent (lossy + lossless)YesYes92%+

Strategy: Serve AVIF with WebP fallback, JPEG as final fallback:

<picture>
  <source srcset="photo.avif" type="image/avif" />
  <source srcset="photo.webp" type="image/webp" />
  <img src="photo.jpg" alt="Description" />
</picture>

7. Lazy loading

Images below the fold should load only when the user scrolls near them.

<!-- Native lazy loading -->
<img src="photo.jpg" alt="…" loading="lazy" width="800" height="600" />
loading valueBehavior
eager (default)Load immediately
lazyDefer until near viewport

Rules of thumb:

  • Hero images / above-the-fold: Use loading="eager" (or omit — it's the default) and add fetchpriority="high".
  • Everything else: Use loading="lazy".
  • Always include width and height to prevent CLS while the image loads.

8. The aspect-ratio property

Reserve space for an image before it loads to prevent Cumulative Layout Shift (CLS):

.thumbnail {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.square-avatar {
  width: 80px;
  aspect-ratio: 1;       /* shorthand for 1 / 1 */
  object-fit: cover;
  border-radius: 50%;
}

The browser calculates the height automatically from the width and ratio, reserving exact space in the layout.

/* Responsive video container */
.video-wrapper {
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
}

.video-wrapper iframe {
  width: 100%;
  height: 100%;
}

9. Image performance checklist

StepAction
1Right format — AVIF > WebP > JPEG/PNG
2Right size — never serve wider than the display size × DPR
3Compress — 80% quality for lossy is usually indistinguishable
4srcset + sizes — let the browser pick the best resolution
5loading="lazy" — defer below-fold images
6width + height or aspect-ratio — prevent CLS
7fetchpriority="high" — on hero/LCP images
8CDN with auto-format — Cloudinary, imgix, Vercel Image Optimization auto-serve best format

10. Key takeaways

  1. object-fit: cover is your go-to for fixed-dimension image containers.
  2. srcset + sizes = resolution switching (browser chooses). <picture> = art direction (you choose).
  3. Ship AVIF → WebP → JPEG for maximum compression with graceful fallback.
  4. loading="lazy" on below-fold images, always with width/height to prevent CLS.
  5. aspect-ratio reserves space and eliminates the old padding-bottom hack.

Explain-It Challenge

Explain without notes:

  1. When would you use object-fit: contain instead of cover, and what visual difference does the user see?
  2. Why does <picture> exist when srcset already handles multiple image sources?
  3. A page has a CLS score of 0.35. Images are the primary cause. Walk through the fixes you'd apply.

Navigation: ← 1.9.c — Breakpoint Planning · 1.9.e — Fluid Typography →