Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lazyRoot optional property to next/image component #33290

Merged
merged 25 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
132fdbb
Added 'rootEl' oprional property to next/Image component resembling '…
11koukou Jan 13, 2022
3fd478a
Merge branch 'canary' into koukou
11koukou Jan 13, 2022
b7c1af6
Merge branch 'canary' into koukou
11koukou Jan 14, 2022
6a59d95
Merge branch 'canary' into koukou
11koukou Jan 14, 2022
36fb8c6
Merge branch 'canary' into koukou
11koukou Jan 14, 2022
4a7922d
Merge branch 'canary' into koukou
11koukou Jan 14, 2022
3e8f06a
Merge branch 'canary' into koukou
11koukou Jan 15, 2022
12062ac
changed 'rootEl' to 'lazyBoundary' and its type as well
11koukou Jan 15, 2022
b25f147
Merge branch 'canary' of https://github.com/vercel/next.js into koukou
11koukou Jan 15, 2022
f2c026f
Merge branch 'koukou' of https://github.com/11koukou/next.js into koukou
11koukou Jan 15, 2022
890afb6
Merge branch 'canary' into koukou
11koukou Jan 16, 2022
cd76b61
Merge branch 'canary' into koukou
11koukou Jan 17, 2022
12dc7cb
Merge branch 'canary' into koukou
11koukou Jan 18, 2022
731f9f1
Merge branch 'canary' into koukou
11koukou Jan 20, 2022
b973e58
Merge branch 'canary' into koukou
11koukou Jan 20, 2022
14f9b5f
added test, fixed initial root detection
11koukou Jan 23, 2022
12c2ccc
Merge branch 'canary' into koukou
11koukou Jan 23, 2022
c50d278
Merge branch 'canary' into koukou
11koukou Jan 24, 2022
fe55cc5
Update test/integration/image-component/default/test/index.test.js
11koukou Jan 24, 2022
b49c235
prop names changed
11koukou Jan 24, 2022
7583a1b
added 'lazyroot' prop to the documentation
11koukou Jan 24, 2022
577e566
Merge branch 'canary' into koukou
11koukou Jan 24, 2022
8a84282
removed unused import
11koukou Jan 24, 2022
49d49d3
Apply suggestions from code review
styfle Jan 24, 2022
e62e3f1
Update docs with lazyRoot added in 12.0.9
styfle Jan 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/api-reference/next/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br/>AVIF support added.<br/>Wrapper `<div>` changed to `<span>`. |
| `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. |
| `v11.0.0` | `src` prop support for static import.<br/>`placeholder` prop added.<br/>`blurDataURL` prop added. |
Expand Down Expand Up @@ -221,6 +222,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 [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)

### unoptimized

When true, the source image will be served as-is instead of changing quality,
Expand Down
3 changes: 3 additions & 0 deletions packages/next/client/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export type ImageProps = Omit<
quality?: number | string
priority?: boolean
loading?: LoadingValue
lazyRoot?: React.RefObject<HTMLElement> | null
lazyBoundary?: string
placeholder?: PlaceholderValue
blurDataURL?: string
Expand Down Expand Up @@ -319,6 +320,7 @@ export default function Image({
unoptimized = false,
priority = false,
loading,
lazyRoot = null,
lazyBoundary = '200px',
className,
quality,
Expand Down Expand Up @@ -510,6 +512,7 @@ export default function Image({
}

const [setIntersection, isIntersected] = useIntersection<HTMLImageElement>({
rootRef: lazyRoot,
rootMargin: lazyBoundary,
disabled: !isLazy,
})
Expand Down
20 changes: 15 additions & 5 deletions packages/next/client/use-intersection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import {
cancelIdleCallback,
} from './request-idle-callback'

type UseIntersectionObserverInit = Pick<IntersectionObserverInit, 'rootMargin'>
type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit
type UseIntersectionObserverInit = Pick<
IntersectionObserverInit,
'rootMargin' | 'root'
>

type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit & {
rootRef?: React.RefObject<HTMLElement> | null
}
type ObserveCallback = (isVisible: boolean) => void
type Observer = {
id: string
Expand All @@ -16,14 +22,15 @@ type Observer = {
const hasIntersectionObserver = typeof IntersectionObserver !== 'undefined'

export function useIntersection<T extends Element>({
rootRef,
rootMargin,
disabled,
}: UseIntersection): [(element: T | null) => void, boolean] {
const isDisabled: boolean = disabled || !hasIntersectionObserver

const unobserve = useRef<Function>()
const [visible, setVisible] = useState(false)

const [root, setRoot] = useState(rootRef ? rootRef.current : null)
const setRef = useCallback(
(el: T | null) => {
if (unobserve.current) {
Expand All @@ -37,11 +44,11 @@ export function useIntersection<T extends Element>({
unobserve.current = observe(
el,
(isVisible) => isVisible && setVisible(isVisible),
{ rootMargin }
{ root, rootMargin }
)
}
},
[isDisabled, rootMargin, visible]
[isDisabled, root, rootMargin, visible]
)

useEffect(() => {
Expand All @@ -53,6 +60,9 @@ export function useIntersection<T extends Element>({
}
}, [visible])

useEffect(() => {
if (rootRef) setRoot(rootRef.current)
}, [rootRef])
return [setRef, visible]
}

Expand Down
32 changes: 32 additions & 0 deletions test/integration/image-component/default/pages/lazy-noref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useRef } from 'react'
import Image from 'next/image'

const Page = () => {
const myRef = useRef(null)

return (
<>
<div
ref={myRef}
style={{
width: '100%',
height: '400px',
position: 'relative',
overflowY: 'scroll',
}}
>
<div style={{ width: '400px', height: '600px' }}>hello</div>
<div style={{ width: '400px', position: 'relative', height: '600px' }}>
<Image
id="myImage"
src="/test.jpg"
width="400"
height="400"
lazyBoundary="1800px"
/>
</div>
</div>
</>
)
}
export default Page
33 changes: 33 additions & 0 deletions test/integration/image-component/default/pages/lazy-withref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useRef } from 'react'
import Image from 'next/image'

const Page = () => {
const myRef = useRef(null)

return (
<>
<div
ref={myRef}
style={{
width: '100%',
height: '400px',
position: 'relative',
overflowY: 'scroll',
}}
>
<div style={{ width: '400px', height: '600px' }}>hello</div>
<div style={{ width: '400px', position: 'relative', height: '600px' }}>
<Image
lazyRoot={myRef}
id="myImage"
src="/test.jpg"
width="400"
height="400"
lazyBoundary="1800px"
/>
</div>
</div>
</>
)
}
export default Page
31 changes: 31 additions & 0 deletions test/integration/image-component/default/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,37 @@ function runTests(mode) {
}
}
})

it('should load the image when the lazyRoot prop is used', 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', () => {
Expand Down