Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: fpapado/react-lazy-images
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1.0.0
Choose a base ref
...
head repository: fpapado/react-lazy-images
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 6 commits
  • 14 files changed
  • 5 contributors

Commits on Jul 24, 2018

  1. 1.0.1

    fpapado committed Jul 24, 2018
    Copy the full SHA
    e98a143 View commit details

Commits on Aug 5, 2018

  1. Feature: Debounced/delayed Loading (#11)

    * Add utils and delayed loading test in stories
    
    * Intermediate solution
    
    Not sure where the reducer/update function is headed, but we'll
    find out, I suppose.
    
    * Finish refactoring
    
    * Make buffering work
    
    It works, but now the intersection logic is not the same. Scrolling
    until inView is true, and then up, triggers the promise cancelation.
    This is undesirable. We want the image to keep loading as long as
    it stays on the screen. Oddly, scrolling past the image, and it
    being in view, still lets it load without canceling (as intendeed).
    Another difference is that loading only commences once the image is
    fully in view, rather than on the margin.
    
    * Document things a bit better
    
    * There was no bug; the placeholder had a 0 height!
    
    * A few more docs
    
    * Name things, expose as props
    
    Added:
    - debugActions: logs actions in dev, warns and logs in prod
    - debounceDurationMs: whether, and how much to debounce loading,
      0 by default, so no change to v1. Added codepath to make it so.
    - Provisional changes.md document with API compatibility
    - Various TODOs and cleanup tasks
    
    * Fix passing of debounceDurationMs to cb
    
    * Use debugActions in the story, destructure it
    
    Also add a note to hopefully remember to destructure next time :)
    
    * Pin microbundle and ts versions
    
    * Clean dir before build
    
    * TS BUG: need declaration: false to build correctly
    
    If we set declaration: true, then typescript (specifically rpt2,
    Rollup's TS plugin) fails with a cryptic path error. This is
    filed under microsoft/TypeScript#25047.
    
    Curiously, this does not happen on master, and I have vaguely
    traced it back to unionize and the fact that it does fancy type
    exports, which we partially re-export here. We would have to
    manually emit a declaration file, which is not hard tbh, but a bit
    annoying. Even funnier, webpack has no issue building in storybook,
    but that might be because it does not emit declarations anyway.
    
    Steps:
    - Get declaration file from master, add to repository, and copy to
    dist/ upon build;
    - See if massaging our exports fixes it;
    - Wait for TS :/
    
    * 1.1.0-rc.1
    
    * Version bump
    
    * Make update static, use Commands
    
    The Elm/JS/Redux/React-Component fusion is complete. The reducer
    is now a pure function, because it only describes side-effects,
    and does not execute them. The logic is really nice to read there.
    
    * Naming things
    
    * Version bump
    
    * Use TS3.0
    
    * Copy definitions separately
    
    Because of the "path undefined" bug, we have to run `tsc`
    separately, to get the definitions and copy them over.
    
    * Do not expose Buffering as a separate state
    
    Expose it under 'Loading' for backwards compatibility
    
    * Improve debounceDurationMs docs
    
    * Add first point to README
    
    * Cancel Promises, clear cache on unmount
    
    * Add docs for debounceDurationMs
    fpapado authored Aug 5, 2018
    Copy the full SHA
    a68ffac View commit details
  2. 1.1.0

    fpapado committed Aug 5, 2018
    Copy the full SHA
    65a4462 View commit details

Commits on Sep 25, 2018

  1. Fix polyfill.io link (#16)

    csabapalfi authored and fpapado committed Sep 25, 2018
    Copy the full SHA
    a1c2235 View commit details

Commits on Apr 23, 2019

  1. Expose margin and threshold in Readme (#21)

    * expose margin and threshold
    
    * update readme
    
    * remove unnecessarily exposed props
    
    * remove unnecessary curly braces
    SeanDemps authored and fpapado committed Apr 23, 2019
    Copy the full SHA
    2ec1f3e View commit details

Commits on Oct 26, 2022

  1. Updates react support to v18 (#47)

    Updates various dependencies to support React 18. Also updates to latest version of react-intersection-observer.
    
    This resolves #45 & #46
    radfahrer authored Oct 26, 2022
    Copy the full SHA
    946e3d5 View commit details
Showing with 6,808 additions and 4,258 deletions.
  1. +1 −1 .eslintrc
  2. +62 −14 README.md
  3. +4 −0 changes.md
  4. +6,213 −4,072 package-lock.json
  5. +19 −15 package.json
  6. +2 −2 src/LazyImage.tsx
  7. +284 −68 src/LazyImageFull.tsx
  8. +0 −65 src/react-intersection-observer.d.ts
  9. +50 −1 stories/LazyImage.story.tsx
  10. +54 −0 stories/OpinionatedComponents.tsx
  11. +92 −0 stories/styles.css
  12. +4 −0 stories/utils.tsx
  13. +3 −20 tsconfig.json
  14. +20 −0 tsconfig_base.json
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"parser": "typescript-eslint-parser",
"parser": "@typescript-eslint/parser",
"plugins": [
"jsx-a11y"
],
76 changes: 62 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
@@ -208,6 +209,32 @@ Any of the presentational patterns presented that are possible with `LazyImage`

In fact, if you check [`src/LazyImage.tsx`](./src/LazyImage.tsx), you will see that `LazyImage` is implemented in terms of `LazyImageFull`!

### Load ahead and threshold

Further control over the Intersection Observer can be provided through the `observerProps` prop object:

```jsx
import { LazyImage } from "react-lazy-images";

<LazyImage
src="/img/porto_buildings_large.jpg"
alt="Buildings with tiled exteriors, lit by the sunset."
placeholder={/* the usual */}
actual={/* the usual */}
observerProps={{
rootMargin: "100px 0",
threshold: 0.3
}}
/>;
```

`rootMargin`: Margin around the window. This can have values similar to the CSS margin property, e.g. `"10px 20px 30px 40px"` (top, right, bottom, left) (defaulted to `"50px 0px"`)
This can provide control if you want to request your image a certain number of pixels ahead of where the user is scrolling.

`threshold`: Number between 0 and 1 indicating the percentage that should be visible before a request is sent. (defaulted to `0.01`)

(See https://github.com/thebuilder/react-intersection-observer#api)

### Load before swap

A common optimisation to the loading strategy is to preload the image before swapping it for the placeholder.
@@ -292,6 +319,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).
@@ -375,7 +421,7 @@ And import it at your app's entry point:
import "intersection-observer";
```
[Polyfill.io is an alternative method of distributing the polyfill](polyfill.io) if you wish.
[Polyfill.io is an alternative method of distributing the polyfill](https://polyfill.io) if you wish.
#### About the polyfill
@@ -410,19 +456,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:
@@ -432,6 +479,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 |
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.
Loading