Codementor Events

Improve Your UX by Dynamically Rendering Images via React.js

Published Mar 21, 2017
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:

slow-image-loading.gif

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:

placeholder.gif

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.

react-onload-c.gif

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>
    )
  }
}

Dynamically Rendering Images via onLoad

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:

Discover and read more posts from Andrew Wong
get started
post comments1Reply
Jason
8 years ago

Props to Dreamweaver circa 1999 for the mmpreload() technique <3