常見場景
在許多填寫表單的頁面中,都會彈出一個選擇器,讓你在彈窗中選擇項目。有時,彈窗自己容納不下內容,須要讓它不斷滾動來展現,但由於事件是冒泡的,有時就會形成底部(body 的Z-index確定是在下面的 )的內容也在滾動。這內外一塊兒滾,就有點尷尬了。vue
舉例,下面兩張圖裏,都有相似的問題。那麼怎麼解決呢?ios
從event. target入手
一種思路就是從事件入手,若是事件對象不是彈窗的話,就讓它什麼都不作。git
(其實這裏有個疑惑,爲何不能經過阻止冒泡來阻止外層的body滾動呢?換了好幾個事件類型都不行,無論是scroll仍是touchmove,很奇怪。
實踐發現,touchmove事件的確不會冒泡了,但body仍然在滾動。。。那我不就不知道事件實現的機制究竟是什麼了,並且用瀏覽器模擬的移動端,也不知道是否是模擬有誤。。。
總之,event.stopPropagation()
方法在這裏不是很管用。。。
)。github
好比圖一中的每一欄滾動條,classname爲listcol,而彈窗爲.listcol,那麼能夠這麼作:web
$('.box--listcol').on('touchmove', function(event) { if(event.target.className.indexOf('listcol') === -1){ event.preventDefault() } }); // 忽略奇怪的縮進,直接從代碼裏粘貼出來的
仍是直接幹body吧
從event入手雖好,但那樣一來,每個彈窗都要單獨綁一個事件,並且!!!很噁心的是,若是有個反人類的操做,好比內容明明無需滾動,卻綁了事件,就仍然會在滾內容的時候帶動外層滾動。。。。(此時內容是不會滾的,由於高度足夠容納了)。chrome
可能表述的不是很清晰,圖二中,滾動區域的classname 爲 jobscrollcontainer, 彈窗自己爲box–job。瀏覽器
$('.box--job').on('touchmove',function(e){ if(!$(e.target).parents().filter('.jobscrollcontainer').length){ e.preventDefault() } })
正常操做的時候是能夠的。但若是不能滾動硬滾,那就抓瞎了。markdown
因此,仍是要從根子抓起,好比,就固定body不讓它滾!app
function toggleBody(isPin){ if(isPin){ document.body.style.height = '100vh' document.body.style['overflow-y'] = 'hidden' } else{ document.body.style.height = 'unset' document.body.style['overflow-y'] = 'auto' } } toggleBody(1) //在跳出彈窗的時候 toggleBody(0) //彈窗消失的時候
超長的頁面怎麼辦呢(2018-11更新)
上面直接限制body當然有效,但若是一個頁面很長很長,超出了100vh,而我正好滾到中間時彈出彈窗。此時若直接限制body的overflow: hidden
則會讓頁面一下彈到頂部,顯然不是好的作法。那麼,又該怎麼作呢?
對移動端,能夠引入touch-action
,限制爲none
,在彈窗消失時再變回auto
。但ios的safari上不支持該屬性(能夠去caniuse上查查,起碼2018.11的時候還不支持)。若是咱們的app在ios上用的是safari內核,就起不到效果了。
這時候,就須要結合event.preventDefault
屬性來用了。注意在綁定addEventListener
的時候,須要多傳一個options
,強調這個事件不是passive
的,不然谷歌等新版瀏覽器會報錯。同時最好也指定capture: true
,這樣能夠早點禁止該事件。
報錯是Unable to preventDefault inside passive event listener due to target being treated as passive.
。這是由於谷歌從chrome51以後引入的新優化。事實上,谷歌建議通常狀況下,將 passive 標誌添加到每一個沒有調用 preventDefault() 的 wheel、mousewheel、touchstart 和 touchmove 事件偵聽器。可是,對於這種禁止了默認事件的eventListener
,在這種狀況下,反而是要強調它不是消極監聽的。由於滾動都不能滾了,無所謂什麼優化了。
代碼以下(vue版本的):
watch: { show(v) { this.toggleContainerTouchAction(v) if (v) { document.body.addEventListener('touchmove', this.stopTouch, { passive: false, capture: true }) } else { document.body.removeEventListener('touchmove', this.stopTouch, { capture: true }) } }, }, methods: { toggleContainerTouchAction(v) { const container = document.querySelector('.container') if (!container) { return } container.style['touch-action'] = v ? 'none' : 'auto' }, stopTouch(e) { e.preventDefault() },
conclusion
直接鎖定body在移動端和web都適用,但若是是從事件入手的話,就要根據須要選擇究竟是用touch
仍是 mousewheel
了。
酌情使用吧,若是一個頁面中有不少不一樣的彈窗,那麼對每個彈窗的蹦出和消失都加上toggleBody事件也是挺煩的。。。也要考慮到用什麼工具作的頁面,若是是框架的話,在組件裏綁會相對簡潔一些。。。