I'm trying to make a small sprite-based game with ReactJS. The green dragon (was taken from HMMII) is flying across the hexagonal field and it's behavior depends on mouse clicking. The sprites change each other with speed depending on a specially chosen time constant - 170ms. More precisely: there is a div representing the dragon and it's properties (top, left, width, height and background-image) always are changing.

At the first stage of the development I've faced with irritating blinking and flickering by rerendering the image. How can avoid it?

Below are described multiple ways I've used with some previews made with Surge. The strongest effect is watched in Google Chrome but in Firefox also are troubles.

0) At first I've tried to use CSS-animation based on @keyframes, but it was no good due to fade effect. And I don't need any fade effects at all, I need rapid rerendering.

1) This is the most straightforward attempt. After clicking on a particular field, componentWillReceiveProps is creating the list of steps and then all of this steps are performing consistently. Also I've tried to use requestAnimationFrame instead of setTimeout but with the same troubles.

    makeStep() {
        const {steps} = this.state;

        this.setState((prevState, props) => ({
            steps: steps.slice(1),
            style: {...}
        }));
    }


    render() {
        const {steps, style} = this.state;
        steps.length ? setTimeout(this.makeStep, DRAGON_RENDER_TIME):
                       this.props.endTurn();

        return (<div id="dragon" style={style}></div>);
    }

Here is the result: http://streuner.surge.sh/ As you can see, dragon is often disapearing by launching and landing, it fly with skipping some sprites.

2) I've tried to test method describen in article: https://itnext.io/stable-image-component-with-placeholder-in-react-7c837b1ebee

In this case I've changed my div with background-image to other div containing explicit img. At first, this.state.isLoaded is false and new sprite will not appear. It appears only after the image has been loaded with onLoad method. Also I've tried to use refs with attempt watch for complete-property of the image but it's always true - maybe because size of the image is very small.

    setLoaded(){
        this.setState((prevState, props) => ({
            isLoaded: true
        }));
    }


    render() {
        const {isLoaded, steps, style} = this.state;
        if(isLoaded) {
            steps.length ? setTimeout(this.makeStep, DRAGON_RENDER_TIME):
                           this.props.endTurn();
        }

        return (<div id="wrap" style={{top:style.top, left:style.left}} >
            <img id="dragon" alt="" src={style.src} onLoad={this.setLoaded}
                 style={{width:style.width,
                         height: style.height,
                         visibility: isLoaded ? "visible": "hidden"}}/>
        </div>);
    }

Here is the result: http://streuner2.surge.sh/ There's no more sprite skipping but the flickering effect is much stronger than in first case.

3) Maybe it was my best attempt. I've read this advice: https://github.com/facebook/react-native/issues/981 and decided to render immediately all of the step images but only the one with opacity = 1, the others have opacity = 0.

    makeStep(index) {
        const {steps} = this.state;

        this.setState((prevState, props) => ({
            index: index + 1,
            steps: steps.map( (s, i) => ({...s, opacity: (i !== index) ? 0: 1}))
        }));
    }


  render()  {
        const {index, steps} = this.state;

        (index < steps.length) ? 
              setTimeout(() => this.makeStep(index), DRAGON_RENDER_TIME):
              this.props.endTurn();

        return ([steps.map((s, i) => 
                   <div className="dragon" key={i} style={s}></div>)]);
    }

It's possible to see the result here: http://streuner3.surge.sh/ There's only one flickering by starting new fly with rerendering all sprites. But the code seems to me more artificial.

I would like to emphasize that the behavior always depends on browser, in Firefox it's much better. Also there are differences with variety of flys in the same browser: sometimes there's no flickering effect but in most of cases it unfortunately is. Maybe I don't understand any basic notion of rerendering images in browser.

1 Answers

0
Dmitriy  Sakhno On

I think you should shift your attention from animation itself and pay more attention to rerendering in React, each time when you change Image component state or props it is rerendering. Read about lifecycle methods and rerendering in React docs.

You change state very fast(in your case it's almost 6 times per second), so I suppose that some of the browsers are not fast enough with Image component rerendering. Try to move out of Image state variables which updates so fast and everything will be ok