From 5d7f7a47afa554e5b04c15aebb7ee1f9d3628cfd Mon Sep 17 00:00:00 2001 From: Alex Castle Date: Tue, 27 Oct 2020 16:41:19 -0700 Subject: [PATCH] Unlazify images if no intersection observer found (#18345) --- packages/next/client/image.tsx | 33 ++++++++++++------- .../image-component/basic/pages/lazy.js | 4 +++ .../basic/pages/missing-observer.js | 25 ++++++++++++++ .../image-component/basic/test/index.test.js | 24 ++++++++++++++ 4 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 test/integration/image-component/basic/pages/missing-observer.js diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 54e6635ed5c6..f861def1a300 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -48,10 +48,10 @@ configDeviceSizes.sort((a, b) => a - b) configImageSizes.sort((a, b) => a - b) let cachedObserver: IntersectionObserver -const IntersectionObserver = - typeof window !== 'undefined' ? window.IntersectionObserver : null function getObserver(): IntersectionObserver | undefined { + const IntersectionObserver = + typeof window !== 'undefined' ? window.IntersectionObserver : null // Return shared instance of IntersectionObserver if already created if (cachedObserver) { return cachedObserver @@ -61,20 +61,12 @@ function getObserver(): IntersectionObserver | undefined { if (!IntersectionObserver) { return undefined } - return (cachedObserver = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { let lazyImage = entry.target as HTMLImageElement - if (lazyImage.dataset.src) { - lazyImage.src = lazyImage.dataset.src - } - if (lazyImage.dataset.srcset) { - lazyImage.srcset = lazyImage.dataset.srcset - } - lazyImage.style.visibility = 'visible' - lazyImage.classList.remove('__lazy') + unLazifyImage(lazyImage) cachedObserver.unobserve(lazyImage) } }) @@ -83,6 +75,17 @@ function getObserver(): IntersectionObserver | undefined { )) } +function unLazifyImage(lazyImage: HTMLImageElement): void { + if (lazyImage.dataset.src) { + lazyImage.src = lazyImage.dataset.src + } + if (lazyImage.dataset.srcset) { + lazyImage.srcset = lazyImage.dataset.srcset + } + lazyImage.style.visibility = 'visible' + lazyImage.classList.remove('__lazy') +} + function getDeviceSizes(width: number | undefined): number[] { if (typeof width !== 'number') { return configDeviceSizes @@ -234,6 +237,11 @@ export default function Image({ lazy = true } + if (typeof window !== 'undefined' && !window.IntersectionObserver) { + // Rendering client side on browser without intersection observer + lazy = false + } + useEffect(() => { const target = thisEl.current @@ -246,6 +254,9 @@ export default function Image({ return () => { observer.unobserve(target) } + } else { + //browsers without intersection observer + unLazifyImage(target) } } }, [thisEl, lazy]) diff --git a/test/integration/image-component/basic/pages/lazy.js b/test/integration/image-component/basic/pages/lazy.js index 161b530bc3c3..d91705249fb4 100644 --- a/test/integration/image-component/basic/pages/lazy.js +++ b/test/integration/image-component/basic/pages/lazy.js @@ -1,5 +1,6 @@ import React from 'react' import Image from 'next/image' +import Link from 'next/link' const Lazy = () => { return ( @@ -45,6 +46,9 @@ const Lazy = () => { height={400} width={1900} > + + observer + ) } diff --git a/test/integration/image-component/basic/pages/missing-observer.js b/test/integration/image-component/basic/pages/missing-observer.js new file mode 100644 index 000000000000..7def70f2f0c7 --- /dev/null +++ b/test/integration/image-component/basic/pages/missing-observer.js @@ -0,0 +1,25 @@ +import React, { useLayoutEffect } from 'react' +import Image from 'next/image' + +const Lazy = () => { + useLayoutEffect(() => { + IntersectionObserver = null //eslint-disable-line + }) + return ( +
+

+ This is a page with one lazy-loaded image, to be used in the test for + browsers without intersection observer. +

+ +
+ ) +} + +export default Lazy diff --git a/test/integration/image-component/basic/test/index.test.js b/test/integration/image-component/basic/test/index.test.js index c5c6d5762e9d..bbdf5cf2162b 100644 --- a/test/integration/image-component/basic/test/index.test.js +++ b/test/integration/image-component/basic/test/index.test.js @@ -280,6 +280,17 @@ describe('Image Component Tests', () => { browser = null }) lazyLoadingTests() + it('should automatically load images if observer does not exist', async () => { + browser = await webdriver(appPort, '/missing-observer') + expect( + await browser.elementById('lazy-no-observer').getAttribute('src') + ).toBe('https://example.com/myaccount/foox.jpg?auto=format&w=1024') + expect( + await browser.elementById('lazy-no-observer').getAttribute('srcset') + ).toBe( + 'https://example.com/myaccount/foox.jpg?auto=format&w=480 480w, https://example.com/myaccount/foox.jpg?auto=format&w=1024 1024w' + ) + }) }) describe('Client-side Lazy Loading Tests', () => { beforeAll(async () => { @@ -291,5 +302,18 @@ describe('Image Component Tests', () => { browser = null }) lazyLoadingTests() + it('should automatically load images if observer does not exist', async () => { + await browser.waitForElementByCss('#observerlink').click() + await waitFor(500) + browser = await webdriver(appPort, '/missing-observer') + expect( + await browser.elementById('lazy-no-observer').getAttribute('src') + ).toBe('https://example.com/myaccount/foox.jpg?auto=format&w=1024') + expect( + await browser.elementById('lazy-no-observer').getAttribute('srcset') + ).toBe( + 'https://example.com/myaccount/foox.jpg?auto=format&w=480 480w, https://example.com/myaccount/foox.jpg?auto=format&w=1024 1024w' + ) + }) }) })