Skip to content

Commit e6467e1

Browse files
author
fpapado
committed
Add experimental img.decode() support
1 parent 8050820 commit e6467e1

File tree

4 files changed

+92
-35
lines changed

4 files changed

+92
-35
lines changed

README.md

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -410,29 +410,32 @@ The presentation can be derived from those plus, crucially, any specific needs y
410410
411411
**`<LazyImage />`** accepts the following props:
412412
413-
| Name | Type | Default | Required | Description |
414-
| ----------------- | ------------------------------------------------------------------------- | ----------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
415-
| **src** | String | | true | The source of the image to load |
416-
| **alt** | String | | false | The alt text description of the image you are loading |
417-
| **srcSet** | String | | false | If your images use srcset, you can pass the `srcSet` prop to provide that information for preloading. |
418-
| **sizes** | String | | false | If your images use srcset, the sizes attribute helps the browser decide which source to load. |
419-
| **actual** | Function (render callback) of type ({imageProps}) => React.ReactNode | | true | Component to display once image has loaded |
420-
| **placeholder** | Function (render callback) of type ({imageProps, ref}) => React.ReactNode | undefined | true | Component to display while no request for the actual image has been made |
421-
| **loading** | Function (render callback) of type () => React.ReactNode | placeholder | false | Component to display while the image is loading |
422-
| **error** | Function (render callback) of type () => React.ReactNode | actual (broken image) | false | Component to display if the image loading has failed (render prop) |
423-
| **loadEagerly** | Boolean | false | false | Whether to skip checking for viewport and always show the 'actual' component |
424-
| **observerProps** | {threshold: number, rootMargin: string} | {threshold: 0.01, rootMargin: "50px 0px"} | false | Subset of props for the IntersectionObserver |
413+
| Name | Type | Default | Required | Description |
414+
| ---------------------- | ------------------------------------------------------------------------- | ----------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
415+
| **src** | String | | true | The source of the image to load |
416+
| **alt** | String | | false | The alt text description of the image you are loading |
417+
| **srcSet** | String | | false | If your images use srcset, you can pass the `srcSet` prop to provide that information for preloading. |
418+
| **sizes** | String | | false | If your images use srcset, the sizes attribute helps the browser decide which source to load. |
419+
| **actual** | Function (render callback) of type ({imageProps}) => React.ReactNode | | true | Component to display once image has loaded |
420+
| **placeholder** | Function (render callback) of type ({imageProps, ref}) => React.ReactNode | undefined | true | Component to display while no request for the actual image has been made |
421+
| **loading** | Function (render callback) of type () => React.ReactNode | placeholder | false | Component to display while the image is loading |
422+
| **error** | Function (render callback) of type () => React.ReactNode | actual (broken image) | false | Component to display if the image loading has failed (render prop) |
423+
| **loadEagerly** | Boolean | false | false | Whether to skip checking for viewport and always show the 'actual' component |
424+
| **observerProps** | {threshold: number, rootMargin: string} | {threshold: 0.01, rootMargin: "50px 0px"} | false | Subset of props for the IntersectionObserver |
425+
| **experimentalDecode** | Boolean | false | false | Decode the image off-main-thread using the Image Decode API. Test before using! |
425426
426427
**`<LazyImageFull />`** accepts the following props:
427428
428-
| Name | Type | Default | Required | Description |
429-
| ----------------- | ------------------------------------------------------------------- | ----------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
430-
| **src** | String | | true | The source of the image to load |
431-
| **alt** | String | | false | The alt text description of the image you are loading |
432-
| **srcSet** | String | | false | If your images use srcset, you can pass the `srcSet` prop to provide that information for preloading. |
433-
| **loadEagerly** | Boolean | false | false | Whether to skip checking for viewport and always show the 'actual' component |
434-
| **observerProps** | {threshold: number, rootMargin: string} | {threshold: 0.01, rootMargin: "50px 0px"} | false | Subset of props for the IntersectionObserver |
435-
| **children** | Function of type ({imageProps, imageState, ref}) => React.ReactNode | | true | Function to call that renders based on the props and state provided to it by LazyImageFull |
429+
| Name | Type | Default | Required | Description |
430+
| ---------------------- | ------------------------------------------------------------------- | ----------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
431+
| **src** | String | | true | The source of the image to load |
432+
| **alt** | String | | false | The alt text description of the image you are loading |
433+
| **srcSet** | String | | false | If your images use srcset, you can pass the `srcSet` prop to provide that information for preloading. |
434+
| **sizes** | String | | false | If your images use srcset, the sizes attribute helps the browser decide which source to load. |
435+
| **loadEagerly** | Boolean | false | false | Whether to skip checking for viewport and always show the 'actual' component |
436+
| **observerProps** | {threshold: number, rootMargin: string} | {threshold: 0.01, rootMargin: "50px 0px"} | false | Subset of props for the IntersectionObserver |
437+
| **children** | Function of type ({imageProps, imageState, ref}) => React.ReactNode | | true | Function to call that renders based on the props and state provided to it by LazyImageFull |
438+
| **experimentalDecode** | Boolean | false | false | Decode the image off-main-thread using the Image Decode API. Test before using! |
436439
437440
[You can consult Typescript types in the code](./src/LazyImage.tsx) for more context.
438441

ROADMAP.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@
99
- [x] Upgrade microbundle
1010
- [x] Check --external all consequence
1111
- [x] Upgrade react-intersection-observer
12-
- [ ] Pass Ref
13-
- [ ] Prop getter
14-
- [ ] Split out renderXYZ components
15-
- [ ] Refactor props
16-
- [ ] Props getter
12+
- [x] Pass Ref
13+
- [x] Prop collection
14+
- [x] Split out renderXYZ components
15+
- [x] Refactor props
1716
- [x] Elicit feedback and use cases for the public API
18-
- [ ] Investigate container `<div>`
17+
- [~] Investigate container `<div>`
1918
- [ ] Solidify `<noscript>` fallback
20-
- [ ] experimentalDecoding
19+
- [x] experimentalDecode
2120

2221
# Investigate
2322

src/LazyImageFull.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ export type CommonLazyImageProps = ImageProps & {
1414
* @see https://github.com/thebuilder/react-intersection-observer#props
1515
*/
1616
observerProps?: ObserverProps;
17+
18+
/** Use the Image Decode API;
19+
* The call to a new HTML <img> element’s decode() function returns a promise, which,
20+
* when fulfilled, ensures that the image can be appended to the DOM without causing
21+
* a decoding delay on the next frame.
22+
* @see: https://www.chromestatus.com/feature/5637156160667648
23+
*/
24+
experimentalDecode?: boolean;
1725
};
1826

1927
/** Valid props for LazyImageFull */
@@ -119,12 +127,15 @@ export class LazyImageFull extends React.Component<
119127
imageState: ImageState.Loading
120128
}));
121129

122-
loadImage({
123-
src: this.props.src,
124-
srcSet: this.props.srcSet,
125-
alt: this.props.alt,
126-
sizes: this.props.sizes
127-
})
130+
loadImage(
131+
{
132+
src: this.props.src,
133+
srcSet: this.props.srcSet,
134+
alt: this.props.alt,
135+
sizes: this.props.sizes
136+
},
137+
this.props.experimentalDecode
138+
)
128139
.then(this.onLoadSuccess)
129140
.catch(this.onLoadError);
130141
}
@@ -147,7 +158,13 @@ export class LazyImageFull extends React.Component<
147158

148159
// Render function
149160
render() {
150-
const { children, loadEagerly, observerProps, ...imageProps } = this.props;
161+
const {
162+
children,
163+
loadEagerly,
164+
observerProps,
165+
experimentalDecode,
166+
...imageProps
167+
} = this.props;
151168

152169
if (loadEagerly) {
153170
// If eager, skip the observer and view changing stuff; resolve the imageState as loaded.
@@ -173,7 +190,10 @@ export class LazyImageFull extends React.Component<
173190
// Utilities
174191

175192
/** Promise constructor for loading an image */
176-
const loadImage = ({ src, srcSet, alt, sizes }: ImageProps) =>
193+
const loadImage = (
194+
{ src, srcSet, alt, sizes }: ImageProps,
195+
experimentalDecode = false
196+
) =>
177197
new Promise((resolve, reject) => {
178198
const image = new Image();
179199
if (srcSet) {
@@ -186,6 +206,18 @@ const loadImage = ({ src, srcSet, alt, sizes }: ImageProps) =>
186206
image.sizes = sizes;
187207
}
188208
image.src = src;
209+
210+
/** @see: https://www.chromestatus.com/feature/5637156160667648 */
211+
if (experimentalDecode && "decode" in image) {
212+
image
213+
// NOTE: .decode() is not in the TS defs yet
214+
//@ts-ignore
215+
.decode()
216+
.then(() => resolve())
217+
.catch((err: any) => reject(err));
218+
return;
219+
}
220+
189221
image.onload = resolve;
190222
image.onerror = reject;
191223
});

stories/LazyImage.story.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,29 @@ stories
206206
</Container>
207207
))
208208
)
209+
.add(
210+
"Experimental decode",
211+
withInfo(
212+
"Decode off-main-thread before appending. Only supported in some browsers, but uses normal API otherwise. Test before using!"
213+
)(() => (
214+
<Container>
215+
<LazyImage
216+
src="img/porto_buildings_large.jpg"
217+
alt="Buildings with tiled exteriors, lit by the sunset."
218+
placeholder={({ imageProps, ref }) => (
219+
<img
220+
ref={ref}
221+
src="img/porto_buildings_lowres.jpg"
222+
alt={imageProps.alt}
223+
className="w-100"
224+
/>
225+
)}
226+
actual={({ imageProps }) => <img {...imageProps} className="w-100" />}
227+
experimentalDecode={true}
228+
/>
229+
</Container>
230+
))
231+
)
209232
.add(
210233
"Background image",
211234
withInfo(

0 commit comments

Comments
 (0)