3
\$\begingroup\$

My react.js Image component will load an image from a URL or base64 string, do some processing on it and put it on a canvas. The "preload" prop is a 10x10 pixel image that is blurred just a little bit to make it look smoother.

The blur algorithm itself is very fast (I also tried removing it, but got the same results in performance), but the component itself seems to be extremely slow, especially in lists. A page that needs to load a list (12 items for example) of images usually takes about 200-600ms before it renders in the DOM. This is unacceptable.

export default class Image extends React.Component {
  constructor(props, context) {
    super(props, context)

    // Initial state
    this.state = {
      preloadReady: false,
      sourceReady: false,
    }

    this.loadImage = this.loadImage.bind(this)
  }

  setImage(props) {
    this.ctx1.clearRect(0, 0, this.refs.preloadElement.width, this.refs.preloadElement.height);
    this.ctx2.clearRect(0, 0, this.refs.completeElement.width, this.refs.completeElement.height);
    this.img1.src = props.preload;
    this.img2.src = props.src;
  }

  onImageLoaded(type) {
    if (type == 'pre') {
      this.load(this.img1, this.ctx1, this.refs.preloadElement, true, (this.props.preloadBlur || 5), false);
      this.setState({preloadReady: true});
    }
    if (type == 'src') {

      // Blur?
      let blur = this.props.sourceBlur == undefined ? false : true;
      let blurScale = this.props.sourceBlur || 0;

      this.load(this.img2, this.ctx2, this.refs.completeElement, blur, blurScale, true);
      this.setState({sourceReady: true});
    }
  }

  load(img, ctx, canvas, blur, blurScale, smooth) {
    var data, filtered;

    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage(img ,0 ,0 ,img.width,img.height,0,0, canvas.width, canvas.height);

    // Blur the image?
    if (blur) {
      data = ctx.getImageData(0, 0, canvas.width, canvas.height);
      filtered = Blur(canvas, 0, 0, img.width, img.height, blurScale);
      ctx.putImageData(filtered, 0, 0);
    }
  }

  loadImage(props) {
    // Creates images in memory
    this.img1 = new window.Image();
    this.img2 = new window.Image();

    // Canvas context
    this.ctx1 = this.refs.preloadElement.getContext('2d');
    this.ctx2 = this.refs.completeElement.getContext('2d');

    // Event listeners
    this.img1.onload = this.onImageLoaded.bind(this, 'pre');
    this.img2.onload = this.onImageLoaded.bind(this, 'src');

    // Set CORS fix
    this.img1.crossOrigin = "anonymous";
    this.img2.crossOrigin = "anonymous";

    // Load images
    this.setImage(props)
  }

  componentWillUnmount() {
    this.img1.onload = null;
    this.img2.onload = null;
  }

  componentDidMount() {
    this.loadImage(this.props)
  }

  shouldComponentUpdate(props, state) {
    if (this.props.src !== props.src) {
      this.setState({sourceReady: false, preloadReady: false}, () => {
        this.setImage(props)
      });
    }
    return true
  }

  render () {
    let {state, props} = this,
    classes = {
      src: state.sourceReady ? 'ready' : '',
      pre: state.preloadReady ? 'ready' : '',
    };

    return (
      <div className={"async-image tr_quick"} style={{width: props.width, height: props.height}}>
          <canvas ref="preloadElement" style={{width: props.width, height: props.height}} className={'preload tr_quick ' + classes.pre}></canvas>
          <canvas ref="completeElement" style={{width: props.width, height: props.height}} className={'src tr_quick ' + classes.src}></canvas>
          {props.showImg ? (<img alt={props.alt} style={{display: 'none'}} src={props.src}/>) : null}
      </div>
    )
  }
}

Like I said, this is a very slow component. I'd like to make it faster while still keeping the great features like blurring.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.