最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Changing background-image property causes a flicker in Firefox - Stack Overflow

programmeradmin0浏览0评论

I'm working on a ponent 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 ponent 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 ponent has updated.
  ponentDidMount() {
    this.interval = setInterval(() => this.rotate(), 5000);
  }

  ponentDidUnmount() {
    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={[
      ".jpg?width=640&crop=smart&auto=webp&s=58d0eb6771296b3016d85ee1828d1c26833fd022",
      ".jpg?width=640&crop=smart&auto=webp&s=1b01fc0c3f20098e6bb1f4126c3c2a54b7bc2b8e",
      ".jpg?width=640&crop=smart&auto=webp&s=ace24e96764bb40a01e7d167a88d35298db76a1c",
      ".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=".6.1/react.min.js"></script>
<script src=".6.1/react-dom.min.js"></script>

<div class="wrapper"></div>

I'm working on a ponent 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 ponent 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 ponent has updated.
  ponentDidMount() {
    this.interval = setInterval(() => this.rotate(), 5000);
  }

  ponentDidUnmount() {
    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./ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/15.6.1/react-dom.min.js"></script>

<div class="wrapper"></div>

Share Improve this question edited Jul 23, 2019 at 19:24 Nico Diz 1,5041 gold badge8 silver badges22 bronze badges asked Jul 23, 2019 at 18:05 James IvesJames Ives 3,3654 gold badges34 silver badges70 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 4

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 ponent has updated.
  ponentDidMount() {
    this.interval = setInterval(() => this.rotate(), 5000);
  }

  ponentDidUnmount() {
    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./ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/15.6.1/react-dom.min.js"></script>

<div class="wrapper"></div>

You can use decode() method that will let you know when image is decoded and ready to be used.

https://developer.mozilla/en-US/docs/Web/API/HTMLImageElement/decode

In your case:

const img = new Image();
img.src = this.props.items[index + 1];
img.decode()
.then(() => {
  // image is decoded and ready to use
})
.catch((encodingError) => {
  // do something with the error.
})
发布评论

评论列表(0)

  1. 暂无评论