It’s a competitive world out there right now. As we all know, just having a good idea isn’t enough to make a company the next billion dollar IPO. Execution is just as important, and when it comes to product, that boils down to one factor — User Experience.
User Experience is a lot more than how our product looks aesthetically. It’s also how performant it is, how intuitive it is — how much it delights our users.
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, it’s all too often we have to sit and watch images painstakingly render. It’s a common sight 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:
use a placeholder to indicate media is loading
A placeholder is modern twist on the classic loading spinner. Rather than 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.
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 for images as they’re being downloaded. We accomplish this by using React’s [onLoad](https://facebook.github.io/react/docs/events.html#image-events)
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: smooth as butter
The end result is an app that loads high resolution images and never keeps the user waiting. The placeholder teases the user and lets them know 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 the browser.
For our placeholder component (LoadingItem
in this example), we simply render the image and apply any animation effects we want:
export default function () {return (<ReactCSSTransitionGrouptransitionName="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>
)
}}
Our Feed
component works as follows:
export default class Feed extends Component {constructor(props) {super(props)this.state = { loadedItems: [] }}
onLoad(feedItem) {this.setState(({ loadedItems }) => {return { loadedItems: loadedItems.concat(feedItem) }})}
render() {return (<div className="feed"><h1 className="feed__h1">{this.props.name}</h1>{this.state.loadedItems.map((item, i) =><FeedItemimgPath={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) =><imgsrc={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 is 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.