React中实现循环滑动效果难点

最近有这样一个需求,在h5页面上,向左/向右滑动循环显示数据项,效果如下:

比如有3个页面,从第一个页面向左滑动到第二个页面,或者从第二个页面向左滑动到第三个页面,很简单,只需要将left从0%切换到-100%,或从-100%更换到-200%,然后在对left增加transition属性,如:transition: left 0.5s,就可以简单实现从第一张滑动到第二张,第二张到第三张,那么从第三个页面如何继续向左滑动到第一个页面,如果仅仅将left从-200%直接更换到0%,那么视觉上的效果就是从最后一页像右滑动到了第一页,滑动效果就变了,如下:

那么如何让从最后一张也保持不变的滑动效果滑动到第一张呢?

从最后一张直接改变属性到第一个页面是不太可能实现该效果了,要实现向左滑动,那么动画的left属性还是得变的越来越小,如-100%--> -200%,为了实现这个效果,我们可增加一个过渡页面,该过渡页面可与第一个页面一样,就是第一个页面的clone页面,作为第4个页面,即第当从第三个页面滑动到第一个页面的时候,实际上滑动到的是该过渡页面(第4个页面),再当从第4个页面继续向左滑动的时候,那么应该滑动到第2个页面,这时可先设置setState({left:0})(render页面的时候并没有变化,由于当前的第4个页面就是第一个页面的clone),在该setStatecallback中再设置left: -100%,这时left的属性就是从0–>-100%,即实现了向左滑动的效果的条件。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
this.outerStyle = {
left: 0
};
this.setState(
{
curPage: 1,
outerStyle: this.outerStyle
},
() => {
this.outerStyle = {
left: `-${this.indicator * 100}%`,
transition: `left 0.5s`
};
this.setState({outerStyle: this.outerStyle});
}
);

但是另一个问题又来了,为何setState left:0好像没起作用, 浏览器直接从当前状态直接过度到了setState callback中的状态?

原来是由于在一个渲染帧中,浏览器会忽略中间的状态,直接渲染最后一个状态,所以其中的left:0效果会没有了,如A->B->C, B和C在一个渲染帧中,那么我们将直接看见从A->C的变化状态,而忽略B的状态,从根本上的解决该问题就是让这2个状态在2个渲染帧中,解决方法有如下:

1.将C的状态放在setTimeout中,最好timeout时间超过16.7ms(1/60),如果timeout设为0,在某些浏览器可能不工作

1
2
3
setTimeout(() => {
this.setState({outerStyle: this.outerStyle});
}, 20);

2.采用requestAnimationFrame

1
2
3
requestAnimationFrame(() => {
this.setState({outerStyle: this.outerStyle});
});

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!