Skip to content

Feature: Debounced/delayed Loading #11

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

Merged
merged 26 commits into from
Aug 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 35 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- Full presentational control for the caller (render props).
- Modern, performant implementation, using [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) and providing [polyfill information](#polyfill-intersection-observer).
- [Eager loading / Server-side rendering support](#eager-loading--server-side-rendering-ssr).
- [Debounce / Delay](#debounce--delay); can wait for an image to be in the viewport for a set time, before loading.
- Works with horizontal scrolling, supports background images.
- [Fallbacks for SEO / when Javascript is disabled](#fallback-without-javascript).
- Easy to understand source code. You should be able to fork and do your thing if desired.
Expand Down Expand Up @@ -292,6 +293,25 @@ Think about the cases where it is beneficial to do this, and apply it with inten
Examples might be eager-loading hero images, preloading the first few elements in a list and so on.
[Some of these use cases are provided as examples](#examples).

### Debounce / Delay

In cases where you have a long list of images that the user might scroll through, then loading intermediate images can waste bandwidth and processing time.
This is undesired.
The way to handle it is with a **minimum duration** that the image has to stay within the viewport, before making the request.
This is specified using the `debounceDurationMs` prop:

```jsx
<LazyImage
src="/img/porto_buildings_large.jpg"
alt="Buildings with tiled exteriors, lit by the sunset."
debounceDurationMs={1000}
placeholder={({ imageProps, ref }) => (
<img ref={ref} src="/img/porto_buildings_lowres.jpg" alt={imageProps.alt} />
)}
actual={({ imageProps }) => <img {...imageProps} />}
/>
```

### Fallback without Javascript

If Javascript is disabled altogether by the user, then they will be stuck with the placeholder (and any images loaded eagerly).
Expand Down Expand Up @@ -410,19 +430,20 @@ The presentation can be derived from those plus, crucially, any specific needs y

**`<LazyImage />`** accepts the following props:

| Name | Type | Default | Required | Description |
| ---------------------- | ------------------------------------------------------------------------- | ----------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| **src** | String | | true | The source of the image to load |
| **alt** | String | | false | The alt text description of the image you are loading |
| **srcSet** | String | | false | If your images use srcset, you can pass the `srcSet` prop to provide that information for preloading. |
| **sizes** | String | | false | If your images use srcset, the sizes attribute helps the browser decide which source to load. |
| **actual** | Function (render callback) of type ({imageProps}) => React.ReactNode | | true | Component to display once image has loaded |
| **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 |
| **loading** | Function (render callback) of type () => React.ReactNode | placeholder | false | Component to display while the image is loading |
| **error** | Function (render callback) of type () => React.ReactNode | actual (broken image) | false | Component to display if the image loading has failed (render prop) |
| **loadEagerly** | Boolean | false | false | Whether to skip checking for viewport and always show the 'actual' component |
| **observerProps** | {threshold: number, rootMargin: string} | {threshold: 0.01, rootMargin: "50px 0px"} | false | Subset of props for the IntersectionObserver |
| **experimentalDecode** | Boolean | false | false | Decode the image off-main-thread using the Image Decode API. Test before using! |
| Name | Type | Default | Required | Description |
| ---------------------- | ------------------------------------------------------------------------- | ----------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **src** | String | | true | The source of the image to load |
| **alt** | String | | false | The alt text description of the image you are loading |
| **srcSet** | String | | false | If your images use srcset, you can pass the `srcSet` prop to provide that information for preloading. |
| **sizes** | String | | false | If your images use srcset, the sizes attribute helps the browser decide which source to load. |
| **actual** | Function (render callback) of type ({imageProps}) => React.ReactNode | | true | Component to display once image has loaded |
| **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 |
| **loading** | Function (render callback) of type () => React.ReactNode | placeholder | false | Component to display while the image is loading |
| **error** | Function (render callback) of type () => React.ReactNode | actual (broken image) | false | Component to display if the image loading has failed (render prop) |
| **debounceDurationMs** | Number | N/A | false | The minimum duration that the image has to be in the viewport before starting to load, in ms. This can help avoid loading images while the user scrolls quickly past them. |
| **loadEagerly** | Boolean | false | false | Whether to skip checking for viewport and always show the 'actual' component |
| **observerProps** | {threshold: number, rootMargin: string} | {threshold: 0.01, rootMargin: "50px 0px"} | false | Subset of props for the IntersectionObserver |
| **experimentalDecode** | Boolean | false | false | Decode the image off-main-thread using the Image Decode API. Test before using! |

**`<LazyImageFull />`** accepts the following props:

Expand All @@ -432,6 +453,7 @@ The presentation can be derived from those plus, crucially, any specific needs y
| **alt** | String | | false | The alt text description of the image you are loading |
| **srcSet** | String | | false | If your images use srcset, you can pass the `srcSet` prop to provide that information for preloading. |
| **sizes** | String | | false | If your images use srcset, the sizes attribute helps the browser decide which source to load. |
| **debounceDurationMs** | Number | N/A | false | The minimum duration that the image has to be in the viewport before starting to load, in ms. |
| **loadEagerly** | Boolean | false | false | Whether to skip checking for viewport and always show the 'actual' component |
| **observerProps** | {threshold: number, rootMargin: string} | {threshold: 0.01, rootMargin: "50px 0px"} | false | Subset of props for the IntersectionObserver |
| **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 |
Expand Down
4 changes: 4 additions & 0 deletions changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- "Buffering" state is exposed in LazyImageFull, but not in LazyImage. Does it make sense to even expose?
- Not exposing it would be non-breaking!
- Add "debounceDuration" or prop; decide on naming!
- Make optional, and 0 by default.
19 changes: 12 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-lazy-images",
"description": "React utilities for lazy image loading",
"version": "1.0.1",
"version": "1.1.0-rc.3",
"source": "src/index.tsx",
"module": "dist/react-lazy-images.es.js",
"main": "dist/react-lazy-images.js",
Expand All @@ -10,9 +10,11 @@
"scripts": {
"dev": "npm-run-all bundle:prod storybook",
"dev:ts": "tsc --watch --pretty",
"build": "npm-run-all bundle:prod size",
"clean": "rimraf dist/",
"build": "npm-run-all clean bundle:prod copy-defs size",
"bundle:prod": "microbundle build",
"bundle:watch": "microbundle watch",
"copy-defs": "tsc && cp -r ts-build/*.d.ts dist/",
"storybook": "start-storybook -p 8080 -s ./stories/demo",
"storybook:build": "npm run bundle:prod && build-storybook -c .storybook -s ./stories/demo -o .out",
"storybook:deploy": "storybook-to-ghpages --existing-output-dir=.out",
Expand Down Expand Up @@ -63,7 +65,8 @@
},
"homepage": "https://github.com/fpapado/react-lazy-images#readme",
"dependencies": {
"react-intersection-observer": "^6.1.0"
"react-intersection-observer": "^6.1.0",
"unionize": "^2.1.2"
},
"peerDependencies": {
"react": "^15 || ^16",
Expand All @@ -88,7 +91,7 @@
"eslint-plugin-jsx-a11y": "^6.1.1",
"husky": "^1.0.0-rc.13",
"lint-staged": "^7.2.0",
"microbundle": "^0.5.1",
"microbundle": "0.5.1",
"npm-run-all": "^4.1.3",
"prettier": "^1.13.7",
"react": "^16.4.1",
Expand All @@ -97,7 +100,7 @@
"react-docgen-typescript-webpack-plugin": "^1.1.0",
"react-dom": "^16.4.1",
"tachyons": "^4.10.0",
"typescript": "^2.9.2",
"typescript-eslint-parser": "^16.0.1"
"typescript": "^3.0.1",
"typescript-eslint-parser": "^17.0.1"
}
}
Loading