Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using the onLoad method to load images in React?

Tags:

reactjs

My goal is to have my gallery images render in as they load.
Looking online, I have learned about the onLoad method.
This is what I have tried to utilise, the idea being that the preloader.gif would render until the image is loaded and simply replace the image src with the loaded image.

As it is now(below), the images seem to load in the same way they normally would if I didn't use the onLoad method so there is a moment where no image is being rendered at all before the images load in.

What am I doing wrong exactly?

I've stripped down the code to make the question easier to read.

import React from 'react';
import ReactDOM from 'react-dom';

import preloaderImage from '../../../img/site/preloader.gif';

class WorksPage extends React.Component {

  constructor(){
    super();
  }

  imageLoadHandler(image,e) {
    e.target.src = image;
  }


  render() {

    const listWork = this.props.array.map((item,index)=> {
      return(
        <li key={item.id}>
          <div>
            <img src={preloaderImage} onLoad={this.imageLoadHandler.bind(this,item.image)}/>
          </div>
        </li>
      )
    });

    return(
      <li>
        <div>
          <ul>
            {listWork}
          </ul>  
        </div>  
      </li>
    )
  }
}

module.exports = WorksPage;
like image 798
Moe-Joe Avatar asked Sep 11 '17 06:09

Moe-Joe


2 Answers

Here is an explanation why preloader image isn't rendered:

  1. preloaderImage loads
  2. When it is loaded imageLoadHandler triggers and src attribute is changed to the main image src (so preloaderImage isn't rendered even it is now loaded)
  3. Now the primary image loads
  4. When it is loaded it is rendered

How you should do it properly:

  1. If you write React app, you should do it React way:

    • Set your images to the component state in its constructor method. You need to do it because you are gonna change the way your images are rendered (in React you achieve render changes via changing either component's props or state, in your case it should be state). But notice that you should set src property for each image in the list to your preloaderImage (in example below I am using object destructuring syntax).

      constructor() {
        super();
        this.state = {
          images: this.props.images.map(image => ({
            ...image,
            src: preloaderImage
          }))
        }
      }
      
    • Now in render method create the image list like this (note, you don't need onLoad handler here):

      const listWork = this.state.images.map(image => (
        <li key={image.id}>
          <div>
            <img src={image.src} />
          </div>
        </li>
      ));
      
  2. In componentWillMount method start to load your primary images src dynamically using vanilla JS (you can read article about it here). As they are loaded call setState method to update images src property:

    componentWillMount() {
      this.state.images.forEach((image, index) => {
        const {src} = this.props.images[index] // get image primary src
        const primaryImage = new Image() // create an image object programmatically
        primaryImage.onload = () => { // use arrow function here
          console.log(`image #${index + 1} is loaded!`)
          const images = [...this.state.images] // copy images array from state
          images[index].src = src // adjust loaded image src
          this.setState({
            images
          })
        }
        primaryImage.src = src // do it after you set onload handler
      })
    }
    

    After setState is called with changed images array React will automatically rerender your images with correct src path.


More elegant way with the use of CSS:

Instead of <img /> tag you can use <div /> tag with background-image css property. There you can simply set your fallback (preload) image:

background-image: url('path/to/primary-image'), url('path/to/preload-image');

You can set paths dynamically with style property (in your render method):

const listWork = this.props.images.map(image => {
  const imageStyle = {
    backgroundImage: `url(${image.src}), url(${preloaderImage});`
  }
  return (
    <li key={image.id}>
      <div>
        <div style={imageStyle} />
      </div>
    </li>
  )
})

Just don't forget to set height and width to this <div />.

like image 65
GProst Avatar answered Sep 20 '22 22:09

GProst


Don't know if this helps but you could set a CSS property for your image while it is loading (e.g. opacity or visibility set not to display the image) and then change that CSS property when the onLoad event is fired. You could use the same for your preloader image, but obviously in reverse mode.

You also should be able to set a fallback action if the image won't load, by calling the onError function, also in the image that you are loading.

Hope this helps!

like image 27
Aibu Avatar answered Sep 19 '22 22:09

Aibu