如何通过一劳永逸地重新渲染来解决图像闪烁?

时间:2019-05-11 14:36:47

标签: reactjs image sprite flicker

我正在尝试使用ReactJS制作一个基于Sprite的小型游戏。绿龙(取自HMMII)在六边形场上飞行,其行为取决于鼠标的单击。精灵会根据特殊选择的时间常数170ms随速度而变化。更准确地说:有一个表示龙的div,并且其属性(顶部,左侧,宽度,高度和背景图像)总是在变化。

在开发的第一阶段,我面临着通过重新渲染图像而引起的眨眼和闪烁的烦恼。如何避免呢?

下面介绍了我在Surge进行的一些预览中使用的多种方法。在Google Chrome浏览器中观察到最强的效果,但在Firefox中也很麻烦。

0)最初,我尝试使用基于@keyframes的CSS动画,但是由于淡入淡出效果不好。而且我根本不需要任何淡入淡出效果,我需要快速重新渲染。

1)这是最直接的尝试。单击特定字段后,componentWillReceiveProps将创建步骤列表,然后所有这些步骤将一致地执行。另外,我尝试使用requestAnimationFrame代替setTimeout,但是存在相同的麻烦。

    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>);
    }

结果是:http://streuner.surge.sh/正如您所看到的,龙经常通过发射和降落而消失,它会跳过一些精灵而飞翔。

2)我尝试测试文章中描述的方法: https://itnext.io/stable-image-component-with-placeholder-in-react-7c837b1ebee

在这种情况下,我将具有背景图像的div更改为包含显式img的其他div。首先,this.state.isLoaded为false,不会显示新的精灵。仅在使用onLoad方法加载图像后才显示。另外,我尝试将refs与try watch一起使用以获取图像的完整属性,但这始终是正确的-也许是因为图像的大小很小。

    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>);
    }

结果是:http://streuner2.surge.sh/没有更多的精灵跳过,但是闪烁效果比第一种情况更强。

3)也许这是我的最佳尝试。我已经阅读了以下建议:https://github.com/facebook/react-native/issues/981,并决定立即渲染所有步骤图像,但是只有一个不透明度= 1,其他的不透明度= 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>)]);
    }

在这里可能会看到结果:http://streuner3.surge.sh/通过重新渲染所有子画面开始新的飞行,只有一次闪烁。但是在我看来,代码似乎更人为。

我想强调一下,该行为始终取决于浏览器,在Firefox中要好得多。同样,在同一浏览器中,各种苍蝇也存在差异:有时没有闪烁效果,但不幸的是在大多数情况下是这样。也许我不了解在浏览器中重新渲染图像的任何基本概念。

2 个答案:

答案 0 :(得分:0)

我认为您应该将注意力从动画本身转移到其他地方,并在每次更改Image组件状态或道具重新渲染时,更加关注React中的重新渲染。在React文档中了解生命周期方法和重新渲染。

您可以非常快速地更改状态(在您的情况下,它几乎是每秒6次),因此我认为某些浏览器的Image组件重新渲染速度不够快。尝试移出图像状态变量,该变量会更新得如此之快,一切都会好的

答案 1 :(得分:0)

我知道答案已经晚了,但在这里发布我的答案,以防有人仍然想找到解决方案,因为我发现这会带来一些流量。 一个简单的解决方法是向图像添加 CSS 过渡属性,如下所示:

transition: all .5s;

它不会阻止图像重新渲染,但至少它确实阻止了图像闪烁。