Topic 5.2
Image Performance
In one line
Give every image a width and height so the page doesn't jump, lazy-load the ones below the fold, raise the priority of the main image, and use modern formats so fewer bytes travel.
Think of it like this
Saving seats at the cinema. If your friends are late but you put coats on their seats, nobody has to shuffle along the row when they arrive. width and height are the coats: they save the image's space, so the text doesn't get pushed down when the picture finally shows up.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Core Web Vitals
- Google's three page-experience measurements: loading (LCP), responsiveness (INP) and visual stability (CLS).
- LCP
- Largest Contentful Paint: the time until the biggest image or text block on the first screen is drawn.
- Layout shift
- Content on the page moving because something above it appeared or changed size.
- CLS
- Cumulative Layout Shift: a score for how much the page jumps while loading. Lower is better.
- Aspect ratio
- An image's shape, width compared to height. 640×360 and 1280×720 are both 16:9.
- Below the fold
- The part of the page you only see after scrolling.
- Lazy loading
- Waiting to download something until it is about to be needed (scrolled near).
- Decoding
- Turning a compressed image file into the grid of coloured pixels the screen shows.
- WebP / AVIF
- Modern image formats that store the same picture in fewer bytes than JPEG or PNG.
Step by step
01See the jump
An image with no size information starts as zero height (or a small alt-text box). When the file arrives, it suddenly becomes 300px tall and pushes the paragraph down. If the reader was about to tap a link, they tap the wrong thing.
<img src="team.jpg" alt="Our team at the office">
<p>Read about our team <a href="/team">here</a>.</p>02Reserve the space
Write the image file's real pixel size. Keep CSS height: auto so the image scales with its width. The browser maps the attributes to aspect-ratio: auto 1200 / 800 and reserves the right height before a single byte arrives.
The auto part means "once the image loads, use its own real ratio instead", so a slightly wrong attribute doesn't stretch the picture.
<img src="team.jpg" alt="Our team at the office" width="1200" height="800">03Lazy-load what's further down
Gallery images, images in comments and footers: mark them loading="lazy". The browser starts loading them a little before they scroll into view (Chromium uses a distance of roughly 1250–2500px depending on the connection), so readers rarely notice.
<img src="gallery-7.jpg" alt="Sunset over the lake" width="800" height="600" loading="lazy">04Speed up the main image
The big image at the top is usually the LCP element. Leave it eager (the default), and add fetchpriority="high" so it doesn't wait behind scripts and other images.
<img src="hero-1280.jpg"
srcset="hero-640.jpg 640w, hero-1280.jpg 1280w, hero-1920.jpg 1920w" sizes="100vw"
alt="Mountains at sunrise" width="1920" height="800" fetchpriority="high">05Shrink the bytes
Offer AVIF and WebP with a JPEG fallback, and export sensible sizes. A 4000px camera photo shown at 800px should never reach the browser at 4000px.
<picture>
<source srcset="team.avif" type="image/avif">
<source srcset="team.webp" type="image/webp">
<img src="team.jpg" alt="Our team at the office" width="1200" height="800" loading="lazy" decoding="async">
</picture>06Measure it
DevTools → Lighthouse reports LCP and CLS and flags images without dimensions, oversized images and old formats. DevTools → Performance panel shows layout shifts. Measure before and after each change.
Try it yourself
- 1
Remove the size and predict
In the first example, delete
width="640" height="360"from the sized image. Predict the first log line and whether its paragraph moves, then run it. - 2
Find the LCP image on a real site
Open a news site, then DevTools → Lighthouse → analyse the page. Under "Largest Contentful Paint element", which element is it? Does it have
fetchpriority="high"or, wrongly,loading="lazy"? - 3
Watch lazy loading
In the third example, open the console and scroll slowly. Note how far away image 2 is when its "Loaded" line appears: the browser starts early on purpose.
Code & diagrams
Both images start with no file. 640×360 at 320px wide is 180px tall, and that space is saved before the image arrives. Watch the second paragraph jump.
Preview
Console
Nothing logged yet.Expected console output
Space saved for the sized image: 180px
Text under the sized image moved: false
Text under the unsized image moved: trueWhen you leave `decoding` or `fetchpriority` out, the property reports the default, `auto`. (For a missing `loading`, browsers disagree on what to report, so this example only asks whether it is lazy.)
Preview
Console
Nothing logged yet.Expected console output
hero: lazy=false decoding=auto fetchpriority=high
later: lazy=true decoding=async fetchpriority=auto
plain: lazy=false decoding=auto fetchpriority=auto
Aspect ratio from attributes: auto 64 / 36Image 1 loads at once. The lazy ones load only when you scroll within about one or two screens of them, so their order and timing depend on you.
Preview
Console
Nothing logged yet.Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Lazy-load the hero image
Add loading="lazy" to the big image at the top of a page.
Break #2
Set width and height but forget height: auto
Give an image width="1200" height="800" and CSS img { max-width: 100%; } only.
Myth vs fact
Myth
With responsive CSS, width and height attributes are old-fashioned.
Fact
They are the modern fix for layout shift. With height: auto in CSS they set only the aspect ratio, and the image still scales freely.
Myth
Lazy-load every image for speed.
Fact
Lazy-loading images on the first screen delays them. Lazy-load only below-the-fold images.
Myth
fetchpriority="high" on all images makes the page faster.
Fact
Priority is relative. Raising everything changes nothing and may delay CSS or scripts. Use it on the single LCP image.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Browsers map
width/heighttoaspect-ratio: auto w / hthrough a UA presentational hint. Theautokeyword means the image's natural ratio wins once it is known; this mapping was added to the HTML spec in 2020 and is in every current engine. - ▸
Chromium fetches all images at low priority at first and raises visible ones after layout.
fetchpriority="high"skips that wait for the LCP image, which often improves LCP noticeably on image-led pages. For a CSS background image used as LCP, use<link rel="preload" as="image" fetchpriority="high">instead (see Topic 6.5). - ▸
Chromium doesn't lazy-load when JavaScript is disabled, because scroll position-based loading could be used for tracking without scripts. Lazy loading also doesn't apply to images inside the initial viewport.
- ▸
img.decode()returns a promise that resolves when the image is decoded and ready to paint. Use it before inserting a large image with JavaScript to avoid a frame where it appears half-drawn.
Remember this
- 1
Images are usually the heaviest part of a page: often more bytes than all the HTML, CSS and JavaScript together. Google measures page experience with Core Web Vitals, and images affect two of them: LCP (Largest Contentful Paint: how long until the biggest thing on screen, often a hero image, is drawn; good is 2.5 seconds or less) and CLS (Cumulative Layout Shift: how much the page jumps around while loading; good is 0.1 or less).
- 2
**Prevent layout shift with
widthandheight. A layout shift** happens when something appears or changes size and pushes other content. Before an image downloads, the browser doesn't know its size, so it gives it no height; when it arrives, everything below jumps. If you writewidth="640" height="360", modern browsers turn those numbers into an aspect ratio (the shape: here 16:9) and reserve the right space at once, even when CSS makes the image fluid withwidth: 100%; height: auto. The attributes are for the shape; CSS still decides the displayed size. - 3
**
loading="lazy"** tells the browser not to download an image until the user scrolls near it. Images far below the first screen ("below the fold") then cost nothing if the reader never gets there. The default isloading="eager"(download right away). Never lazy-load the main image at the top of the page: the browser would wait for layout before fetching it, which makes LCP worse. The same attribute works on<iframe>(Topic 5.5). - 4
**
fetchpriority** gives a hint about how important a download is:high,loworauto(the default). Browsers already fetch images at a low priority until layout shows they're visible;fetchpriority="high"on the hero image tells the browser to fetch it early and fast. Use it on one image, the LCP image, not everywhere: if everything is high priority, nothing is. - 5
**
decoding** is about turning the downloaded bytes into pixels (decoding).decoding="async"lets the browser decode the image off the main path so text can be shown first;syncdecodes before showing;auto(the default) lets the browser decide. It's a small hint; modern browsers already decode most images off the main thread. - 6
Pick the right format. JPEG for photos, PNG for screenshots and images needing exact pixels or transparency, SVG for logos and icons (Topic 5.6). WebP is typically 25–35% smaller than JPEG at similar quality, and AVIF often smaller still; both are supported by all current major browsers. Serve them with
<picture>andtype(Topic 5.1) if you need a JPEG fallback. Also compress, and never send a file wider than its largest displayed size × 2.
Explain it without notes
What is layout shift, and how do width and height attributes prevent it while still allowing a fluid image?
Which images should be lazy-loaded and which should not? Why?
What does fetchpriority="high" do, and where should you use it?
How would you cut the bytes of a page full of product photos?
Practice
Write a gallery image beach.jpg (1600×1067) that sits far down the page and must not cause layout shift.
Write the hero image hero.jpg (1920×800) for the top of a landing page so that it loads as early as possible.
Trade-offs
- ↔
Modern formats cut bytes but need an encoding step in your build and a fallback; AVIF also encodes slowly, so large sites often pre-generate it or use an image CDN.
- ↔
Lazy loading saves data for readers who don't scroll, but images may appear a moment late on very fast scrolling; keep the most likely-seen images eager.
Done when you can
I give every
<img>awidthandheightand useheight: autoin CSS.I lazy-load below-the-fold images and never the hero image.
I can use
fetchpriority="high"on the LCP image.I know what
decoding="async"hints.I can choose between JPEG, PNG, SVG, WebP and AVIF.
I can check LCP and CLS with Lighthouse.