|
1 |
| -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). |
| 1 | +# react-suspense-image |
2 | 2 |
|
3 |
| -## Available Scripts |
| 3 | +Apply React suspense for loading image |
4 | 4 |
|
5 |
| -In the project directory, you can run: |
| 5 | +--- |
6 | 6 |
|
7 |
| -### `yarn start` |
| 7 | +<blockquote> |
| 8 | +When the user is under a slow network or the image size is large, the image will paint step by step which makes the user feel even slower. A solution for that is to display a placeholder and replace it after the image was loaded. In this article, I will demonstrate how to achieve it by using react suspense. |
| 9 | +</blockquote> |
8 | 10 |
|
9 |
| -Runs the app in the development mode.<br /> |
10 |
| -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. |
| 11 | +## Agenda |
11 | 12 |
|
12 |
| -The page will reload if you make edits.<br /> |
13 |
| -You will also see any lint errors in the console. |
| 13 | +- [Generate Image Placeholder](#agenda-1) |
| 14 | +- [React-Cache](#agenda-2) |
| 15 | +- [React-Suspense](#agenda-3) |
| 16 | +- [SrcSet](#agenda-4) |
14 | 17 |
|
15 |
| -### `yarn test` |
| 18 | +### Generate Image Placeholder <a name="agenda-1"></a> |
16 | 19 |
|
17 |
| -Launches the test runner in the interactive watch mode.<br /> |
18 |
| -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. |
| 20 | +Because we want to display the image when it's fully loaded. We still need to display something during the image loading process. |
| 21 | +A solution is to display the same image with a smaller size. But we will have to generate a smaller version for all our images. This might not be the best solution in some scenarios. |
| 22 | +Another solution I want to show you is to generate a placeholder. Here I generate an SVG base on the size and color we need and encode to Base64. Then we can use it as a placeholder before the image is loaded. |
19 | 23 |
|
20 |
| -### `yarn build` |
| 24 | +```js |
| 25 | +const cache = {}; |
| 26 | +const generatePlaceholder = (ratio, color) => { |
| 27 | + const width = 1; |
| 28 | + const height = ratio; |
| 29 | + const key = `${ratio},${color}`; |
21 | 30 |
|
22 |
| -Builds the app for production to the `build` folder.<br /> |
23 |
| -It correctly bundles React in production mode and optimizes the build for the best performance. |
| 31 | + if (!cache[key]) { |
| 32 | + cache[key] = `data:image/svg+xml;base64, ${window.btoa( |
| 33 | + `<svg height="${height}" width="${width}" xmlns="http://www.w3.org/2000/svg"> |
| 34 | + <rect x="0" y="0" width="${width}" height="${height}" fill="${color}"/> |
| 35 | + </svg>` |
| 36 | + )}`; |
| 37 | + } |
24 | 38 |
|
25 |
| -The build is minified and the filenames include the hashes.<br /> |
26 |
| -Your app is ready to be deployed! |
| 39 | + return cache[key]; |
| 40 | +}; |
| 41 | +``` |
27 | 42 |
|
28 |
| -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. |
| 43 | +### React-Cache <a name="agenda-2"></a> |
29 | 44 |
|
30 |
| -### `yarn eject` |
| 45 | +To use react suspense for the image loading, we will need to apply react cache to create a resource and resolve when image is loaded. |
31 | 46 |
|
32 |
| -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** |
| 47 | +```js |
| 48 | +import { unstable_createResource } from "react-cache"; |
33 | 49 |
|
34 |
| -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. |
| 50 | +const ImageResource = unstable_createResource( |
| 51 | + src => |
| 52 | + new Promise(resolve => { |
| 53 | + const img = new Image(); |
| 54 | + img.src = src; |
| 55 | + img.onload = resolve; |
| 56 | + }) |
| 57 | +); |
| 58 | +``` |
35 | 59 |
|
36 |
| -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. |
| 60 | +If we use this in our application, we will see an error: |
| 61 | + `Cannot ready property 'readContext' of undefined` |
37 | 62 |
|
38 |
| -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. |
| 63 | +The reason is that the API of React-cache is not ready and it's unstable at the moment. |
| 64 | +So we need to add a patch to fix this issue. |
| 65 | +Here I use [patch-package](https://www.npmjs.com/package/patch-package) to handle this problem. |
39 | 66 |
|
40 |
| -## Learn More |
| 67 | +1. install package |
41 | 68 |
|
42 |
| -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). |
| 69 | +```shell |
| 70 | +yarn add patch-package postinstall-postinstall |
| 71 | +``` |
43 | 72 |
|
44 |
| -To learn React, check out the [React documentation](https://reactjs.org/). |
| 73 | +2. add postinstall script at package.json |
45 | 74 |
|
46 |
| -### Code Splitting |
| 75 | +```json |
| 76 | +"postinstall": "patch-package" |
| 77 | +``` |
47 | 78 |
|
48 |
| -This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting |
| 79 | +3. modify the code base on the [comment](https://github.com/facebook/react/issues/14575#issuecomment-455096301) |
| 80 | +4. generate patch |
49 | 81 |
|
50 |
| -### Analyzing the Bundle Size |
| 82 | +```shell |
| 83 | +yarn patch-package react-cache |
| 84 | +``` |
51 | 85 |
|
52 |
| -This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size |
| 86 | +### React-Suspense <a name="agenda-3"></a> |
53 | 87 |
|
54 |
| -### Making a Progressive Web App |
| 88 | +Now we can apply React suspense to create a lazy load image. |
| 89 | +Here we put our image src into the ImageResource which created through React-cache and use the placeholder as a fallback in React suspense. |
| 90 | +Before the image loaded, the suspense will display the fallback. |
| 91 | +After the image loaded and resolve the resource, the placeholder will be replaced by the original image. |
55 | 92 |
|
56 |
| -This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app |
| 93 | +```js |
| 94 | +import React, { Suspense } from "react"; |
57 | 95 |
|
58 |
| -### Advanced Configuration |
| 96 | +const OriImg = ({ src, alt }) => { |
| 97 | + ImageResource.read(src); |
59 | 98 |
|
60 |
| -This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration |
| 99 | + return <img src={src} alt={alt} />; |
| 100 | +}; |
61 | 101 |
|
62 |
| -### Deployment |
| 102 | +const LazyLoadImg = ({ src, alt, ratio }) => { |
| 103 | + const placeholder = generatePlaceholder(ratio, "black"); |
63 | 104 |
|
64 |
| -This section has moved here: https://facebook.github.io/create-react-app/docs/deployment |
| 105 | + return ( |
| 106 | + <Suspense fallback={<img src={placeholder} alt={alt} />}> |
| 107 | + <OriImg src={src} alt={alt} /> |
| 108 | + </Suspense> |
| 109 | + ); |
| 110 | +}; |
| 111 | +``` |
65 | 112 |
|
66 |
| -### `yarn build` fails to minify |
| 113 | +The result will look like this. And here is the repository for reference - [react-image-suspense](https://github.com/oahehc/react-image-suspense). |
67 | 114 |
|
68 |
| -This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify |
| 115 | + |
| 116 | + |
| 117 | +### SrcSet <a name="agenda-4"></a> |
| 118 | + |
| 119 | +It's worth mentioning that although display a placeholder while the image is loading can increase the user experience. But it won't make the image load faster. Therefore, to provide a proper size of the image is very important. |
| 120 | + |
| 121 | +If we want to display different sizes of the image on our web application base on the screen size. We can use `srcset` attribute on the img tag. |
| 122 | + |
| 123 | +```html |
| 124 | +<img sizes="(min-width: 40em) 80vw, 100vw" srcset=" ... " alt="…" /> |
| 125 | +``` |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +## Reference |
| 130 | + |
| 131 | +- [Creating a modern image gallery with React Suspense](https://medium.com/@andrisgauracs/creating-a-modern-image-gallery-with-react-suspense-4e2c1b5a19b7) |
| 132 | +- [Cannot ready property 'readContext' of undefined](https://github.com/facebook/react/issues/14575) |
| 133 | +- [patch-package](https://www.npmjs.com/package/patch-package) |
| 134 | +- [Responsive Images in CSS](https://css-tricks.com/responsive-images-css/) |
0 commit comments