Introduction
Most times on my initial visit to a website, I take my time to observe and enjoy how images are rendered. In fact, if the website happens to include an image on their hero section, that is what first catches my attention before I go ahead to read the texts on the page.
Visual appealing images aids good User Experience (UX) while browsing a web or mobile application. It also play a crucial role in beautifying the User Interface (UI). In some cases, images speaks louder than the texts associated with it.
Rendering images poorly in your application could harm your UI, thereby causing bad UX. In this article, I will walk you through "rendering images the good way in your react application".
Prerequisites
This article is for everyone who loves building and beautifying web and mobile applications, and those who use them. However, having basic understanding of the below tools and concepts in web development is a plus as we would write some code to better explain some concepts.
- Basic knowledge of Frontend Development (HTML/CSS/JS)
- Basic Knowledge of React
What does Image Rendering mean?
Image Rendering is the process of displaying images on the UI of an application for the users to see and interact with them. When you visit an application and an image gets loaded on the screen, that process of the image loading is what is referred to as Image Rendering.
Why should we care about how images are rendered?
There are more to Image Rendering than just loading images on the User Interface (UI). If images are not properly rendered, they tend to make a bad UI and even harm User Experience (UX). This is why proper image rendering is considered a critical skill every Frontend Developer should possess.
How to render images the good way in a react app?
The basic rendering of images in most Frontend Web Development languages, libraries and frameworks follow a simply convention where the HTML img
element is used. The img
tag primarily consists of two major standard attributes; src
and alt
attributes. The src
attribute takes a string of the url or the relative path of the image to be rendered, while the alt
attribute takes a string containing a description of the image to be rendered. The description provided in the alt
attribute is what would be rendered if the image is broken or failed to render. The alt
attribute also aids accessibility (a11y) by describing to screen readers what the image is. Other img
tag standard attributes include srcset
, sizes
, loading
, crossorigin
, usemap
, etc.
React renders HTML elements using JSX. The JSX translates to HTML under the hood.
In order to render images the good way in a react application, below are the tips and tricks that would magnify how they are rendered by react:
1. Emphases on the alt attribute
The alt
attribute is very important and should never be omitted when rendering images with the img
tag. The alt
attribute does not only display the provided description when the image failed to load, but also play a crucial role in aiding a11y by providing screen readers contents to read out to the user about the image. I have once debugged someone's code and discovered that all img
tags in the entire code base does not have alt
attribute, I had to include it and provide good description for each each image. Not including the alt
attribute is a bad practice and should be avoided at all times.
Do: <img src="imageURL/relativePath" alt="a good image description" />
Don't: <img src="imageURL/relativePath" />
2. Best image format and file size
Generally, the image file format and size matters a lot. They determine the clarity and the speed at which the image is loaded.
The smaller the image size, the faster it would render.
It is highly recommended that the file format for icons and small graphics should always be SVG as they are usually smaller in size and scalable.
<img src="iconUrl.svg" alt="a good icon description" />
The WebP format is one of the best formats to save and render images of high quality and size. It is a modern format that provides great compression and high quality when compared to old formats like JPEG and PNG.
<img src="imageUrl.webp" alt="a good image description" />
The image above shows an image format conversion from JPG to WebP using Squoosh.app. In this conversion, the 2.79MB JPG image (see bottom-left corner of the image) was converted to a 705kB WebP format, saving 75% of the file size while still maintaining the image quality (see bottom-right). You can further adjust the quality and compare the difference by dragging the divider left-right to get smaller size. Squoosh.app is a great tool for image conversion that does not upload your image elsewhere as you are basically doing all your conversion offline and with your computer, and it has a download button that you can click to simply save your file after conversion. The Squoosh.app also converts to other image formats which you can explore.
Other image formats (e.g, JPEG and PNG) are still valid and can be used if the image size is small, but should be used with caution. Since WebP saves us a lot image size while still maintaining high quality, why not just use it all the time in your application? Its support across browsers as at the date of the publish of this article is 96.9%, which means it has good support across all major browsers.
3. Proper image box sizing
Providing a proper size for the image to be rendered should be practiced. This backs the certainty of the image fitting the available space for it across screens as desired. The intrinsic sizing being discussed in this case include width
, height
and aspect-ratio
.
<img src="imageURL/relativePath" alt="a good image description" width="200px" height="300px" />
The image sizing, just like every other HTML element, could be styled inline, internally or with an external css file.
<img src="imageURL/relativePath" alt="a good image description" />
img {
width: 200px;
height: 300px;
}
If the image is wrapped with a container, e.g div
or span
, and the desire is to make it cover the container, in most cases the container would take the size of the img
tag. But if the container has a specified width and height, the img
tag could be given a relative value (e.g width="100%"
), although this should be applied with careful examination as the aspect-ratio
of the image may affect the styling and should be adjusted accordingly.
<div width="200px" height="300px">
<img src="imageURL/relativePath" alt="a good image description" width="100%" height="100%" />
</div>
In order for aspect-ratio
to take effect, at least one of the image sizes must be auto
.
<img src="imageUrl/relativePath" alt="a good image description" width="200px" height="auto" aspect-ratio="1/1" />
Sizing the image is one of the basic good practice that aids good User Interface (UI). When an image is properly sized, you are certain as a Frontend Developer that its shape across screens gives a pleasing view to the user.
4. Lazy loading
Diving deeper into "rendering images the good way", one of the critical aspect to be considered is how the image is rendered. One of the key ways to render an image wholly is to lazy-load it. This ensures that the image is in the viewport and has fully been loaded before displaying it on the UI. To achieve this, you can use the img
tag loading attribute where you set the value to "lazy", or you can use other small-size React image lazy-loading libraries that may include extra loading features.
- The loading attribute
The loading attribute gives us the ability to control how the image is rendered. It helps to command the image to either load immediately the UI is loaded regardless of whether the image is in the viewport (loading="eager"
), or load only when the image reaches a certain viewport (loading="lazy"
). This means that the loading
attribute can take values, eager and lazy. By default, images are rendered eagerly (loading="eager"
).
<img src="imageURL/relativePath" alt="a good image description" loading="eager" />
<img src="imageURL/relativePath" alt="a good image description" loading="lazy" />
Lazy-loading images generally improves image rendering. It ensures that the image only loads when needed, and in most cases, it forces the image to only display when it is fully ready, thereby preventing the image from rendering part-by-part on slow or poor network.
- React lazy-loading libraries
Using a third-party library to lazy-load images is also an option. One may prefer to use a library which simply involves installing and importing the library, and then using it to control the img
tag in your application as the library's documentation suggests. Although in most cases, using the loading
attribute works just fine rather than importing a third-party library if you are rendering a few simple images with less configurations. Using a third-party library was preferred due to its extra features and poor browser support for the loading
attribute in the past, but as at the time of the publish of this article, the img
tag loading
attribute is 95.73% supported across all major browsers, which means it's safe to simple use the loading
attribute to lazy-load images.
Further more on the third-party library option, there are two React lazy-loading libraries I recommend for use; they are react-lazyload and react-lazy-load-image-component. Both libraries supports lazy-loading images and components.
- react-lazyload:
Installation:
pnpm install --save react-lazyload
Basic Usage
import LazyLoad from 'react-lazyload';
function App() {
return (
<>
<h1>Lazy loaded image</h1>
<LazyLoad height="100%" placeholder={ <p>Loading...</p> }>
<img
src="imageURL/relativePath"
alt="a good image description"
width="200px"
height="300px"
/>
</LazyLoad>
</>
)
}
export default App;
The react-lazyload library wraps the img
tag with it's LazyLoad
component just as shown in the code block above. It can only wrap one child img
tag at a time. This library performs well with speed at which it renders images. Its simplicity and features makes it unique. Some of the props the LazyLoad
component can take include placeholder, once, height, unmountIfInvisible, debounce/throttle, overflow, etc.
- react-lazy-load-image-component library:
Installation:
pnpm i --save react-lazy-load-image-component
Basic Usage
import { LazyLoadImage } from 'react-lazy-load-image-component';
function App() {
return (
<>
<h1>Lazy loaded image</h1>
<LazyLoadImage
src="imageURL/relativePath"
placeholder={ <p>Loading...</p> }
alt="a good image description"
height="400px"
width="600px"
/>
</>
)
}
export default App;
The features of this library makes it easier to handle the loading state of the image. Unlike react-lazyload library which wraps the img
tag with an opening and closing tag (<LazyLoad><img src=ββ alt=ββ /></LazyLoad>
), this library makes it seem as though you are still using the img
tag, you simply replace the img
with LazyLoadImage
and all the attributes of img
tag can be passed to the LazyLoadImage
component as props. React-lazy-load-image-component library performs great when the images to be rendered are many, it ensures that only images that would fit the viewport are fetched, the more you scroll, the more it fetches, saving the amount of network requests that would have been made if all images were to be fetched at once. The props that could be passed to this component aside all img
tag attributes include placeholderSrc, threshold, effect, useIntersectionObserver, debounce/throttle, onLoad, beforeLoad, afterLoad, placeholder, etc.
5. Responsive Images
Generally, responsiveness is a very important aspect of Frontend Development that ensures that all UI components and elements of an application are displayed in correct sizes across devices. The concept of displaying the correct size of an element with respect to the device's screen size can be applied to the img
tag with the srcset
attribute for image rendering, where different sizes are passed to the sizes
attribute with respect to the number of image URL passed to the srcset
attribute.
<img
src="imageURL-small"
srcSet="imageURL-small 500w, imageURL-medium 1000w, imageURL-large 1500w"
sizes="(max-width: 600px) 480px, (max-width: 1200px) 800px, 1200px"
alt="a good image description"
/>
The above code block shows a basic application of responsive image where different image URLs were provided in the srcset
attribute, and their respective sizes were provided and separated with commas (,) in the sizes
attribute. This ensures that the correct size of the device screen size is loaded, which improves rendering speed with respect to the image file size. A default URL is passed to the src
attribute.
6. Progressive Image Loading
The concept of progressive image loading involves rendering a low-quality image placeholders (LQIP) or a blurred version of the original image to be displayed while it is loading. A typical example of this type of image rendering is seem on unsplash.com and pexels.com.
I personally recommend rending images progressively as it gives your users/visitors a picture of what is expected to be loaded. This improves both UI and UX because it initially occupy a space for the loading image which prevents Cumulative Layout Shift (CLS), and also provide the user a hint of what is loading.
Implementation of Progressive Image Loading with BlurHash in React
BlurHash is a compact representation of a placeholder for an image.
Installation:
pnpm install --save blurhash react-blurhash
Basic Structure of React Blurhash component
<Blurhash
hash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
width={350}
height={323}
resolutionX={32}
resolutionY={32}
punch={1}
/>
The React Blurhash components takes hash
, width
, height
, resolutionX
, resolutionY
, and punch
as props.
The hash
prop's value is a string of the encoded hash of the original image to be rendered. The width
and height
props can be a valid CSS string or number value for width and height of the Blurhash image. The resolutionX
, resolutionY
controls the horizontal axis and vertical axis resolution of the blurred image, while the punch
takes care of contrast of the blurred image.
To generate a BlurHash string, we would simply use the generator on the BlurHash website (blurha.sh) for this article. On blurha.sh, scroll down to find the image upload button, upload the image you need the BlurHash string for and copy the generated hash.
Basic Usage
import { useState, useEffect } from "react";
import { Blurhash } from "react-blurhash";
const [imageLoaded, setImageLoaded] = useState(false);
const [imageError, setImageError] = useState(false);
const [imageSrc, setImageSrc] = useState(null);
useEffect(() => {
const img = new Image();
img.onload = () => {
setImageSrc(img.src);
setImageLoaded(true);
};
img.onerror = () => {
setImageError(true);
};
img.src = "imageUrl/relativePath";
}, []);
function App() {
return (
<div>
{!imageLoaded && !imageError && (
<div>
<Blurhash
hash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
width="100%"
height="100%"
resolutionX={32}
resolutionY={32}
punch={1}
/>
</div>
)}
{imageError && <div>Error loading image</div>}
{imageSrc && <img src={imageSrc} alt="a good image description" loading="lazy" />}
</div>
)
}
export default App;
In the above use case of the React BlurHash component, I manually handled the loading state and error state of the image without using a React library. The code simply uses useEffect()
to watch updates of the state of the rendering image. When the loading is true
, the BlurHash component is shown, but when the image is fully loaded, the original image is shown to replace the the BlurHash component, and on error, the error state becomes true
and an error message is displayed instead (in our case 'Error loading image').
You may be asking why all these lines of code just to display an image?. The desire to better handle image rendering in React got us here. Although we can use a React library to save our time and reduce the lines of code.
Using react-lazy-load-image-component library with BlurHash
The react-lazy-load-image-component performs well with BlurHash when you simply pass the Bluhash component to its placeholder
prop.
import { LazyLoadImage } from 'react-lazy-load-image-component';
import { Blurhash } from 'react-blurhash'
function App() {
return (
<>
<h1>Lazy loaded image</h1>
<LazyLoadImage
src="imageUrl/relativePath"
placeholder={
<Blurhash
hash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
height={400}
width={600}
resolutionX={32}
resolutionY={32}
punch={1}
/>
}
alt="a good image description"
height="400px"
width="600px"
/>
</>
)
}
export default App;
The above code block greatly reduced lines of code compared to when the image state was controlled manually. You can also decide to observe the loading state of the LazyLoadImage
component with the onLoad
prop and update the state when the image is loaded (e.g, const [loading, setLoading] = useState(true)
) rather than using the placeholder
prop. The onLoad
prop can be assigned a function that updates the loading state (e.g, () => setLoading(false)
), which then gives the ability to conditionally display the Blurhash
component based on the loading state of the image. Although the option of using the onLoad
prop to manually manage loading state can cause Cumulative Layout Shift (CLS) if the wrapper div
or span
containing the Blurhash
component and the LazyLoadImage
component is not properly styled.
7. Other Ways Of Rendering Images Efficiently
There are lots of good ways of rendering images. This means we have options as developers. These options include, but are not limited to Preloading Key Images, Using Image CDN, Using Background Images for Decorative Images, and Exploring Other Methods Of Progressive Image Rendering.
Conclusion
Effectively rendering images in your application is crucial for optimizing performance and greatly improving User Experience (UX). Also, this great act makes your User Interface (UI) more charming and lovely to visit regularly.
"When images are properly rendered on the UI of your application, kindly note that the joy I have in heart as your dearest user and frequent visitor is beyond imagination".
Always enable code analysis tools like ESLint in your coding environment to alert you when you make mistakes like forgetting to include the almighty important alt
attribute in your img
tag.
As a Frontend Developer or a UI Engineer, make it a priority to render images following best practices that would greatly improve how fast they are rendered by firstly considering the sizes and formats of your images before using them in your application.
Thank you for reading through to this part of the article!
Let's keep following best practices while coding! π
Resources
1. React lazy-loading libriries
2. Example websites that uses Progressive Image Loading
3. BlurHash
4. Image format converter
Top comments (14)
π₯ Saving this because I'll reference this when implementing lazy loading for images. This is good!πͺ
Yes! Thank you, Favour. Iβm glad it made sense.
Letβs keep coding.π
I will definitely reference this article when working on anything that requires images or image rendering, this is a really good read.
Sure, you should. π
I am happy it was worth reading. π
Thank you, Chidinma.
Thank you for this man, you did great here π
Iβm glad itβs resourceful! π€
Letβs keep coding. π
Insightful π₯π―
I'm glad it is! π π
Awesome π. You can check the next/Image component or the GatsbyImplementation of Image component.
Thank you! π. I have seen you play around
Next/Image
component in one of your NextJS YouTube livestreams. I have also played around with it and it's truly mind-blowing. I would check out the Gatsby Implementation as well.I was focused on pure React in the article, but I should have included React Frameworks' implementation somewhere in the article because they are truly worthy mentions!
I appreciate your insight once again! π
Oh my πβ¦.definitely putting this to practice!!
Yes! π
My bossπ you dey always deliver.
Thanks brotherly π
Iβm glad I did here π
Letβs keep coding!