什么是react-swipeable-views
react-swipeable-views 是一个触屏端手势滑动进行整屏切换的react组件。
使用痛点
api无法满足业务需求
有一个场景,当用户在页面中完成某些操作后,才可以上划 但是此组件提供的disabled
属性,会将上划,下划都禁用
页面底部的区域元素会被操作栏遮挡
因为设计和布局都是按照整个浏览器高度,但是在地址栏和操作栏的影响下 一些定位在底部元素就无法看到了
容器高度无法自动适配浏览器视窗高度
某些浏览器行为(如上图),比如上划到底部后,继续上划,浏览器地址栏会消失,此时视窗大小会发生变化 如果不进行高度重新适配,transform:translateY的位置
还是依据之前的视窗高度,造成显示错位
原生局部滚动会和组件干架
如何解决
api无法满足业务需求
基本思路:克隆一份源码到本地,对源码进行扩展
扩展属性
在handleSwipeMove中处理
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
const [rightHeight, setRightHeight] = useState(0)
useEffect(() => {
setRightHeight(window.innerHeight)
},[])
复制代码
给swipeView中的组件设置高度
const getRightHeight = () =>{
return {height:rightHeight}
}
<SwipeableViews
resistance
axis="y"
animateHeight={true}
>
<APage style={getRightHeight()}/>
<BPage style={getRightHeight()}/>
<CPage style={getRightHeight()}/>
</SwipeableViews>
复制代码
const A|B|CPage =({style}) => {
return <div style={style}>
...........
...........
...........
...........
</div>
}
复制代码
容器高度无法自动适配浏览器视窗高度
方法一
const [animateHeight, setAnimateHeight] = useState(true)
useEffect(() => {
window.onresize = function () {
// 重新调整swipeview内元素高度并通知组件重新适配高度
setRightHeight(window.innerHeight)
setAnimateHeight(false)
setTimeout(() => {
setAnimateHeight(true)
}, 60)
}
},[])
复制代码
const getRightHeight = () =>{
return {height:rightHeight}
}
<SwipeableViews
resistance
axis="y"
animateHeight={animateHeight}
>
<APage style={getRightHeight()}/>
<BPage style={getRightHeight()}/>
<CPage style={getRightHeight()}/>
</SwipeableViews>
复制代码
这是通过对animateHeight
的更改触屏对containerStyle
的高度的修复 见源码
这种方法会导致高度瞬间消失,使得页面会瞬间白屏下(如下图)
所以,看到这,笔者开始思考,会不会直接修改containerStyle
来的更简单呢
方法二
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
容器中拷贝一份到外面
<>
<SwipeableViews
resistance
axis="y"
containerStyle={getRightHeight()}
>
<APage style={getRightHeight()}/>
<BPage style={getRightHeight()}/>
<CPage style={getRightHeight()}/>
</SwipeableViews>
<CPage style={getRightHeight()}/>
</>
复制代码
到第三页隐藏swipeView 显示外部第三页
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'}}/>
</>
复制代码
- 为什么不能使用
currentIndex < 2?<SwipeableViews/>:<CPage>
这种形式?
答:这种方式会导致组件的重新渲染,滑动位置的状态会丢失
- 为什么还要保留
SwipeableViews
内部的CPage
?
答:保证流畅的过渡,内部CPage
的存在会使得切换到第三页的临界状态依然会存在过渡动画
给第三页加上一个touch事件,保证可从第三页回到第二页
// 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>
}
复制代码
其他开发问题
底部引导提示在滑动时需消失,但是当手指滑动到该图标上,会阻止上划
原因分析
可能是因为滑动时,将该提示display:none
处理,导致图层之间的touch事件不连续
解决方法
display:none
改为opacity:0
注意
考虑一下会不会遮挡后面图层的元素
结语
前端开发就是逆天而行,共勉。