移動端滾動穿透與滾動溢出解決方案
滾動穿透

問題描述
在移動端 WEB 開發(fā)的時候(小程序也雷同),如上錄屏所示,如果頁面超過一屏高度出現(xiàn)滾動條時,在 fixed 定位的彈窗遮罩層上進(jìn)行滑動,它下面的內(nèi)容也會跟著一起滾動,看起來好像事件穿透到下面的DOM元素上一樣,我們姑且稱之為滾動穿透。
問題原因
能夠猜想是文檔(document)的滾動事件被觸發(fā)了,如果能禁用滾動事件就好辦了。
案例偽代碼
class="btn">點擊出現(xiàn)彈窗</div>
??div>
??<div?class="popup-body?popup-bottom">
????<div?class="header">我是標(biāo)題div>
????<div?class="content">
??????<div>0div>????????
??????<div>1div>
??????<div>...div>
????div>
??div>
</div>
.popup-mask?{
??background-color:?rgba(0,?0,?0,?0.5);
??position:?fixed;
??z-index:?998;
??top:?0;
??bottom:?0;
??left:?0;
??right:?0;
}
.popup-body?{
??padding:?0?50px?40px;
??background-color:?#fff;
??position:?fixed;
??z-index:?999;
}
? 解決方案A (touch-action)
默認(rèn)情況下,平移(滾動)和縮放手勢由瀏覽器專門處理,但是可以通過 CSS 特性 touch-action 來改變觸摸手勢的行為。摘取幾個 touch-action 的值如下。
值 描述 auto 啟用瀏覽器處理所有平移和縮放手勢。 none 禁用瀏覽器處理所有平移和縮放手勢。 manipulation 啟用平移和縮放手勢,但禁用其他非標(biāo)準(zhǔn)手勢,例如雙擊縮放。 pinch-zoom 啟用頁面的多指平移和縮放。
于是在 popup 元素上設(shè)置該屬性,禁用元素(及其不可滾動的后代)上的所有手勢就可以解決該問題了。
.popup?{
??touch-action:?none;
}
Note: [無障礙設(shè)計] 阻止頁面縮放可能會影響視力不佳的人閱讀和理解頁面內(nèi)容,不過小程序本身好像就不可以縮放!
? 解決方案B (event.preventDefault)
來自 W3C 的一個標(biāo)準(zhǔn)。

描述.jpg 大意是說,在 touchstart 和 touchmove 事件中調(diào)用 preventDefault 方法可以阻止任何關(guān)聯(lián)事件的默認(rèn)行為,包括鼠標(biāo)事件和滾動。
因此我們可以這樣處理。
Step 1、監(jiān)聽彈窗最外層元素(popup)的 touchmove 事件并阻止默認(rèn)行為來禁用所有滾動(包括彈窗內(nèi)部的滾動元素)。
Step 2、釋放彈窗內(nèi)的滾動元素,允許其滾動:同樣監(jiān)聽 touchmove 事件,但是阻止該滾動元素的冒泡行為(stopPropagation),使得在滾動的時候最外層元素(popup)無法接收到 touchmove 事件。
const?popup?=?document.querySelector('.popup')
const?scrollBox?=?document.querySelector('.content')
popup.addEventListener('touchmove',?(e)?=>?{
??//?Step?1:?阻止默認(rèn)事件
??e.preventDefault()
})
scrollBox.addEventListener('touchmove',?(e)?=>?{
??//?Step?2:?阻止冒泡
??e.stopPropagation()
})
滾動溢出

滾動溢出.gif 問題描述
如上錄屏所示,彈窗內(nèi)也含有滾動元素,在滾動元素滾到底部或頂部時,再往下或往上滾動,也會觸發(fā)頁面的滾動,這種現(xiàn)象稱之為滾動鏈(scroll chaining), 但是感覺滾動溢出(overscroll)這個名字更言辭達(dá)意。
? 解決方案A (overscroll-behavior)
overscroll-behavior 是 CSS 的一個特性,允許控制瀏覽器滾動到邊界的表現(xiàn),它有如下幾個值。
值 描述 auto 默認(rèn)效果,元素的滾動可以傳播到祖先元素。 contain 阻止?jié)L動鏈,滾動不會傳播到祖先元素,但是會顯示節(jié)點自身的局部效果。例如 Android 上過度滾動的發(fā)光效果或 iOS 上的橡皮筋效果。 none 與 contain 相同,但是會阻止自身的過度效果。
所以可以這樣解決問題:
.content?{
??overscroll-behavior:?none;
}
簡潔干凈高性能,不過 Safari 全系不支持,兼容性如下,有沒有感覺 Safari 就是現(xiàn)代版的 IE(偶然聽路人說的)!

? 解決方案B (event.preventDefault)
借用 event.preventDefault 的能力,當(dāng)組件滾動到底部或頂部時,通過調(diào)用 event.preventDefault 阻止所有滾動,從而頁面滾動也不會觸發(fā)了,而在滾動之間則不做處理。
let?initialPageY?=?0
scrollBox.addEventListener('touchstart',?(e)?=>?{
????initialPageY?=?e.changedTouches[0].pageY
})
scrollBox.addEventListener('touchmove',?(e)?=>?{
????const?deltaY?=?e.changedTouches[0].pageY?-?initialPageY
????
????//?禁止向上滾動溢出
????if?(e.cancelable?&&?deltaY?>?0?&&?scrollBox.scrollTop?<=?0)?{
????????e.preventDefault()
????}
????//?禁止向下滾動溢出
????if?(
????????e.cancelable?&&
????????deltaY?0?&&?
????????scrollBox.scrollTop?+?scrollBox.clientHeight?>=?scrollBox.scrollHeight
????)?{
????????e.preventDefault()
????}
})
解決方案完整 Demo
https://github.com/Barrior/cases/blob/main/overscroll.html#L107-L143
關(guān)于本文
作者:Barrior
https://segmentfault.com/a/1190000040675446
最后
歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認(rèn)真的解答喲!
回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽! 回復(fù)「閱讀」,每日刷刷高質(zhì)量好文! 如果這篇文章對你有幫助,「在看」是最大的支持 ?》》面試官也在看的算法資料《《
“在看和轉(zhuǎn)發(fā)”就是最大的支持
瀏覽
38
