[react-swipeable-views](https://github.com/oliviertassinari/react-swipeable-views)
是一个触屏端手势滑动进行整屏切换的react组件。
使用痛点
api无法满足业务需求
有一个场景,当用户在页面中完成某些操作后,才可以上划
但是此组件提供的disabled
属性,会将上划,下划都禁用
页面底部的区域元素会被操作栏遮挡
![](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
因为设计和布局都是按照整个浏览器高度,但是在地址栏和操作栏的影响下 一些定位在底部元素就无法看到了
容器高度无法自动适配浏览器视窗高度
![](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
某些浏览器行为(如上图),比如上划到底部后,继续上划,浏览器地址栏会消失,此时视窗大小会发生变化
如果不进行高度重新适配,transform:translateY的位置
还是依据之前的视窗高度,造成显示错位
原生局部滚动会和组件干架
![1ec411d0488d50c487bf33676fd73e25 (2).gif](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
swipe时会导致内容区域元素层级变化
![](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
如何解决
api无法满足业务需求
基本思路:克隆一份源码到本地,对源码进行扩展
扩展属性
![image.png](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
在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 () { 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](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
这种方法会导致高度瞬间消失,使得页面会瞬间白屏下(如下图)
![d8e8940fb2d62a28d82d553fb257d969 (1).gif](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
所以,看到这,笔者开始思考,会不会直接修改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'}}/> </>
|
- 为什么不能使用
currentIndex < 2?<SwipeableViews/>:<CPage>
这种形式?
答:这种方式会导致组件的重新渲染,滑动位置的状态会丢失
- 为什么还要保留
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
| const onTouchDownToLastPage = () => { setCurrentIndex(1) }
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 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时会导致内容区域元素层级变化
解决方法
![](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
其他开发问题
![image.png](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
底部引导提示在滑动时需消失,但是当手指滑动到该图标上,会阻止上划
原因分析
可能是因为滑动时,将该提示display:none
处理,导致图层之间的touch事件不连续
解决方法
display:none
改为opacity:0
注意
考虑一下会不会遮挡后面图层的元素
结语
前端开发就是逆天而行,共勉。