解决react-swipeable-views在浏览器中的适配的问题

什么是react-swipeable-views

[react-swipeable-views](https://github.com/oliviertassinari/react-swipeable-views)是一个触屏端手势滑动进行整屏切换的react组件。

使用痛点

api无法满足业务需求

有一个场景,当用户在页面中完成某些操作后,才可以上划
但是此组件提供的disabled属性,会将上划,下划都禁用

页面底部的区域元素会被操作栏遮挡

因为设计和布局都是按照整个浏览器高度,但是在地址栏和操作栏的影响下 一些定位在底部元素就无法看到了

容器高度无法自动适配浏览器视窗高度

某些浏览器行为(如上图),比如上划到底部后,继续上划,浏览器地址栏会消失,此时视窗大小会发生变化
如果不进行高度重新适配,transform:translateY的位置还是依据之前的视窗高度,造成显示错位

原生局部滚动会和组件干架

1ec411d0488d50c487bf33676fd73e25 (2).gif

swipe时会导致内容区域元素层级变化

如何解决

api无法满足业务需求

基本思路:克隆一份源码到本地,对源码进行扩展

扩展属性

image.png

在handleSwipeMove中处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 handleSwipeMove = event => {
...............................
...............................
...............................
...............................
const { axis,
children,
ignoreNativeScroll,
onSwitching,
resistance,
disableTouchUp,
disableTouchDown
} = this.props;
const touch = applyRotationMatrix(event.touches[0], axis);
const isTouchUp = touch.pageX - this.startX < 0
const isTouchDown = touch.pageX - this.startX > 0
if (disableTouchUp && isTouchUp) {
console.log('isTouchUp',isTouchUp)
return;
}
if (disableTouchDown && isTouchDown) {
return;
}
...............................
...............................
...............................
...............................
}

页面底部的区域元素会被操作栏遮挡

获取正确height

1
2
3
4
const [rightHeight, setRightHeight] = useState(0)   
useEffect(() => {
setRightHeight(window.innerHeight)
},[])

给swipeView中的组件设置高度

1
2
3
4
5
6
7
8
9
10
11
12
const getRightHeight = () =>{
return {height:rightHeight}
}
<SwipeableViews
resistance
axis="y"
animateHeight={true}
>
<APage style={getRightHeight()}/>
<BPage style={getRightHeight()}/>
<CPage style={getRightHeight()}/>
</SwipeableViews>
1
2
3
4
5
6
7
8
9
const A|B|CPage =({style}) => {

return <div style={style}>
...........
...........
...........
...........
</div>
}

容器高度无法自动适配浏览器视窗高度

方法一

1
2
3
4
5
6
7
8
9
10
11
const [animateHeight, setAnimateHeight] = useState(true)
useEffect(() => {
window.onresize = function () {
// 重新调整swipeview内元素高度并通知组件重新适配高度
setRightHeight(window.innerHeight)
setAnimateHeight(false)
setTimeout(() => {
setAnimateHeight(true)
}, 60)
}
},[])
1
2
3
4
5
6
7
8
9
10
11
12
const getRightHeight = () =>{
return {height:rightHeight}
}
<SwipeableViews
resistance
axis="y"
animateHeight={animateHeight}
>
<APage style={getRightHeight()}/>
<BPage style={getRightHeight()}/>
<CPage style={getRightHeight()}/>
</SwipeableViews>

这是通过对animateHeight的更改触屏对containerStyle的高度的修复
见源码
image.png
这种方法会导致高度瞬间消失,使得页面会瞬间白屏下(如下图)
d8e8940fb2d62a28d82d553fb257d969 (1).gif
所以,看到这,笔者开始思考,会不会直接修改containerStyle来的更简单呢

方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
const getRightHeight = () =>{
return {height:rightHeight}
}

<SwipeableViews
resistance
axis="y"
containerStyle={getRightHeight()}
>
<APage style={getRightHeight()}/>
<BPage style={getRightHeight()}/>
<CPage style={getRightHeight()}/>
</SwipeableViews>

事实证明确,确实不会闪屏,但还会存在视窗resize时,下一页内容出现的问题
究其根本原因,因为该组件是使用的transform:translateY对一屏位置进行确定,所以屏与屏之间是连续的
resize事件发生时,我们绑定的回调函函数执行,state改变到页面重新绘制是个异步行为,所以页面会先保持未重新适配前的状态,再变为适配后的状态。

原生局部滚动会和组件干架

思路:如果放在容器内部会互相感扰,那就放到容器外部

将最后一页从SwipeableViews容器中拷贝一份到外面

1
2
3
4
5
6
7
8
9
10
11
12
13

<>
<SwipeableViews
resistance
axis="y"
containerStyle={getRightHeight()}
>
<APage style={getRightHeight()}/>
<BPage style={getRightHeight()}/>
<CPage style={getRightHeight()}/>
</SwipeableViews>
<CPage style={getRightHeight()}/>
</>

到第三页隐藏swipeView 显示外部第三页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const [currentIndex, setCurrentIndex] = useState(0)  

const onSwitching = (index) => {
setCurrentIndex(index)
}

<>
<SwipeableViews
style={{display: currentIndex < 2 ? 'block' : 'none'}}
resistance
axis="y"
containerStyle={getRightHeight()}
onSwitching={onSwitching}
>
<APage style={getRightHeight()}/>
<BPage style={getRightHeight()}/>
<CPage style={getRightHeight()}/>
</SwipeableViews>
<CPage style={{...getRightHeight(),display: currentIndex >= 2 ? 'block' : 'none'}}/>
</>
  1. 为什么不能使用currentIndex < 2?<SwipeableViews/>:<CPage>这种形式?

答:这种方式会导致组件的重新渲染,滑动位置的状态会丢失

  1. 为什么还要保留SwipeableViews内部的CPage

答:保证流畅的过渡,内部CPage的存在会使得切换到第三页的临界状态依然会存在过渡动画

给第三页加上一个touch事件,保证可从第三页回到第二页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Parent 回到第二页
const onTouchDownToLastPage = () => {
setCurrentIndex(1)
}

// Child
const CPage =({style,onTouchDownToLastPage}) => {
const onTouchStart = (e) => {
if(touchStartY.current === null) {
setHasTouched(true)
}
touchStartY.current = e.changedTouches[0].pageY
}
const onTouchMove = (e) => {

let delta = e.changedTouches[0].pageY - touchStartY.current
const dom = comicImgRef.current
const scrollTop = comicWrap.current.scrollTop
// (delta > 0 保证下滑 Math.abs(scrollTop) < 50 保证滚动调离顶部阈值范围
if (delta > 0 && Math.abs(scrollTop) < 50) {
onTouchDownToLastPage && onTouchDownToLastPage()
}
}

return <div style={style} onTouchStart={onTouchStart} onTouchMove={onTouchMove}>
...........
...........
<div className={styles.comic} ref={comicImgRef}></div>
...........
...........
</div>
}

swipe时会导致内容区域元素层级变化

解决方法

其他开发问题

image.png

底部引导提示在滑动时需消失,但是当手指滑动到该图标上,会阻止上划

原因分析

可能是因为滑动时,将该提示display:none处理,导致图层之间的touch事件不连续

解决方法

display:none改为opacity:0

注意

考虑一下会不会遮挡后面图层的元素

结语

前端开发就是逆天而行,共勉。

查看评论