Search code examples
javascriptreactjsreact-toolbox

Reload Live Image without Flickering in React


I would like to reload an image at a static URL indefinitely in React. Through some searching I arrived at the below less than ideal solution. It works, but I would like to remove the flickering of the image loading. I realize the issues is that the component is re-rendered, and then the image loads. I've seen a couple examples that use the two images one as a placeholder and the loading one hidden until it loads using onLoad and setState, but they all assume a finite number of images. How can I make this display the last image in the CardMedia until the new one is loaded and then replaced without flicker every five seconds?

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

import { Card, CardMedia, CardTitle } from 'react-toolbox/lib/card';

const LIVE_IMAGE = 'https://cdn-images-1.medium.com/max/1600/1*oi8WLwC2u0EEI1j9uKmwWg.png';

class LiveImageCard extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      liveImage: null
    };
  }

  componentDidMount() {
    this.interval = setInterval(
      () => this.setState({
        liveImage: `${LIVE_IMAGE}?${new Date().getTime()}`,
      }),
      5000
    );
  }
  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {

    return (
      <Card style={{width: '350px'}}>
        <CardTitle title="Live Image" />
        <CardMedia
          aspectRatio="wide"
          image={this.state.liveImage}
        />
      </Card>
    );
  }
}

ReactDOM.render(
  <LiveImageCard />,
  document.getElementById('root'),
);

Solution

  • Not sure if this is the best solution, but I ended up with this and it works such that you don't see the flicker.

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    import { Card, CardMedia, CardTitle } from 'react-toolbox/lib/card';
    
    const LIVE_IMAGE = 'https://cdn-images-1.medium.com/max/1600/1*oi8WLwC2u0EEI1j9uKmwWg.png';
    
    class LiveImageCard extends React.Component {
      constructor(props) {
        super(props);
    
        this.loadImage = () => {
          const component = this;
    
          const img = new Image();
          img.crossOrigin = "Anonymous";
          img.onload = function () {
            var canvas = document.createElement("canvas");
            canvas.width =this.width;
            canvas.height =this.height;
    
            var ctx = canvas.getContext("2d");
            ctx.drawImage(this, 0, 0);
    
            var dataURL = canvas.toDataURL("image/png");
            component.setState({liveImage: dataURL});
          };
    
          img.src = `${LIVE_IMAGE}?${new Date().getTime()}`;
          this.setState({ loadingImage: img });
        }
    
        this.state = {
          loadingImage: null,
          liveImage: null
        };
      }
    
    
      componentDidMount() {
        this.loadImage();
        this.interval = setInterval(this.loadImage, 5000);
      }
      componentWillUnmount() {
        clearInterval(this.interval);
      }
    
      render() {
    
        return (
          <Card style={{width: '350px'}}>
            <CardTitle title="Live Image" />
            <CardMedia
              aspectRatio="wide"
              image={this.state.liveImage}
            />
          </Card>
        );
      }
    }
    
    ReactDOM.render(
      <LiveImageCard />,
      document.getElementById('root'),
    );
    

    Or as a standalone component without the React-Toolbox

    class LiveImage extends React.Component {
      constructor(props) {
        super(props);
    
        this.loadImage = () => {
          const component = this;
    
          const img = new Image();
          img.crossOrigin = "Anonymous";
          img.onload = function () {
            var canvas = document.createElement("canvas");
            canvas.width =this.width;
            canvas.height =this.height;
    
            var ctx = canvas.getContext("2d");
            ctx.drawImage(this, 0, 0);
    
            var dataURL = canvas.toDataURL("image/png");
            component.setState({liveImage: dataURL});
          };
    
          img.src = `${this.props.image}?${new Date().getTime()}`;
          this.setState({ loadingImage: img });
        }
    
        this.state = {
          loadingImage: null,
          liveImage: null
        };
      }
    
      componentDidMount() {
        this.loadImage();
        this.interval = setInterval(this.loadImage, this.props.interval);
      }
      componentWillUnmount() {
        clearInterval(this.interval);
      }
    
      render() {
        return (
          <img src={this.state.liveImage} {...this.props} />
        );
      }
    }