Improve Your UX by Dynamically Rendering Images via React.js
We live in a highly competitive world. As we all know, just having a good idea isn’t enough to make your company the next billion dollar IPO — execution is extrememly important. When it comes to your product, it boils down to one factor — User Experience.
User Experience is a lot more than how your product looks aesthetically. It’s also about how performant it is and how intuitive it is — essentially, it's about how much it delights your user.
We’ve all been there — discovering a new app or web page for the first time and seeing something like this:
With high resolution photos and retina screens, all too often we have to sit and watch images painstakingly render. It’s common to see an image slowly rendering from top to bottom.
This problem can be solved.
The first 2 obvious optimizations are to use a CDN and utilize caching (your browser automatically does this) to make images load faster. However, we can also trick our user’s perception of load time, which results in an overall positive increase in user experience.
Here are 2 tricks we can use to improve our UX on media-rich apps:
1. Use a Placeholder:
A placeholder is a modern twist on the classic loading spinner. Rather than using a generic spinner to indicate the app is loading, we use a placeholder that communicates to the user what type of content is loading — images.
Facebook and LinkedIn are both good examples that use this technique to improve their UX.
2. Dynamically adding images to the DOM:
The second optimization is to fully download our images before showing them on the screen. This will avoid the classic top-to-bottom rendering we’re used to seeing as they’re being downloaded. We can accomplish this by using React’s onLoad
event — we can make the request to the server for the image files, but not render the image in the DOM until the entire file has been downloaded.
The end result is an app that loads high resolution images and never keeps the user waiting. The placeholder teases the users and lets them know that images are being loaded. Furthermore, we hold off on rendering the images until they have been fully downloaded from the server so our user never has to see images painting from top to bottom in their browser.
You can view a live demo here.
Show Me the Code!
Rendering the Placeholder
For our placeholder component (LoadingItem
in this example), we simply render the image and apply any animation effects we want:
export default function () {
return (
<ReactCSSTransitionGroup
transitionName="loadingItem"
transitionAppear={true}
transitionAppearTimeout={500}
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
<img className="feed__loading-item" src={img} />
</ReactCSSTransitionGroup>
)
}
In the render of our Feed component, we simply render LoadingItem
as long as we still have FeedItems
being loaded:
export default class Feed extends Component {
...
render() {
return (
<div className="feed">
...
{this.props.items.length > this.state.loadedItems.length &&
<LoadingItem />
}
...
</div>
)
}
}
onLoad
Dynamically Rendering Images via Our Feed
component works as follows:
export default class Feed extends Component {
constructor(props) {
super(props)
this.state = { loadedItems: [] }
}
onLoad(feedItem) {
let updatedItems = this.state.loadedItems
updatedItems.push({
name: feedItem.name,
imgPath: feedItem.imgPath
})
this.setState({ loadedItems: updatedItems })
}
render() {
return (
<div className="feed">
<h1 className="feed__h1">{this.props.name}</h1>
{this.state.loadedItems.map((item, i) =>
<FeedItem
imgPath={item.imgPath}
name={item.name}
renderModal={this.props.renderModal}
key={i} />
)}
{this.props.items.length > this.state.loadedItems.length &&
<LoadingItem />
}
<div className="hidden">
{this.props.items.map((item, i) =>
<img
src={item.imgPath}
onLoad={this.onLoad.bind(this, item)}
key={i} />
)}
</div>
</div>
)
}
}
So what’s happening here? We have a hidden <div>
at the bottom that is responsible for downloading the image files. When the file has finished downloading, the onLoad
event will trigger, which updates the newly loaded item in the state. When the state updates, the newly loaded item is rendered into the DOM with the image already fully downloaded.
That's It! Here are some more resources:
- View live demo
- View full source code (star the repo if you found this helpful!)
- Succesful UX Design: Make Intuitive Decisions for Users
- 7 User Experience Design Tips For Better Web Development
Props to Dreamweaver circa 1999 for the mmpreload() technique <3