Skip to content

Commit c7d0453

Browse files
committed
Image: default lazy images to sizes="auto" for optimal srcset selection
The JS Image component injects the real <img> only once it scrolls into view, at which point its box is already laid out, so sizes="auto" resolves from the rendered width. Add loading="lazy" + sizes="auto, 100vw" to the injected <img> (and the shared <source> elements / noscript fallback) when no explicit sizes is given and the image is not priority. Verified in Chrome: a 400px slot now fetches the 400w candidate instead of the 1600w one. Degrades to 100vw on browsers without sizes="auto" support (e.g. Safari) — no regression.
1 parent 5b34ece commit c7d0453

1 file changed

Lines changed: 20 additions & 2 deletions

File tree

src/Image/index.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,24 @@ export const Image = forwardRef<HTMLDivElement, ImagePropTypes>(
202202
const addImage = imageAddStrategy({ priority, inView, loaded });
203203
const showImage = imageShowStrategy({ priority, inView, loaded });
204204

205-
const webpSource = buildWebpSource(data, sizes);
206-
const regularSource = buildRegularSource(data, sizes, srcSetCandidates);
205+
// When no explicit `sizes` is given, default lazy images to `sizes="auto"`
206+
// so the browser picks the optimal `srcset` candidate from the rendered
207+
// width. This component injects the real <img> only once it scrolls into
208+
// view, at which point its box is already laid out — so `auto` resolves
209+
// correctly. Per the HTML spec, `auto` needs the <img> to be lazy AND carry
210+
// a `sizes` starting with `auto`, and a <source>'s `auto` only engages when
211+
// its sibling <img> does too — hence `resolvedSizes` + `loading="lazy"` on
212+
// the injected <img> below (and the <noscript> fallback already is lazy).
213+
// Skipped for `priority` (eager) images; `, 100vw` is the legacy fallback.
214+
const resolvedSizes =
215+
sizes ?? data.sizes ?? (priority ? undefined : 'auto, 100vw');
216+
217+
const webpSource = buildWebpSource(data, resolvedSizes);
218+
const regularSource = buildRegularSource(
219+
data,
220+
resolvedSizes,
221+
srcSetCandidates,
222+
);
207223

208224
const transition =
209225
fadeInDuration > 0 ? `opacity ${fadeInDuration}ms` : undefined;
@@ -298,6 +314,8 @@ export const Image = forwardRef<HTMLDivElement, ImagePropTypes>(
298314
alt={data.alt ?? ''}
299315
title={data.title ?? undefined}
300316
onLoad={handleLoad}
317+
sizes={resolvedSizes}
318+
loading={priority ? undefined : 'lazy'}
301319
{...priorityProp(priority ? 'high' : undefined)}
302320
className={imgClassName}
303321
style={{

0 commit comments

Comments
 (0)