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,完 ~

巴拉巴拉傳送門 -> 原文連接

相關文章
相關標籤/搜索