Rendering Images in React Native Faster with imgix
The built-in React Native <Image />
component is feature-rich and provides many ways to improve page-load times. But keeping up with the best practices for responsive images requires spending lots of engineering hours making things just right.
Integrating imgix into your React Native workflow allows you to take advantage of the <Image />
component’s features while spending little time doing it.
What we’ll cover
Let’s run through an example of how we can leverage imgix to speed up our app. At the end we’ll have an image component that can:
- Resize an image automatically to fit the device dimensions
- Update image resolution automatically to match the device DPR
- “Blur-up” from an low-quality image placeholder (LQIP) to better serve low bandwidth users
You can follow along with each step using the commit history at the example repository. Or you can view the final product in the embedded React Native app below.
Try toggling the Expo preview to see how the image loads at each step.
Rendering an imgix image with the <Image />
component
To integrate imgix into our application, we first need to create an account and upload our first image. Once we’re set up, we can render our imgix image using the React Native (RN) <Image />
component.
We’ll start by setting our image URL as the source
URI on the Image component. We must also add a height
and some styles to our <Image />
. Otherwise, React Native won’t know what size to render the image as.
Now we have an imgix image rendering in our App!
Styling the image to avoid flash-of-unstyled-content (FLOUC)
What if our user has a slow connection and we want to display something as it loads? Let’s go ahead and add a background color to our image. This will give the user a sense of the dimensions of the image they’re about to see as it loads.
Using @imgix/core-js
to leverage imgix’s Rendering API
Let’s move on and start using imgix’s Rendering API and @imgix/js-core
library to optimize our images, so they load fast. First, we need to install the package.
# inside the project's root directory, run
npm i @imgix/js-core
# or
yarn add @imgix/js-core
Then, we need to instantiate the client in our component. This will let us make use of its URL-building functions later.
const imgix = new ImgixClient({ domain: "sdk-test.imgix.net" })
Now, we can use it to resize the image to match our desired height. Our designers want a 950
px tall image for this page. We can pass this as an imgix rendering API parameter to the ImgixClient
’s buildURL()
function.
const imgix = new ImgixClient({ domain: "sdk-test.imgix.net" })
const height = 950
const imgixParams = { h: height }
const uri = {
uri: imgix.buildURL("amsterdam.jpg", imgixParams),
}
Now our image will be automatically resized to match our set height value.
Automatically cropping to fit inside the device dimensions with the rendering API
Still, we can do much better. Let’s have the image automatically size itself to the window it’s displayed in. This way, the image will be the right size on every device. We’ll use React Native’s useWindowDimensions
module to get the current window’s dimensions.
// ...
const imgix = new ImgixClient({ domain: "sdk-test.imgix.net" })
const { height } = useWindowDimensions()
const imgixParams = { h: height }
const uri = {
uri: imgix.buildURL("amsterdam.jpg", imgixParams),
}
// ...
Now that we can measure the device’s dimensions, why not crop the image to match the device’s height and width? This saves us lots of bytes and loads our image even faster. We can do this using the fit: crop
imgix rendering API parameter.
The image is resized and cropped to fit just right on any device automatically.
Automatically adapting to each device’s device-pixel-ratio (DPR) with the rendering API
We still haven’t accounted for one of the hardest things about working with images on mobile devices; device pixel ratio (DPR).
Mobile devices vary significantly in screen resolution, and images that look great on one can look terrible on the other if you fail to account for the differences in DPR.
Some developers try to get around this by compressing their images to fit the device resolution they plan to ship on. And their images look great until Apple or Google comes out with a new phone size or screen resolution that makes the images look terrible.
Instead, let’s have our image automatically adjust its DPR depending on the device it’s on. We’ll use React Native’s PixelRatio
module and imgix rendering API’s dpr
parameter to make it happen.
// ...
const imgixParams = {
fit: "crop",
h: height,
w: width,
dpr: PixelRatio.get(),
}
const uri = {
uri: imgix.buildURL("amsterdam.jpg", imgixParams),
}
Our image will always be rendered with the correct DPR, ensuring it looks great on low- or high-resolution screens. The drawback here is that our image will be pretty heavy on high-resolution devices. A slower connection could lead to long load times and a “flash” of content.
Creating a low-quality image placeholder (LQIP) with the rendering API
We can get around this issue by storing two versions of this image, an LQIP and the full-resolution image. We will first load the LQIP and then load the full-resolution image once it’s ready.
To do this, we’ll create a low-resolution version of the image by setting the w
and h
imgix parameters to be 1/4 of the full-size image width and height. We’ll also add a blur
parameter so that the image doesn’t look pixelated.
Note: the imgix rendering API has a
blurhash
parameter. It yields a hash that can be used with a library likereact-native-blurhash
. This is can be more performant but is incompatible with Expo Snacks.
//...
const imgix = new ImgixClient({ domain: "sdk-test.imgix.net" })
const { width, height } = useWindowDimensions()
/**
* create a "full resolution image" that matches the device dimensions and
* device-pixel-ratio.
*/
const imgixParams = {
fit: "crop",
h: height,
uri: imgix.buildURL("amsterdam.jpg", imgixParams),
}
/**
* create a low quality image placeholder with imgix rendering API
*/
const lqipParams = {
fit: "crop",
w: width / 4,
h: height / 4,
dpr: 1,
blur: 25,
}
const lqipUri = { uri: imgix.buildURL("amsterdam.jpg", lqipParams) }
Then we add the lqipUri
as the source
for our placeholder image.
return (
// ...
<View style={styles.imageContainer}>
<Image source={lqipUri} style={[styles.image]} />
<Image source={uri} style={[styles.image]} />
</View>
</View>
);
Finally, we use some styling to absolutely position the images so that they render over one another.
// ...
image: {
position: "absolute",
backgroundColor: "#e1e4e8",
left: 0,
right: 0,
bottom: 0,
top: 0,
},
Adding a “blur-up” animation
Our low-resolution image will now render while our full-resolution image is still being requested.
But the transition is a little jarring. Let’s add some animations to smoothly fade in from one image to the next, or “blur-up”.
To do this, we’ll use the React Native Animated
component and React’s useRef
hook. We create a reference to the component’s initial “transparent” state, and then, once the image has loaded, onLoad
, triggers a transition to a full opacity state.
Create a re-usable <ImgixImage />
component
Our image is looking great! Using imgix, our image now:
- resizes automatically to fit the device dimensions
- updates automatically to match the device DPR
- “blurs-up” to better serve low bandwidth users
The only thing left to do is clean things up and refactor our implementation into its own component, ImgixImage.js
.
We can now re-use this component across our application and take advantage of the imgix rendering API anywhere.
Summary
Using imgix with React Native is simple, reliable, and dynamic. It’s an ideal solution for those who work with many images and want those images delivered in the appropriate format and size for all devices where they ship their app.
Other Resources
- js-core library: Check out the official docs for the
@imgix/js-core
library