Abstract
In this mini-series consisting of two posts I will build a React Component Image which, using custom hooks, shows a low-resolution image that is immediately replaced when the high-resolution counterpart is completely downloaded. In the second phase, I will take care of postponing the download of the second only when the component becomes visible
Table of content
- Low-resolution & High Resolution
- High-resolution only when is visible
Low-resolution & High-resolution
Concept
The rendering of a high-resolution image can take - especially for slow connections - several seconds. This lack of readiness results in worse UX
In this post, I deal with solving the problem by building a component that in addition to the high-resolution image source receives one for the low-resolution image to be shown as a replacement until the first is fully downloaded and available
In the next post, I will take care of postponing the download of the high-resolution image only when the component becomes visible within the view. Regardless, the user will not see a missing image as the relative low resolution will already be present
Process
In a project generated via create-react-app I delete all that is superfluous
Then I initialize the construction of the Image component
mkdir src/components
touch src/components/Image.jsx
It is actually two <img> placed one above the other and made visible alternately. To make them superimposable it is sufficient to use a wrapper with the necessary CSS properties. Furthermore, since the two images may have different sizes, it is recommended that while a wrapper defines width and height, the images contained therein adapt to its directives
Image.js
const Image = ({ width = '100%', height = '100%', lowResSrc, highResSrc }) => {
  const styles = {
    wrapper: {
      position: 'relative',
      width,
      height,
    },
    image: {
      position: 'absolute',
      width: '100%',
      height: '100%',
    },
  }
  return (
    <div style={styles.wrapper}>
      <img src={lowResSrc} style={styles.image} />
      <img src={highResSrc} style={styles.image} />
    </div>
  )
}
export default Image
Inline CSS is used rather than another solution for simplicity's sake
Now I use the component and I provide it with the required props
App.js (but it could be anywhere)
const srcTuple = [
  'https://via.placeholder.com/150',
  'https://via.placeholder.com/600',
]
...
<Image
  width={300}
  height={300}
  lowResSrc={srcTuple[0]}
  highResSrc={srcTuple[1]}
/>
At this point on the screen, there is the image related to srcTuple[0] (the low-resolution source) because that is what the style wants. For the replacement to occur, it is necessary to be able to intervene when the download of the high-resolution image is completed
To do this I can use the onLoad method of the<img> attribute. The explanatory name indicates when it is performed
The question remains of what to actually make it perform
With a view to modern React, I decided to opt for a custom hook
It must keep track of the state of the image loading and on the basis of it return a style that leads to a pleasant transition between the two images of the component. To do this it must expose a method that will be associated with the onLoad method
mkdir src/hooks
touch src/hooks/useImageOnLoad.js
useImageOnLoad.js
import { useState } from 'react'
const useImageOnLoad = () => {
  const [isLoaded, setIsLoaded] = useState(false)
  const handleImageOnLoad = () => setIsLoaded(true)
  const transitionStyles = {
    lowRes: {
      opacity: isLoaded ? 0 : 1,
      filter: 'blur(2px)',
      transition: 'opacity 500ms ease-out 50ms',
    },
    highRes: {
      opacity: isLoaded ? 1 : 0,
      transition: 'opacity 500ms ease-in 50ms',
    },
  }
  return { handleImageOnLoad, transitionStyles }
}
export default useImageOnLoad
So, just integrate the hook into the component. The method is associated with the onLoad on the high resolution <img>tag. The styles returned by the hook must be associated with its <img> tags
Image.js (snellito)
const Image = ({ ... }) => {
  const { handleImageOnLoad, transitionStyles } = useImageOnLoad()
  const styles = {...}
  const lowResStyle = { ...styles.image, ...transitionStyles.lowRes }
  const hightResStyle = { ...styles.image, ...transitionStyles.highRes }
  return (
    <div style={styles.wrapper}>
      <img src={lowResSrc} style={lowResStyle} />
      <img src={highResSrc} style={hightResStyle} onLoad={handleImageOnLoad} />
    </div>
  )
}
export default Image
Considerations
Given the very little use of the network in this demo, to make the effect more appreciable it can be convenient
- multiply the number of <Image />components and their contents
- simulate throttling in the Network tab of the Developer Tools
- disable cache
Finally, it is true that compared to a simple <img /> with a single source, <Image /> requires a few more bytes to be downloaded (AKA the low-resolution image). However, it's a small price to pay for a better UX, it's so true?
Thanks for reading, continue to the next post ๐จ
 
![Cover image for Lazy Loading Image - [1/2]](https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4npyf4bydgub1jfe79d2.png) 
              


 
    
Top comments (0)