5

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'),
);
Rob Cannon
  • 384
  • 4
  • 16
  • The issue comes from the fact that you are switching the image source in your `CardMedia` when the image starts loading (but isn't fully loaded). Your Card Media component should handle that for you or you will have to write your own and use intermediate 1s timeout in between the time the image starts loading and you swap the HTML img source: https://stackoverflow.com/questions/5438612/how-to-display-a-constantly-changing-image-file-in-a-browser-without-refresh-fli – klugjo Apr 17 '18 at 02:38
  • I appreciate the response, but your comment is glossing over the problem. I've tried a number of ways to do what you are suggesting, and none of them work. For instance, to do what you are suggesting, I could make my `setInterval` function something like `const img = new Image(); img.onload = () => this.setState({ liveImage: img }); img.src = \`${LIVE_IMAGE}?${new Date().getTime()}\`; this.setState({ temp: img });` but React or the React-Toolbox doesn't like using the `Image` object in the `CardMedia` attribute. – Rob Cannon Apr 17 '18 at 12:55

2 Answers2

3

For me, removing <Image> props, resizeMode={"contain"} solved flickering issue on Android.

Nay
  • 677
  • 1
  • 8
  • 14
1

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} />
    );
  }
}
Rob Cannon
  • 384
  • 4
  • 16
  • Could you explain why it works ? I'm trying to do the same in React-Native. Thanks in advance. –  Apr 24 '18 at 13:44
  • @OlivierMATROT We can use this component to fix this issue on android devices. https://www.npmjs.com/package/react-native-no-flicker-image – Stanislav Mayorov Aug 17 '18 at 20:05