Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 0a71528

Browse files
authored
Don't lazy-load already-loaded image in client-side transition (vercel#26968)
fixes vercel#19074 This change disables image lazy-loading when both of the following are true: 1) A image is being rendered following a client-side page transition 2) The image has been previously loaded during this session. Before this change, all images with lazy-loading enabled have a visible flicker during client-side page transitions, even though they're already loaded. With this change, there's are two performance risks: 1) There's a chance that some offscreen images will have lazy-loading disabled unnecessarily because they were previously loaded. I think the performance hit here is pretty negligible and the situation is unlikely to come up very often. 2) There's a chance a different-sized version of the image will be selected by the browser, but lazy-loading will be disabled anyway. This seems even more unlikely to me, and anyway the performance hit from a stray un-lazy-loaded image (on a client-side transition) is very minor. In both cases, I think the performance risk is outweighed by the UX improvement of getting rid of the image flicker on page transition.
1 parent 44ee7f0 commit 0a71528

File tree

3 files changed

+23
-14
lines changed

3 files changed

+23
-14
lines changed

packages/next/client/image.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
} from '../server/image-config'
1010
import { useIntersection } from './use-intersection'
1111

12+
const loadedImageURLs = new Set<string>()
13+
1214
if (typeof window === 'undefined') {
1315
;(global as any).__NEXT_IMAGE_IMPORTED = true
1416
}
@@ -264,6 +266,7 @@ function defaultImageLoader(loaderProps: ImageLoaderProps) {
264266
// handler instead of the img's onLoad attribute.
265267
function handleLoading(
266268
img: HTMLImageElement | null,
269+
src: string,
267270
placeholder: PlaceholderValue,
268271
onLoadingComplete?: () => void
269272
) {
@@ -279,6 +282,7 @@ function handleLoading(
279282
img.style.backgroundSize = 'none'
280283
img.style.backgroundImage = 'none'
281284
}
285+
loadedImageURLs.add(src)
282286
if (onLoadingComplete) {
283287
onLoadingComplete()
284288
}
@@ -423,6 +427,9 @@ export default function Image({
423427
unoptimized = true
424428
isLazy = false
425429
}
430+
if (src && typeof window !== 'undefined' && loadedImageURLs.has(src)) {
431+
isLazy = false
432+
}
426433

427434
const [setRef, isIntersected] = useIntersection<HTMLImageElement>({
428435
rootMargin: '200px',
@@ -558,6 +565,8 @@ export default function Image({
558565
})
559566
}
560567

568+
let srcString: string = src
569+
561570
return (
562571
<div style={wrapperStyle}>
563572
{sizerStyle ? (
@@ -605,7 +614,7 @@ export default function Image({
605614
className={className}
606615
ref={(img) => {
607616
setRef(img)
608-
handleLoading(img, placeholder, onLoadingComplete)
617+
handleLoading(img, srcString, placeholder, onLoadingComplete)
609618
}}
610619
style={imgStyle}
611620
/>

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ const Lazy = () => {
88
<p id="stubtext">This is a page with lazy-loaded images</p>
99
<Image
1010
id="lazy-top"
11-
src="foo1.jpg"
11+
src="lazy1.jpg"
1212
height={400}
1313
width={1024}
1414
loading="lazy"
1515
></Image>
1616
<div style={{ height: '2000px' }}></div>
1717
<Image
1818
id="lazy-mid"
19-
src="foo2.jpg"
19+
src="lazy2.jpg"
2020
loading="lazy"
2121
height={400}
2222
width={300}
@@ -25,7 +25,7 @@ const Lazy = () => {
2525
<div style={{ height: '2000px' }}></div>
2626
<Image
2727
id="lazy-bottom"
28-
src="https://www.otherhost.com/foo3.jpg"
28+
src="https://www.otherhost.com/lazy3.jpg"
2929
height={400}
3030
width={300}
3131
unoptimized
@@ -34,14 +34,14 @@ const Lazy = () => {
3434
<div style={{ height: '2000px' }}></div>
3535
<Image
3636
id="lazy-without-attribute"
37-
src="foo4.jpg"
37+
src="lazy4.jpg"
3838
height={400}
3939
width={800}
4040
></Image>
4141
<div style={{ height: '2000px' }}></div>
4242
<Image
4343
id="eager-loading"
44-
src="foo5.jpg"
44+
src="lazy5.jpg"
4545
loading="eager"
4646
height={400}
4747
width={1900}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,10 @@ function runTests() {
109109
function lazyLoadingTests() {
110110
it('should have loaded the first image immediately', async () => {
111111
expect(await browser.elementById('lazy-top').getAttribute('src')).toBe(
112-
'https://example.com/myaccount/foo1.jpg?auto=format&fit=max&w=2000'
112+
'https://example.com/myaccount/lazy1.jpg?auto=format&fit=max&w=2000'
113113
)
114114
expect(await browser.elementById('lazy-top').getAttribute('srcset')).toBe(
115-
'https://example.com/myaccount/foo1.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/foo1.jpg?auto=format&fit=max&w=2000 2x'
115+
'https://example.com/myaccount/lazy1.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/lazy1.jpg?auto=format&fit=max&w=2000 2x'
116116
)
117117
})
118118
it('should not have loaded the second image immediately', async () => {
@@ -140,11 +140,11 @@ function lazyLoadingTests() {
140140

141141
await check(() => {
142142
return browser.elementById('lazy-mid').getAttribute('src')
143-
}, 'https://example.com/myaccount/foo2.jpg?auto=format&fit=max&w=1024')
143+
}, 'https://example.com/myaccount/lazy2.jpg?auto=format&fit=max&w=1024')
144144

145145
await check(() => {
146146
return browser.elementById('lazy-mid').getAttribute('srcset')
147-
}, 'https://example.com/myaccount/foo2.jpg?auto=format&fit=max&w=480 1x, https://example.com/myaccount/foo2.jpg?auto=format&fit=max&w=1024 2x')
147+
}, 'https://example.com/myaccount/lazy2.jpg?auto=format&fit=max&w=480 1x, https://example.com/myaccount/lazy2.jpg?auto=format&fit=max&w=1024 2x')
148148
})
149149
it('should not have loaded the third image after scrolling down', async () => {
150150
expect(await browser.elementById('lazy-bottom').getAttribute('src')).toBe(
@@ -165,7 +165,7 @@ function lazyLoadingTests() {
165165
)
166166
await waitFor(200)
167167
expect(await browser.elementById('lazy-bottom').getAttribute('src')).toBe(
168-
'https://www.otherhost.com/foo3.jpg'
168+
'https://www.otherhost.com/lazy3.jpg'
169169
)
170170
expect(
171171
await browser.elementById('lazy-bottom').getAttribute('srcset')
@@ -189,17 +189,17 @@ function lazyLoadingTests() {
189189
await waitFor(200)
190190
expect(
191191
await browser.elementById('lazy-without-attribute').getAttribute('src')
192-
).toBe('https://example.com/myaccount/foo4.jpg?auto=format&fit=max&w=1600')
192+
).toBe('https://example.com/myaccount/lazy4.jpg?auto=format&fit=max&w=1600')
193193
expect(
194194
await browser.elementById('lazy-without-attribute').getAttribute('srcset')
195195
).toBe(
196-
'https://example.com/myaccount/foo4.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/foo4.jpg?auto=format&fit=max&w=1600 2x'
196+
'https://example.com/myaccount/lazy4.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/lazy4.jpg?auto=format&fit=max&w=1600 2x'
197197
)
198198
})
199199

200200
it('should load the fifth image eagerly, without scrolling', async () => {
201201
expect(await browser.elementById('eager-loading').getAttribute('src')).toBe(
202-
'https://example.com/myaccount/foo5.jpg?auto=format&fit=max&w=2000'
202+
'https://example.com/myaccount/lazy5.jpg?auto=format&fit=max&w=2000'
203203
)
204204
expect(
205205
await browser.elementById('eager-loading').getAttribute('srcset')

0 commit comments

Comments
 (0)