From 132fdbb5b5bdc4a360f14ca6a61a26a85d7a2b4d Mon Sep 17 00:00:00 2001 From: 11koukou Date: Fri, 14 Jan 2022 00:32:39 +0200 Subject: [PATCH 1/9] Added 'rootEl' oprional property to next/Image component resembling 'root' option of the Intersection Observer API --- packages/next/client/image.tsx | 5 +++++ packages/next/client/use-intersection.tsx | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index d242b731f250..37572b9725db 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -101,6 +101,8 @@ export type ImageProps = Omit< quality?: number | string priority?: boolean loading?: LoadingValue + //added 'rootEl' property of type HTMLElement, nullable for being able to set default value to null (see Below) + rootEl?: HTMLElement | null lazyBoundary?: string placeholder?: PlaceholderValue blurDataURL?: string @@ -315,6 +317,7 @@ export default function Image({ unoptimized = false, priority = false, loading, + rootEl = null, //setting to null for the Intersection Observer API to set html root element as 'root' by default lazyBoundary = '200px', className, quality, @@ -505,6 +508,8 @@ export default function Image({ } const [setRef, isIntersected] = useIntersection({ + //added 'root' to Options + root: rootEl, rootMargin: lazyBoundary, disabled: !isLazy, }) diff --git a/packages/next/client/use-intersection.tsx b/packages/next/client/use-intersection.tsx index 62d188f579a1..8ad40c9e6e19 100644 --- a/packages/next/client/use-intersection.tsx +++ b/packages/next/client/use-intersection.tsx @@ -4,7 +4,11 @@ import { cancelIdleCallback, } from './request-idle-callback' -type UseIntersectionObserverInit = Pick +type UseIntersectionObserverInit = Pick< + IntersectionObserverInit, + 'rootMargin' | 'root' +> //added 'root' as a property + type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit type ObserveCallback = (isVisible: boolean) => void type Observer = { @@ -16,6 +20,8 @@ type Observer = { const hasIntersectionObserver = typeof IntersectionObserver !== 'undefined' export function useIntersection({ + //added 'root' as an option + root, rootMargin, disabled, }: UseIntersection): [(element: T | null) => void, boolean] { @@ -37,11 +43,11 @@ export function useIntersection({ unobserve.current = observe( el, (isVisible) => isVisible && setVisible(isVisible), - { rootMargin } + { root, rootMargin } //added 'root' as an option ) } }, - [isDisabled, rootMargin, visible] + [isDisabled, root, rootMargin, visible] //added 'root' as a dependency ) useEffect(() => { From 12062accf88a4ba980f90be199981035b989e728 Mon Sep 17 00:00:00 2001 From: 11koukou Date: Sat, 15 Jan 2022 09:11:59 +0200 Subject: [PATCH 2/9] changed 'rootEl' to 'lazyBoundary' and its type as well --- packages/next/client/image.tsx | 8 +++----- packages/next/client/use-intersection.tsx | 7 +++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 37572b9725db..44bb9c960fc2 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -101,8 +101,7 @@ export type ImageProps = Omit< quality?: number | string priority?: boolean loading?: LoadingValue - //added 'rootEl' property of type HTMLElement, nullable for being able to set default value to null (see Below) - rootEl?: HTMLElement | null + lazyRoot?: React.RefObject | null lazyBoundary?: string placeholder?: PlaceholderValue blurDataURL?: string @@ -317,7 +316,7 @@ export default function Image({ unoptimized = false, priority = false, loading, - rootEl = null, //setting to null for the Intersection Observer API to set html root element as 'root' by default + lazyRoot = null, lazyBoundary = '200px', className, quality, @@ -508,8 +507,7 @@ export default function Image({ } const [setRef, isIntersected] = useIntersection({ - //added 'root' to Options - root: rootEl, + root: lazyRoot?.current, rootMargin: lazyBoundary, disabled: !isLazy, }) diff --git a/packages/next/client/use-intersection.tsx b/packages/next/client/use-intersection.tsx index 8ad40c9e6e19..7509c71d173e 100644 --- a/packages/next/client/use-intersection.tsx +++ b/packages/next/client/use-intersection.tsx @@ -7,7 +7,7 @@ import { type UseIntersectionObserverInit = Pick< IntersectionObserverInit, 'rootMargin' | 'root' -> //added 'root' as a property +> type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit type ObserveCallback = (isVisible: boolean) => void @@ -20,7 +20,6 @@ type Observer = { const hasIntersectionObserver = typeof IntersectionObserver !== 'undefined' export function useIntersection({ - //added 'root' as an option root, rootMargin, disabled, @@ -43,11 +42,11 @@ export function useIntersection({ unobserve.current = observe( el, (isVisible) => isVisible && setVisible(isVisible), - { root, rootMargin } //added 'root' as an option + { root, rootMargin } ) } }, - [isDisabled, root, rootMargin, visible] //added 'root' as a dependency + [isDisabled, root, rootMargin, visible] ) useEffect(() => { From 14f9b5f2ad195f612806dfafec672d51d8756ba6 Mon Sep 17 00:00:00 2001 From: 11koukou Date: Sun, 23 Jan 2022 10:10:18 +0200 Subject: [PATCH 3/9] added test, fixed initial root detection --- packages/next/client/image.tsx | 3 +- packages/next/client/use-intersection.tsx | 16 ++++++--- .../default/pages/lazy-noref.js | 32 ++++++++++++++++++ .../default/pages/lazy-withref.js | 33 +++++++++++++++++++ .../default/test/index.test.js | 31 +++++++++++++++++ 5 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 test/integration/image-component/default/pages/lazy-noref.js create mode 100644 test/integration/image-component/default/pages/lazy-withref.js diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index bc565e376935..5828c9560754 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -511,9 +511,8 @@ export default function Image({ } } - const [setIntersection, isIntersected] = useIntersection({ - root: lazyRoot?.current, + rootEl: lazyRoot, rootMargin: lazyBoundary, disabled: !isLazy, }) diff --git a/packages/next/client/use-intersection.tsx b/packages/next/client/use-intersection.tsx index 7509c71d173e..6a67279cdab2 100644 --- a/packages/next/client/use-intersection.tsx +++ b/packages/next/client/use-intersection.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react' +import { ReactLoadablePlugin } from '../build/webpack/plugins/react-loadable-plugin' import { requestIdleCallback, cancelIdleCallback, @@ -9,7 +10,9 @@ type UseIntersectionObserverInit = Pick< 'rootMargin' | 'root' > -type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit +type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit & { + rootEl?: React.RefObject | null + } type ObserveCallback = (isVisible: boolean) => void type Observer = { id: string @@ -20,7 +23,7 @@ type Observer = { const hasIntersectionObserver = typeof IntersectionObserver !== 'undefined' export function useIntersection({ - root, + rootEl, rootMargin, disabled, }: UseIntersection): [(element: T | null) => void, boolean] { @@ -28,7 +31,7 @@ export function useIntersection({ const unobserve = useRef() const [visible, setVisible] = useState(false) - + const [myRoot, setMyroot] = useState(rootEl ? rootEl.current : null) const setRef = useCallback( (el: T | null) => { if (unobserve.current) { @@ -42,11 +45,11 @@ export function useIntersection({ unobserve.current = observe( el, (isVisible) => isVisible && setVisible(isVisible), - { root, rootMargin } + { root: myRoot, rootMargin } ) } }, - [isDisabled, root, rootMargin, visible] + [isDisabled, myRoot, rootMargin, visible] ) useEffect(() => { @@ -58,6 +61,9 @@ export function useIntersection({ } }, [visible]) + useEffect(() => { + if (rootEl) setMyroot(rootEl.current) + }, [rootEl]) return [setRef, visible] } diff --git a/test/integration/image-component/default/pages/lazy-noref.js b/test/integration/image-component/default/pages/lazy-noref.js new file mode 100644 index 000000000000..3edfe7df7902 --- /dev/null +++ b/test/integration/image-component/default/pages/lazy-noref.js @@ -0,0 +1,32 @@ +import React, { useRef } from 'react' +import Image from 'next/image' + +const Page = () => { + const myRef = useRef(null) + + return ( + <> +
+
hello
+
+ +
+
+ + ) +} +export default Page diff --git a/test/integration/image-component/default/pages/lazy-withref.js b/test/integration/image-component/default/pages/lazy-withref.js new file mode 100644 index 000000000000..d21a4c0a9dad --- /dev/null +++ b/test/integration/image-component/default/pages/lazy-withref.js @@ -0,0 +1,33 @@ +import React, { useRef } from 'react' +import Image from 'next/image' + +const Page = () => { + const myRef = useRef(null) + + return ( + <> +
+
hello
+
+ +
+
+ + ) +} +export default Page diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index d7d2c050f7c4..1c0a2a276743 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -1008,6 +1008,37 @@ function runTests(mode) { } } }) + + it('should load the lazy', async () => { + let browser + try { + //trying on '/lazy-noref' it fails + browser = await webdriver(appPort, '/lazy-withref') + + await check(async () => { + const result = await browser.eval( + `document.getElementById('myImage').naturalWidth` + ) + + if (result < 400) { + throw new Error('Incorrectly loaded image') + } + + return 'result-correct' + }, /result-correct/) + + expect( + await hasImageMatchingUrl( + browser, + `http://localhost:${appPort}/_next/image?url=%2Ftest.jpg&w=828&q=75` + ) + ).toBe(true) + } finally { + if (browser) { + await browser.close() + } + } + }) } describe('Image Component Tests', () => { From fe55cc579d51cb9269ce0c062e58eb99efcb5610 Mon Sep 17 00:00:00 2001 From: 11koukou <97431276+11koukou@users.noreply.github.com> Date: Mon, 24 Jan 2022 23:48:37 +0200 Subject: [PATCH 4/9] Update test/integration/image-component/default/test/index.test.js Co-authored-by: Steven --- test/integration/image-component/default/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 1c0a2a276743..5dae3d31658c 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -1009,7 +1009,7 @@ function runTests(mode) { } }) - it('should load the lazy', async () => { + it('should load the image when the lazyRoot prop is used', async () => { let browser try { //trying on '/lazy-noref' it fails From b49c23505a80175ef04aa6094c8bc1aa07f06ef3 Mon Sep 17 00:00:00 2001 From: 11koukou Date: Mon, 24 Jan 2022 23:53:57 +0200 Subject: [PATCH 5/9] prop names changed --- packages/next/client/image.tsx | 2 +- packages/next/client/use-intersection.tsx | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 5828c9560754..0980f8abd535 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -512,7 +512,7 @@ export default function Image({ } const [setIntersection, isIntersected] = useIntersection({ - rootEl: lazyRoot, + rootRef: lazyRoot, rootMargin: lazyBoundary, disabled: !isLazy, }) diff --git a/packages/next/client/use-intersection.tsx b/packages/next/client/use-intersection.tsx index 6a67279cdab2..62ecd6251f81 100644 --- a/packages/next/client/use-intersection.tsx +++ b/packages/next/client/use-intersection.tsx @@ -11,7 +11,7 @@ type UseIntersectionObserverInit = Pick< > type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit & { - rootEl?: React.RefObject | null + rootRef?: React.RefObject | null } type ObserveCallback = (isVisible: boolean) => void type Observer = { @@ -23,7 +23,7 @@ type Observer = { const hasIntersectionObserver = typeof IntersectionObserver !== 'undefined' export function useIntersection({ - rootEl, + rootRef, rootMargin, disabled, }: UseIntersection): [(element: T | null) => void, boolean] { @@ -31,7 +31,7 @@ export function useIntersection({ const unobserve = useRef() const [visible, setVisible] = useState(false) - const [myRoot, setMyroot] = useState(rootEl ? rootEl.current : null) + const [root, setRoot] = useState(rootRef ? rootRef.current : null) const setRef = useCallback( (el: T | null) => { if (unobserve.current) { @@ -45,11 +45,11 @@ export function useIntersection({ unobserve.current = observe( el, (isVisible) => isVisible && setVisible(isVisible), - { root: myRoot, rootMargin } + { root: root, rootMargin } ) } }, - [isDisabled, myRoot, rootMargin, visible] + [isDisabled, root, rootMargin, visible] ) useEffect(() => { @@ -62,8 +62,8 @@ export function useIntersection({ }, [visible]) useEffect(() => { - if (rootEl) setMyroot(rootEl.current) - }, [rootEl]) + if (rootRef) setRoot(rootRef.current) + }, [rootRef]) return [setRef, visible] } From 7583a1b79297b8961250b70ff3e43a76bf61dc23 Mon Sep 17 00:00:00 2001 From: 11koukou Date: Tue, 25 Jan 2022 00:27:53 +0200 Subject: [PATCH 6/9] added 'lazyroot' prop to the documentation --- docs/api-reference/next/image.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index b6a6640e3dd5..64101ee7e6f3 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -221,6 +221,12 @@ A string (with similar syntax to the margin property) that acts as the bounding [Learn more](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) +### lazyRoot + +A React RefObject pointing to the Element which the [lazyBoundary](#lazyBoundary) calculates for the Intersection detection. Defaults to 'null', refering to the root Html Element. + +[Learn more](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/root) + ### unoptimized When true, the source image will be served as-is instead of changing quality, From 8a84282df715a25e0a0de4677e59977f8b926354 Mon Sep 17 00:00:00 2001 From: 11koukou Date: Tue, 25 Jan 2022 00:38:19 +0200 Subject: [PATCH 7/9] removed unused import --- packages/next/client/use-intersection.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next/client/use-intersection.tsx b/packages/next/client/use-intersection.tsx index 62ecd6251f81..75937dfa3151 100644 --- a/packages/next/client/use-intersection.tsx +++ b/packages/next/client/use-intersection.tsx @@ -1,5 +1,4 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { ReactLoadablePlugin } from '../build/webpack/plugins/react-loadable-plugin' import { requestIdleCallback, cancelIdleCallback, From 49d49d38cd0e76b310597eb6e15100b9547ea7b5 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 24 Jan 2022 18:17:46 -0500 Subject: [PATCH 8/9] Apply suggestions from code review --- docs/api-reference/next/image.md | 2 +- packages/next/client/use-intersection.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 4c05a8f783e9..6d370747c928 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -223,7 +223,7 @@ A string (with similar syntax to the margin property) that acts as the bounding ### lazyRoot -A React RefObject pointing to the Element which the [lazyBoundary](#lazyBoundary) calculates for the Intersection detection. Defaults to 'null', refering to the root Html Element. +A React [Ref](https://reactjs.org/docs/refs-and-the-dom.html) pointing to the Element which the [lazyBoundary](#lazyBoundary) calculates for the Intersection detection. Defaults to `null`, referring to the document viewport. [Learn more](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/root) diff --git a/packages/next/client/use-intersection.tsx b/packages/next/client/use-intersection.tsx index 75937dfa3151..94ee9d97f1d1 100644 --- a/packages/next/client/use-intersection.tsx +++ b/packages/next/client/use-intersection.tsx @@ -44,7 +44,7 @@ export function useIntersection({ unobserve.current = observe( el, (isVisible) => isVisible && setVisible(isVisible), - { root: root, rootMargin } + { root, rootMargin } ) } }, From e62e3f12949ffe775fe5fda4869fbdfeb3adae45 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 24 Jan 2022 18:21:23 -0500 Subject: [PATCH 9/9] Update docs with lazyRoot added in 12.0.9 --- docs/api-reference/next/image.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 6d370747c928..830771ce3447 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -16,6 +16,7 @@ description: Enable Image Optimization with the built-in Image component. | Version | Changes | | --------- | ------------------------------------------------------------------------------------------------- | +| `v12.0.9` | `lazyRoot` prop added | | `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | | `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | | `v11.0.0` | `src` prop support for static import.
`placeholder` prop added.
`blurDataURL` prop added. |