Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing background-image property causes a flicker in Firefox

I'm working on a component that rotates a series of background images in a banner on my page. The problem I'm running into is that when the background-image properties url is changed via state it seems to cause a flash of white. This flashing doesn't seem to happen all the time in Chrome, but does happen consistently in Firefox and sometimes Safari. For additional context I'm using Mac OSX.

At first I assumed this was because the images are being retrieved by the browser when they are requested, but to avoid this I've made some considerations for pre-fetching by rendering a hidden image tag with the resource.

{this.props.items.map(item => (
  <img src={item} style={{ display: "none" }} />
))}

I've also tried creating a new image in the rotate method that pre-fetches the next rotation item ahead of the transition, but neither seem to work.

const img = new Image();
img.src = this.props.items[index + 1];

Where am I going wrong here? I've attached an example of the component below. Any help would be appreciated.

class Cats extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      background: props.items[0],
      index: 0
    };

    this.rotate = this.rotate.bind(this);
  }

  // Let's you see when the component has updated.
  componentDidMount() {
    this.interval = setInterval(() => this.rotate(), 5000);
  }

  componentDidUnmount() {
    clearInterval(this.interval);
  }

  rotate() {
    const maximum = this.props.items.length - 1;
    const index = this.state.index === maximum ? 0 : this.state.index + 1;

    this.setState({
      background: this.props.items[index],
      index
    });
  }

  render() {
    return (
      <div
        className="background"
        style={{ backgroundImage: `url(${this.state.background})` }}
      >
        {this.props.items.map(item => (
          <img src={item} style={{ display: "none" }} />
        ))}
      </div>
    );
  }
}

ReactDOM.render(
  <Cats
    items={[
      "https://preview.redd.it/8lt2w3du0zb31.jpg?width=640&crop=smart&auto=webp&s=58d0eb6771296b3016d85ee1828d1c26833fd022",
      "https://preview.redd.it/120qmpjmg1c31.jpg?width=640&crop=smart&auto=webp&s=1b01fc0c3f20098e6bb1f4126c3c2a54b7bc2b8e",
      "https://preview.redd.it/guprqpenoxb31.jpg?width=640&crop=smart&auto=webp&s=ace24e96764bb40a01e7d167a88d35298db76a1c",
      "https://preview.redd.it/mlzq0x1o0xb31.jpg?width=640&crop=smart&auto=webp&s=b3fd159069f45b6c354de975daffde21f04c3ad5"
    ]}
  />,
  document.querySelector(".wrapper")
);
html, body, .wrapper {
  width: 100%;
  height: 100%;
}

.background {
  position: static;
  background-size: cover;
  height: 100%;
  width: 100%;
  transition: background-image 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.1/react-dom.min.js"></script>

<div class="wrapper"></div>
like image 943
James Ives Avatar asked Oct 19 '25 13:10

James Ives


1 Answers

Unfortunately, it seems like this flicker is a known bug in Firefox caused by its image decoder, which won't decode an image until it's displayed for the first time. In the snippet below, I created overlapping divs, one which loads the next image slightly earlier and sits behind the other. This way when the other "flickers," the proper image is already displayed behind, rather than a white background.

You could also theoretically display all the images in the hidden div really quickly, then set it back to white, since the images only need to be displayed once for the decoder to work.

Depending on the long-term goal for this project, the most proper way around this problem may be to use a <canvas> to render your images. The canvas element uses a different decoder which won't cause a flicker.

class Cats extends React.Component {
  constructor(props) {
    super(props);
    
    this.props.items.forEach((item) => {
       const img = new Image(640, 640);
       img.src = item;
    });

    this.state = {
      background: props.items[0],
      preloadBackground: props.items[1],
      index: 0
    };

    this.rotate = this.rotate.bind(this);
  }

  // Let's you see when the component has updated.
  componentDidMount() {
    this.interval = setInterval(() => this.rotate(), 5000);
  }

  componentDidUnmount() {
    clearInterval(this.interval);
  }

  rotate() {
    const maximum = this.props.items.length - 1;
    const index = this.state.index === maximum ? 0 : this.state.index + 1;

    this.setState({
      preloadBackground: this.props.items[index],
      index
    });
    
    setTimeout(() => {
      this.setState({
        background: this.props.items[index],
      });
    }, 100);
  }

  render() {
    return (
      <div className="pane">
        <div
          className="preload-background"
          style={{ backgroundImage: `url(${this.state.preloadBackground})` }}
        >
        </div>
        <div
          className="background"
          style={{ backgroundImage: `url(${this.state.background})` }}
        >
        </div>
      </div>
    );
  }
}

ReactDOM.render(
  <Cats
    items={[
      "https://preview.redd.it/8lt2w3du0zb31.jpg?width=640&crop=smart&auto=webp&s=58d0eb6771296b3016d85ee1828d1c26833fd022",
      "https://preview.redd.it/120qmpjmg1c31.jpg?width=640&crop=smart&auto=webp&s=1b01fc0c3f20098e6bb1f4126c3c2a54b7bc2b8e",
      "https://preview.redd.it/guprqpenoxb31.jpg?width=640&crop=smart&auto=webp&s=ace24e96764bb40a01e7d167a88d35298db76a1c",
      "https://preview.redd.it/mlzq0x1o0xb31.jpg?width=640&crop=smart&auto=webp&s=b3fd159069f45b6c354de975daffde21f04c3ad5"
    ]}
  />,
  document.querySelector(".wrapper")
);
html, body, .wrapper, .pane {
  width: 100%;
  height: 100%;
}

.background {
  position: static;
  background-size: cover;
  height: 100%;
  width: 100%;
  transition: background-image 1s ease-in-out;
}

.preload-background {
  position: absolute;
  background-size: cover;
  height: 100%;
  width: 100%;
  z-index: -1;
  transition: background-image 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.1/react-dom.min.js"></script>

<div class="wrapper"></div>
like image 103
eiko Avatar answered Oct 21 '25 01:10

eiko



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!