Skip to content

Commit 07adc8e

Browse files
authored
Change Image component lazy=true to loading=lazy (#18138)
This PR updates the `<Image>` component to follow the same property naming as native `<img>`. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Img#attr-loading This currently allows two values,`loading=lazy` and `loading=eager`, but there might be new values added in a future spec. cc @atcastle
1 parent fe4d16c commit 07adc8e

File tree

5 files changed

+82
-20
lines changed

5 files changed

+82
-20
lines changed

packages/next/client/image.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React, { ReactElement, useEffect, useRef } from 'react'
22
import Head from '../next-server/lib/head'
33

4+
const VALID_LOADING_VALUES = ['lazy', 'eager', undefined] as const
5+
type LoadingValue = typeof VALID_LOADING_VALUES[number]
6+
47
const loaders = new Map<LoaderKey, (props: LoaderProps) => string>([
58
['imgix', imgixLoader],
69
['cloudinary', cloudinaryLoader],
@@ -18,12 +21,12 @@ type ImageData = {
1821

1922
type ImageProps = Omit<
2023
JSX.IntrinsicElements['img'],
21-
'src' | 'srcSet' | 'ref' | 'width' | 'height'
24+
'src' | 'srcSet' | 'ref' | 'width' | 'height' | 'loading'
2225
> & {
2326
src: string
2427
quality?: string
2528
priority?: boolean
26-
lazy?: boolean
29+
loading?: LoadingValue
2730
unoptimized?: boolean
2831
} & (
2932
| { width: number; height: number; unsized?: false }
@@ -142,7 +145,7 @@ export default function Image({
142145
sizes,
143146
unoptimized = false,
144147
priority = false,
145-
lazy,
148+
loading,
146149
className,
147150
quality,
148151
width,
@@ -152,17 +155,23 @@ export default function Image({
152155
}: ImageProps) {
153156
const thisEl = useRef<HTMLImageElement>(null)
154157

155-
// Sanity Checks:
156-
// If priority and lazy are present, log an error and use priority only.
157-
if (priority && lazy) {
158-
if (process.env.NODE_ENV !== 'production') {
158+
if (process.env.NODE_ENV !== 'production') {
159+
if (!VALID_LOADING_VALUES.includes(loading)) {
160+
throw new Error(
161+
`Image with src "${src}" has invalid "loading" property. Provided "${loading}" should be one of ${VALID_LOADING_VALUES.map(
162+
String
163+
).join(',')}.`
164+
)
165+
}
166+
if (priority && loading === 'lazy') {
159167
throw new Error(
160-
`Image with src "${src}" has both "priority" and "lazy" properties. Only one should be used.`
168+
`Image with src "${src}" has both "priority" and "loading=lazy" properties. Only one should be used.`
161169
)
162170
}
163171
}
164172

165-
if (!priority && typeof lazy === 'undefined') {
173+
let lazy = loading === 'lazy'
174+
if (!priority && typeof loading === 'undefined') {
166175
lazy = true
167176
}
168177

test/integration/image-component/basic/pages/client-side.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const ClientSide = () => {
99
<Image
1010
id="basic-image"
1111
src="foo.jpg"
12-
lazy={false}
12+
loading="eager"
1313
width={300}
1414
height={400}
1515
quality={60}
@@ -18,7 +18,7 @@ const ClientSide = () => {
1818
id="attribute-test"
1919
data-demo="demo-value"
2020
src="bar.jpg"
21-
lazy={false}
21+
loading="eager"
2222
width={300}
2323
height={400}
2424
/>
@@ -27,15 +27,15 @@ const ClientSide = () => {
2727
data-demo="demo-value"
2828
host="secondary"
2929
src="foo2.jpg"
30-
lazy={false}
30+
loading="eager"
3131
width={300}
3232
height={400}
3333
/>
3434
<Image
3535
id="unoptimized-image"
3636
unoptimized
3737
src="https://arbitraryurl.com/foo.jpg"
38-
lazy={false}
38+
loading="eager"
3939
width={300}
4040
height={400}
4141
/>

test/integration/image-component/basic/pages/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const Page = () => {
99
<Image
1010
id="basic-image"
1111
src="foo.jpg"
12-
lazy={false}
12+
loading="eager"
1313
width={300}
1414
height={400}
1515
quality={60}
@@ -18,7 +18,7 @@ const Page = () => {
1818
id="attribute-test"
1919
data-demo="demo-value"
2020
src="bar.jpg"
21-
lazy={false}
21+
loading="eager"
2222
width={300}
2323
height={400}
2424
/>
@@ -27,15 +27,15 @@ const Page = () => {
2727
data-demo="demo-value"
2828
host="secondary"
2929
src="foo2.jpg"
30-
lazy={false}
30+
loading="eager"
3131
width={300}
3232
height={400}
3333
/>
3434
<Image
3535
id="unoptimized-image"
3636
unoptimized
3737
src="https://arbitraryurl.com/foo.jpg"
38-
lazy={false}
38+
loading="eager"
3939
width={300}
4040
height={400}
4141
/>

test/integration/image-component/basic/pages/lazy.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@ const Lazy = () => {
55
return (
66
<div>
77
<p id="stubtext">This is a page with lazy-loaded images</p>
8-
<Image id="lazy-top" src="foo1.jpg" height={400} width={300} lazy></Image>
8+
<Image
9+
id="lazy-top"
10+
src="foo1.jpg"
11+
height={400}
12+
width={300}
13+
loading="lazy"
14+
></Image>
915
<div style={{ height: '2000px' }}></div>
1016
<Image
1117
id="lazy-mid"
1218
src="foo2.jpg"
13-
lazy
19+
loading="lazy"
1420
height={400}
1521
width={300}
1622
className="exampleclass"
@@ -22,7 +28,22 @@ const Lazy = () => {
2228
height={400}
2329
width={300}
2430
unoptimized
25-
lazy
31+
loading="lazy"
32+
></Image>
33+
<div style={{ height: '2000px' }}></div>
34+
<Image
35+
id="lazy-without-attribute"
36+
src="foo4.jpg"
37+
height={400}
38+
width={300}
39+
></Image>
40+
<div style={{ height: '2000px' }}></div>
41+
<Image
42+
id="eager-loading"
43+
src="foo5.jpg"
44+
loading="eager"
45+
height={400}
46+
width={300}
2647
></Image>
2748
</div>
2849
)

test/integration/image-component/basic/test/index.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,38 @@ function lazyLoadingTests() {
132132
await browser.elementById('lazy-bottom').getAttribute('srcset')
133133
).toBeFalsy()
134134
})
135+
it('should load the fourth image lazily after scrolling down', async () => {
136+
expect(
137+
await browser.elementById('lazy-without-attribute').getAttribute('src')
138+
).toBeFalsy()
139+
expect(
140+
await browser.elementById('lazy-without-attribute').getAttribute('srcset')
141+
).toBeFalsy()
142+
let viewportHeight = await browser.eval(`window.innerHeight`)
143+
let topOfBottomImage = await browser.eval(
144+
`document.getElementById('lazy-without-attribute').parentElement.offsetTop`
145+
)
146+
let buffer = 150
147+
await browser.eval(
148+
`window.scrollTo(0, ${topOfBottomImage - (viewportHeight + buffer)})`
149+
)
150+
await waitFor(200)
151+
expect(
152+
await browser.elementById('lazy-without-attribute').getAttribute('src')
153+
).toBe('https://example.com/myaccount/foo4.jpg?auto=format')
154+
expect(
155+
await browser.elementById('lazy-without-attribute').getAttribute('srcset')
156+
).toBeTruthy()
157+
})
158+
159+
it('should load the fifth image eagerly, without scrolling', async () => {
160+
expect(await browser.elementById('eager-loading').getAttribute('src')).toBe(
161+
'https://example.com/myaccount/foo5.jpg?auto=format'
162+
)
163+
expect(
164+
await browser.elementById('eager-loading').getAttribute('srcset')
165+
).toBeTruthy()
166+
})
135167
}
136168

137169
async function hasPreloadLinkMatchingUrl(url) {

0 commit comments

Comments
 (0)