發頁面上某個元素或者達到某個條件時,頁面彈出模態框的場景應該是很常見的了,特別是在屏幕較小的移動端,例以下面這種:javascript
對於這個效果,以前一直都沒怎麼在乎探究過,由於以爲應該沒什麼好弄的,直到,我接到了一個包含此效果的需求以後,我才知道什麼叫眼高手低,仍是太年輕。css
第一次嘗試這個效果的時候,我稍稍思考了一下,以爲給 body
加個 overflow:hidden;height: 100vh;
的樣式應該就能夠了,因此就這麼寫了。html
DOM
以下:java
// HTML
<div class="box"> <div class="box1"> </div> </div>
當彈出層出現時,給 body
設置樣式:node
document.body.style.height = '100%' document.body.style.overflow = 'hidden'
寫完後順便在個人幾個瀏覽器上試了一下,桌面瀏覽器模擬移動端的那種,效果槓槓滴,和我預想的同樣,沒毛病。chrome
but
,哪有這麼簡單的事情?瀏覽器
桌面瀏覽器是 ok
了,可是這個項目的主要場景是 移動端,因此要經過真正的移動端瀏覽器才行,桌面瀏覽器模擬的可不算數,結果移動端瀏覽器中,除了 移動chrome
以外,全跪了,給 body
加上那兩個樣式和沒加的效果是同樣的,背景層改怎麼滾動還怎麼滾動。緩存
百思不得其解之下,只好跑到網上找了一圈,結果然讓我又找到了一個答案,說是僅僅給 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 } })
若是背景層是可見的呢,只要用戶不瞎,確定能看到頁面發生跳動了啊。
看來只經過 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
的時候,判斷一下有沒有過界,若是過界了反彈回來就行