移動端彈出層背景禁止滾動


發頁面上某個元素或者達到某個條件時,頁面彈出模態框的場景應該是很常見的了,特別是在屏幕較小的移動端,例如下面這種:

這里寫圖片描述

對於這個效果,之前一直都沒怎么在意探究過,因為覺得應該沒什么好弄的,直到,我接到了一個包含此效果的需求之后,我才知道什么叫眼高手低,還是太年輕。


body: overflow:hidden

第一次嘗試這個效果的時候,我稍稍思考了一下,覺得給 body加個 overflow:hidden;height: 100vh;的樣式應該就可以了,所以就這么寫了。

DOM如下:

// HTML
<div class="box"> <div class="box1"> </div> </div>

當彈出層出現時,給 body設置樣式:

document.body.style.height = '100%' document.body.style.overflow = 'hidden'

寫完后順便在我的幾個瀏覽器上試了一下,桌面瀏覽器模擬移動端的那種,效果杠杠滴,和我預想的一樣,沒毛病。

but,哪有這么簡單的事情?

桌面瀏覽器是 ok了,但是這個項目的主要場景是 移動端,所以要通過真正的移動端瀏覽器才行,桌面瀏覽器模擬的可不算數,結果移動端瀏覽器中,除了 移動chrome之外,全跪了,給 body加上那兩個樣式和沒加的效果是一樣的,背景層改怎么滾動還怎么滾動。


body+html: overflow:hidden

百思不得其解之下,只好跑到網上找了一圈,結果真讓我又找到了一個答案,說是僅僅給 body設置 overflow是不行的,還必須同時給 html節點也加上這個樣式才行,於是就試了一下。

document.documentElement.style.height = '100%' document.documentElement.style.overflow = 'hidden' document.body.style.height = '100%' document.body.style.overflow = 'hidden'

桌面瀏覽器模擬移動端測試通過,之前跪了的移動瀏覽器現在也都 ok了,給這兩個元素加上上述樣式后,彈出層背景 body確定是不會滾動了。

but,又出現了另外一個問題,當將頁面往下滾動一段距離,也就是說 document.body.scrollTop 大於 0時,再顯示彈出層,增加上述四行代碼時,頁面自動滾到了最頂部,也就是說瀏覽器像是自動執行了這一行代碼 document.body.scrollTop=0

仔細想想也是,之前頁面是超出一個屏幕高度的,所以可以滾動,但是現在你把頁面高度設為一個屏幕高度 100%,並且 overflow:hidden,那么根據 overflow:hidden的特性,瀏覽器肯定是要從頁面的頭部開始截取一個屏幕的高度,剩下的再 hidden

如果彈出層時,背景是完全看不到的,一片漆黑,也就是類似 rgba(0,0,0,1),而不是半透明 rgba(0,0,0,.6),那么實際無傷大雅,也就是一行代碼的事情。

在彈出層彈出之前,先保存此事頁面的 scrollTop,然后在彈出層關閉的時候,再將頁面的 scrollTop設定到之前保存的那個位置,所以這樣最起碼看起來背景是沒變的,就像下面這樣:

var box1 = $('.box1') var body= document.body var scrolltop body.addEventListener('click', function() { if (box1.className.indexOf('hidden') !== -1) { // 保存頁面滾動到的位置 scrolltop = document.body.scrollTop body.style.height = '100%' body.style.overflow = 'hidden' document.documentElement.style.height = '100%' document.documentElement.style.overflow = 'hidden' // 顯示彈出層 box1.className ='box1 show' } else { // 隱藏彈出層 box1.className ='box1 hidden' body.style.height = 'auto' body.style.overflow = 'visible' document.documentElement.style.height = 'auto' document.documentElement.style.overflow = 'visible' // 恢復頁面滾動到的位置 document.body.scrollTop = scrolltop } })

如果背景層是可見的呢,只要用戶不瞎,肯定能看到頁面發生跳動了啊。


JS控制

  • 區別對待

看來只通過 css來完成這個效果是有些難度了,於是將主意打到了 js上,如下:

box1.addEventListener('touchmove', function(e){ e.preventDefault() })

 

直接禁止彈出層的掉滾動事件,因為彈出層是滿屏覆蓋在 頁面上的,而且這個事件也沒有 點透,所以確實是達到了禁止背景 頁面滾動的效果。

but,背景元素的滾動是禁止掉了,但這種禁止幾乎是把頁面上所有元素的滾動事件都禁止掉了,如果在彈出層元素 box1中存在可以滾動的元素,那么同樣也會被禁止滾動,這可不是我們想要的,所以必須要把彈出層內的元素排除在外才行。

彈出層內可滾動區域包括可滾動的頂級元素,以及此元素下所有的子元素,所以只要判斷當前正在滾動的區域是此區域內的元素,則允許滾動,所以這里需要判斷當前 touch的元素是不是彈出層內可以滾動的元素,以及是不是其子元素,判斷是否為其子元素只需要一個循環遞歸即可,例如以下代碼:

function getRecursiveEle(ele, parentClassName) { if (ele.className.indexOf(parentClassName) !== -1) { return ele } else { if (ele.nodeName.toLowerCase() === 'body') { return null } ele = ele.parentNode return getRecursiveEle(ele, parentClassName) } }

調用此函數,傳入 e.target以及彈出層可以滾動的元素類名即可,返 回 true,則表明是可以滾動的元素。

but,雖然你直接禁止了彈出層可滾動元素其外的元素滾動,然而同時又允許彈出層內可滾動元素滾動,那么當將滾動元素滾動到頭的時候,背景還是會滾動。

所以,還需要加一個判斷,當滾動元素滾動到頭或者尾部的時候,再禁止所有元素滾動,這里的滾動到頭包括兩種情況,到頭和到尾。

// 滾動到頭的情況,其中touchstartY 為開始滾動時接觸點的 `pageY`
if(modal.scrollTop <= 0) { e.targetTouches[0].pageY > touchstartY && e.preventDefault() }
// 滾動到尾的情況,其中touchstartY 為開始滾動時接觸點的 `pageY`,itemH為可滾動元素框內部的子元素總高度,+2是因為邊界問題 (itemH - modal.offsetHeight < modal.scrollTop + 2) && (e.targetTouches[0].pageY < touchstartY) && e.preventDefault()

這樣的話,問題大體上解決了,但還有點小問題,在有的瀏覽器上,當可滾動元素滾動到頭的時候,背景依舊還是會稍微滾動一點距離,不太完美。

  • 另想他法

依舊讓覆蓋整個屏幕的彈出層禁止滾動,彈出層內部可滾動元素的滾動通過 js來控制,例如使用 translate控制上下滾動距離。

// touchstartY 為touchstart事件發生時的 e.targetTouches[0].pageY var translateEndY = 0, translateEndYTemp = 0 box1.addEventListener('touchmove', function(e) { // 禁止默認滾動 e.preventDefault() translateEndYTemp = e.targetTouches[0].pageY-touchstartY + translateEndY // 通過改變 translate來滾動元素 $('.item').style.transform = 'translate(0, '+translateEndYTemp+'px)' }) // 緩存下每次滾動過的距離 box1.addEventListener('touchend', function(e) { translateEndY = translateEndYTemp })

嗯,這樣就差不多了,不過因為滾動時通過 translate實現的,所以滾動元素是不受父元素約束的,也就是說滾動元素會滾過界,這個很好解決,在 touchend的時候,判斷一下有沒有過界,如果過界了反彈回來就行


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM