Responsive Images and Page Weight
Overview
You will be learning how to optimize websites with image in the HTML. There will be a few CSS nicities to make things look better, but the majority of the work will be done by using HTML tags and attributes.
This assignmenet is build off of the responsive images and sizes lesson.
Deliverable
Completed page-weight html and css files.
Starting Files
Please download and extract the files to get started.
Exercise 1 - Page Weight
Images are almost always the #1 reason a webpage is slow. A single unoptimised hero image can weigh more than every other resource on the page combined. This lesson has two phases:
Phase 1 — Design first. Get the layout looking great using CSS grid, clip-path, object-fit, and container queries.
Phase 2 — Performance. Measure with Lighthouse, then apply responsive images, srcset, modern formats, and preloading to slash the page weight.
You'll start with the starter files (above that you downloaded) and incrementally improve them, running a Lighthouse audit after each change so you can see the impact in real numbers.
Notice that the HTML has no srcset, no preloading, and every image is served at full 1280 × 720 resolution regardless of screen size. Additionally, it has not specified widths or heights for the images.
Design First - Responsive Hero Shape
The hero uses a parallelogram clip-path made with percentage values. That looks fine on large screens, but at narrow widths the clipped edges chew into the text.
The fix: swap the percentage-based clip-path for a viewport-unit version. In page-weight.css, find the two clip-path lines inside .hero and switch which one is commented out:
/* Before (comment this one out) */
clip-path: polygon(25% 0%, 100% 0%, 75% 100%, 0% 100%);
/* After (uncomment this one) */
clip-path: polygon(10vw 0vh, 100vw 0vh, 90vw 100vh, 0vw 100vh);
Why does this help? Percentage values are relative to the element — as the element shrinks, so does the visible area. vw/vh values are relative to the viewport, so the absolute pixel inset stays proportional to the screen, not the element.
Design First - Fixing The Photo Cards
Right now the grid images render at their native size — no cropping, no consistent shape. The result looks chaotic. We need each card to act as a fixed-ratio window that crops its image cleanly.
In page-weight.css, scroll to the bottom and uncomment all the rules inside div.photo and div.photo img:
div.photo {
aspect-ratio: 16/9;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
div.photo img {
display: block;
width: 100%;
object-fit: cover;
}Key concepts
aspect-ratio: 16/9 locks the card to a widescreen shape.
overflow: hidden clips any image that bleeds outside.
object-fit: cover tells the image to fill the card entirely, cropping rather than squishing.
How to Measure Page Weight in Chrome
Google Chrome ships with a built-in auditing tool called Lighthouse. It simulates a mid-tier device on a slow 4G connection and gives you a score out of 100 for Performance, Accessibility, and more.
Running a Lighthouse Audit
1. Open your page in Chrome in an Incognito window. Right-click anywhere and choose:
Inspect or F12 (win) (mac) Cmd+Opt+I.2. In DevTools, click theLighthousetab. If you don't see it, click the >> overflow arrow.
3 Under Categories, checkPerformance. Under Device, chooseMobile (it's the harder target).
4 Click Analyze page load. After 30–60 seconds you'll get a detailed report.
Phase 2 - Reduce Page Weight
Now that the design looks good, let's make it fast. Run Lighthouse on your current page and note the score — that's your baseline. Each exercise below will improve it.
Responsive Images With SRCSET and SIZES
Right now every grid image is served at 1280 × 720 — even on a phone where it renders at 300 × 170. That's roughly 18× more pixels than needed. The browser has no way to know which size is appropriate because we haven't told it.
srcset gives the browser a menu of image sizes. sizes tells it how wide the image will actually render. The browser picks the best match.
Update each photo card in your HTML. Here is the pattern (using photo 101 as the example):
<!-- Before -->
<img src="https://picsum.photos/id/101/1280/720" alt="Photo 1">
<!-- After -->
<img
src="https://picsum.photos/id/101/640/360"
srcset="
https://picsum.photos/id/101/400/225 400w,
https://picsum.photos/id/101/640/360 640w,
https://picsum.photos/id/101/1280/720 1280w
"
sizes="(max-width: 40em) 100vw,
(max-width: 900px) 50vw,
33vw"
alt="Photo 1">How to read the sizes attribute
Think of it as a series of media condition → slot width pairs. The browser reads them top to bottom and uses the first one that matches.
"On screens narrower than 40em, this image will be 100% of the viewport wide. On screens up to 900px, it'll be half. Otherwise, one-third."
Preloading the LCP HERO IMAGE
Lighthouse's biggest complaint about image-heavy pages is usually a slow LCP. The hero image is almost always the Largest Contentful Paint element — and the browser discovers it late, only after it has parsed and started rendering the page.
A <link rel="preload"> in the <head> tells the browser to fetch the hero image immediately, before it even starts parsing the <body>.
In your HTML, find the commented-out preload line and uncomment it:
<!-- Uncomment this line -->
<link rel="preload"
href="https://picsum.photos/id/101/2560/1440"
as="image">⚠️ Only preload what's above the fold
Preloading everything defeats the purpose. Use it only for the single most important image the user sees immediately. Preloading hidden or below-fold images actually hurts performance by competing with critical resources.
Lazy Loading Below Fold Images
The grid images load immediately even though most of them are below the visible area. Adding loading="lazy" tells the browser to defer those requests until the user scrolls close to them — instant bandwidth savings on first load.
<!-- Add loading="lazy" to all grid images (NOT the hero) -->
<img
src="https://picsum.photos/id/101/640/360"
srcset="..."
sizes="..."
loading="lazy"
alt="Photo 1">🚫 Never put loading="lazy" on the hero/LCP image
Lazy loading the hero actively delays LCP and tanks your Lighthouse score. It belongs on images that are below the fold only.
Modern Formats with <picture>
JPEG is universal but inefficient. WebP and AVIF can deliver the same visual quality at 30–80% smaller file sizes. The <picture> element lets you offer modern formats with a JPEG fallback — the browser picks the best one it supports.
<picture>
<!-- Best: AVIF (smallest, Chrome 85+, Firefox 93+) -->
<source
type="image/avif"
srcset="
https://picsum.photos/id/101/400/225 400w,
https://picsum.photos/id/101/640/360 640w,
https://picsum.photos/id/101/1280/720 1280w
"
sizes="(max-width: 40em) 100vw, (max-width: 900px) 50vw, 33vw">
<!-- Better: WebP (Chrome, Firefox, Safari 14+) -->
<source
type="image/webp"
srcset="
https://picsum.photos/id/101/400/225 400w,
https://picsum.photos/id/101/640/360 640w,
https://picsum.photos/id/101/1280/720 1280w
"
sizes="(max-width: 40em) 100vw, (max-width: 900px) 50vw, 33vw">
<!-- Fallback: JPEG for older browsers -->
<img
src="https://picsum.photos/id/101/640/360"
loading="lazy"
alt="Photo 1">
</picture>💡 Note on picsum.photos
The Picsum CDN we're using in this exercise serves JPEG regardless of which URL you request. In a real project you'd generate WebP/AVIF files from your originals (e.g., with Squoosh, Sharp, or ImageMagick). The code structure above is correct — just be aware the actual bytes served here won't change.
Quick Reference — Before & After
Technique | What it fixes | Lighthouse impact |
srcset + sizes | Sending a 1280px image to a 300px slot | ↑ Performance, ↓ LCP |
rel="preload" | Browser discovers hero image too late | ↑↑ LCP (biggest win) |
loading="lazy" | All images download on first load | ↑ Speed Index, ↓ TBT |
<picture> + WebP/AVIF | JPEG file size overhead | ↓ total bytes transferred |
object-fit: cover | Layout shift as images load | ↑ CLS |
aspect-ratio | Cards jumping when image loads | ↑ CLS |
Locked Message
