Lazy-loading images with React and Semantic UI

Categories:

Images make up most of the website’s content size and therefore play a huge part in page load times. This is especially problematic with single-page websites like most landing pages are nowadays. A solution to this problem is lazy loading images, i.e., the images below the fold are loaded asynchronously at a later time. There are two main ways to do lazy loading:

  1. Delay loading images until the above the fold content is loaded, and then load all images
  2. Delay loading the image until it is in the user’s viewport

Lazy-loading images when they are in the viewport is really easy with Semantic UI: You define the image src as data-src instead and then attach a visibility handler to the images:

$('.demo.items .image img')
  .visibility({
    type       : 'image',
    transition : 'fade in',
    duration   : 1000
  })
;

Lazy-loading images using React and its React Semantic UI port is a bit different as we won’t use jQuery, but React components instead. We can create a LazyImage React component that loads the image when it’s in the viewport. For that, we use the Visibility component that has a callback when the top edge of the component is in the viewport:

import PropTypes from 'prop-types'
import { Visibility, Image, Loader } from 'semantic-ui-react'

export default class LazyImage extends React.Component {
    static propTypes = {
        src: PropTypes.string.isRequired,
        size: PropTypes.string,
    }

    static defaultProps = {
        size: `medium`,
    }

    state = {
        show: false,
    }

    showImage = () => {
        this.setState({
            show: true,
        })
    }

    render() {
        const { size } = this.props
        if (!this.state.show) {
            return (
                <Visibility as="span" onTopVisible={this.showImage}>
                    <Loader active inline="centered" size={size} />
                </Visibility>
            )
        }
        return <Image {...this.props} />
    }
}

The usage is exactly the same as Semantic UI React’s Image component as we just forward the props to it:

<LazyImage
    src="https://source.unsplash.com/random/400x300"
    size="mini"
    rounded
/>

If the image is not in the viewport we render a Loader. You might wonder why we do that because if the image is not in the viewport, it’s not visible by definition and it doesn’t matter what we render as its placeholder. This is true in theory, but I noticed you can still see a Loader when the image is in the initial viewport and you’re using server-side rendering. The server will serve the Loader and it takes a fraction of a second until the app is rehydrated and the Visibility handler is hooked up.