React禁止页面滚动踩坑实践与方案梳理

最近在使用 React 技术栈重构一个单页应用,其中有个页面是实现城市选择功能,主要是根据城市的首字母来快速跳转到相应位置,比较相似原生 APP 中的电话联系人查找功能,页面如图
功能界面前端

主要问题

在上下滑动右侧 fixed 定位的元素时,页面会跟着一块儿滑动vue

滚动右侧整个页面跟着滚动

固然这个现象在开发过程当中应该会常常遇到,好比弹起 modal 框时,若是 modal框的内容高度小于框高度,滑动内容也会致使页面跟着滑动, 那么在 React 中像往常同样处理react

<div className="nonius"
  id="nonius"
  onTouchStart={this.sidebarTouchStart.bind(this)}
  onTouchMove={this.sidebarTouchMove.bind(this)}
  onTouchEnd={this.sidebarTouchEnd.bind(this)}
>

使用 React 提供的事件绑定机制,分别绑定三个 handler ,在 onTouchMove 事件中,我但愿经过 preventDefault 可以阻止父级元素的滚动ios

sidebarTouchMove(e) {
  e.preventDefault();
  ...
}

但实际的反馈却事与愿违,在调试中,我发现 Chrome 是有警告的,而且没有达到想要的效果
chorme 开发工具警告提示git

根据警告提示,找到的缘由是github

AddEventListenerOptions defaults passive to false. With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored)..
If the value is explicitly provided in the AddEventListenerOptions it will continue having the value specified by the page.
This is behind a flag starting in Chrome 54, and enabled by default in Chrome 56. See https://developers.google.com...

来源: https://www.chromestatus.com/...web

根据 chrome 的提示得知,是由于 Chrome 如今默认把经过在 document 上绑定的事件监听器 passive 属性(后面细说)默认置为 true,这样就会致使我设置的 e.preventDefault() 被忽视了。固然 Chrome 的这个作法是有道理,是为了提升页面滚动的性能,那么为了防止带来的反作用,官方考虑的很周到,给咱们提供了一个 CSS 属性专门用来解决这个问题chrome

#fixed-div {
  touch-action: none;
}
In rare cases this change can result in unintended scrolling. This is usually easily addressed by applying a  touch-action: nonestyle to the element where scrolling shouldn't occur.

https://developer.mozilla.org...浏览器

加上了这个属性,感受世界总算和平了,But!在 ios 系统上测试发现,这个属性 x 用没有,查了下 Can I Use
can i use 截图app

肯定无误,就是不支持,因此这个属性只在 Chrome 安卓等机型下是支持的,ios 这个就用不了,理想很丰满,显示很骨感。既然不兼容,那只能降级处理了,为了保证良好的功能体验,感受是还要从 passive 上作处理,说到 passive 根据 MDN文档:addEventListener 的介绍,为了提升页面滚动性能,大多浏览器都默认把 touchstart 和 touchmove 在文档元素上直接注册的这个事件监听器属性设置成 passive:true ,而经过 AddEventListener 注册的事件依然没有变化

既然如今默认将事件 passive 的属性默认设置为 true ,那我就显式设置为 false 好了,查遍 React 的文档,也没发现事件监听器能够支持配置这个属性的,在 github 上发现这个帖子 Support Passive Event Listeners #6436 目前看依然是 open 状态的,如今不肯定有没有支持这个属性

解决方案

既然这样,只能单独对 touchmove 经过 AddEventListener 方法去注册事件监听了

// 为元素添加事件监听   
document.getElementById('nonius').addEventListener("touchmove", (e) => {
  // 执行滚动回调
  this.sidebarTouchMove(e)
}, {
  passive: false //  禁止 passive 效果
})

加上这个方法后,this.sidebarTouchMove(e) 方法中的 e.preventDefault() 方法就能够正常使用了,并且没有警告提示,问题到此就算解决了

总结

总结下,这里的坑主要是 chrome 和 safari 平台的标准不统一致使的,新的标准出台,其它宿主环境不能很好的支持,固然 react 官方对这个属性的支持也比较慢,一样的前端 UI 框架 Vue 就处理的很棒
vue对passive属性支持的相关语法
不当心暴露了,我是个 Vue粉,233
ok,完 ~

巴拉巴拉传送门 -> 原文连接

相关文章
相关标签/搜索