滾動穿透問題探索

在移動端開發中,模態(Modal)彈窗能夠說是很是之常見了,做爲正在看這篇文章的你,可能寫過基於Vue、React或者小程序的統一彈窗Modal組件;可能處理過定寬高或者不定寬高的狀況等,看起來一個小小的模態框其實蘊藏了大大的知識點,本文就帶你去探索模態框中的滾動穿透(scroll chaining)問題。前端

背景

俗話說,產品有三寶:彈窗、浮層加引導,足以見彈窗在產品同窗心目中的地位。對任意一個剛入門的前端同窗來講,實現一個模態框基本均可以達到信手拈來的地步,可是,當模態框裏邊的內容滾動起來之後,就會出現各類各樣的讓人摸不着頭腦的問題,其中,最出名的想必就是滾動穿透。小程序

那麼,什麼是滾動穿透呢?滾動穿透即:移動端彈出fixed彈窗的話,在彈窗上滑動會致使下層的頁面跟着滾動,這個叫 「滾動穿透」。首先,滾動穿透的現象是出如今移動端的,PC上不存在此種狀況。瞭解了什麼是滾動穿透以及它出現的場景,那麼,接下來就直接進入正題。bash

H5的滾動穿透

首先咱們須要明白,滾動穿透出現的前提是模態框裏的內容高度大於元素自己的高度,即滾動時出現。測試

解決方案一

當咱們在模態框內部滾動的時候,底部內容也會跟着滾動,那麼若是禁止掉遮罩層的滾動事件,底部內容也就天然不會滾動了。(本文假設模態框和遮罩層都處在同一級),廢話很少說,翠花,上代碼。ui

<div class="popup" v-if="showPopDialog" @click="closePopDialog" @touchmove="touchForbidden"></div>

touchForbidden(e){
    e.preventDefault()
},
複製代碼

按照上面這樣操做之後,模態框裏邊的內容是正常滾動的,觸摸背景也不會隨着滾動,這麼看來,好像咱們的問題已經成功的解決了,可是實際狀況並無這麼樂觀,通過反覆測試,發現當在模態框的頂部或者底部邊緣隨意滑動時,仍然能觸發底部內容的滑動。this

既然已經發現了觸發的規律,那順着規律去解決問題就行了嘛,順藤摸瓜,當打開模態框時,咱們能夠進行邊緣檢測,當用戶手賤滑動到模態框頂部或者模態框最底部時,咱們就禁止滑動,這樣應該就能夠解決上述問題了,翠花,繼續上代碼。spa

<div class="content" v-if="showPopDialog" @touchmove="touchMove" id="canmove" @touchstart="touchStart">
      我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的
</div>
複製代碼

咱們在模態框上首先註冊touchstart事件,獲得用戶首次觸摸的y座標值3d

touchStart(e){
     this.firstY = e.targetTouches[0].clientY;
}
複製代碼

而後,當用戶在模態框滑動的時候,獲得其滑動過程當中的y座標值,當滑動過程當中觸點的clientY > stratY的時候代表滑動方向向下,在用戶往下滑的過程當中,滑動距離爲0則代表爲用戶的滑動位置爲模態框的最頂部,此時就獲得了用戶滑動到模態框頂部的邊緣條件。code

一樣的道理,當clientY < startY時代表滑動方向爲向上,scrollTop + offsetHeight >= scrollHeight則代表已經滑動到了模態框最底部。cdn

touchMove(e){
    let target = document.getElementById('canmove')
    let offsetHeight = target.offsetHeight,
      scrollHeight = target.scrollHeight;
    let changedTouches = e.changedTouches;
    let scrollTop = target.scrollTop;
    if (changedTouches.length > 0) {
      let touch = changedTouches[0] || {};
      let moveY = touch.clientY;
      if (moveY > this.firstY && scrollTop === 0) {
        // 滑動到彈窗頂部臨界條件
        e.preventDefault()
        return false
      } else if (moveY < this.firstY && scrollTop + offsetHeight >= scrollHeight) {
        // 滑動到底部臨界條件
        e.preventDefault()
        return false
      }
    }
}
複製代碼

ok,接下來進行測試,再沒有發現任何問題,完美解決,方案一Done。

在解決有關滑動問題的過程當中,總會出現clientHeight、offsetHeight、scrollHeight、scrollTop等不是親兄弟而勝似親兄弟的一系列葫蘆娃,有必要來複習一波關於他們的基礎知識。

  • offsetHeight和元素的滾動沒有任何關係,它只表明元素的高度,包括border、padding、水平滾動條但不包括margin
  • clientHeight相似於offsetHeight,不一樣的是它只包括padding不包括border、水平滾動條以及margin(引用互聯網例圖)

  • scrollHeight只有當元素出現滾動時纔有意義,元素不滾動時候,scrollHeight==clientHeight,當元素滾動時,scrollHeight的值爲scrollTop + clientHeight

  • scrollTop爲元素滾動時隱藏的部分
  • offsetTop依然和滾動沒有關係,表明當前元素頂部距離最近父元素頂部的距離

在咱們處理滾動過程當中,基本都會使用到上述變量,圖文結合的方式相信你們更容易理解清楚它們之間的關係。

解決方案二

接下來咱們來研究方案二,在打開彈窗的時候,能夠經過爲底部內容區域增長動態class來阻止底部內容滑動,可是這樣致使的問題是會丟失底部內容區域的滾動距離,沒事,不慌!在丟失滾動距離以前咱們能夠將它記錄下來,關閉彈窗的時候再將以前記錄的scrollTop設置回去不就ok了嗎?理論可行,接下來咱們就正式開始coding...

當打開模態框的時候,須要爲底部內容區域增長一個動態class來阻止滑動,以下:

.forbidden_scroll{
    position: fixed;
    height: 100%;
  }
複製代碼

打開模態框以前,咱們須要記錄當前的底部內容的scrollTop值,

touchmove(e){
    this.scrollTop = document.getElementById('scrollElement').scrollTop
}
複製代碼

當關閉彈窗的時候,首先移除掉剛纔添加的動態class,再將scrollTop設置回去便可。

closePopDialog(){
    this.showPopDialog = false
    this.top = -this.scrollTop
    this.showStyle = false
},
複製代碼

整個模板部分代碼以下:

<div class="main">
    <div :class="showStyle ? 'forbidden_open' : 'article'" id="scrollElement" :style="{'margin-top': top + 'px'}" @touchmove="touchmove">
      <div class="block_red">
        <div class="block_click" @click="openPopDialog">Click Me</div>
      </div>
      <div class="block_yellow"></div>
      <div class="block_green"></div>
    </div>
    <div class="popup" v-if="showPopDialog" @click="closePopDialog" @touchmove="touchForbidden">
    </div>
    <div class="content" v-if="showPopDialog">
      我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的
      我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的
      我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的
      我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的
      我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的我是來進行測試的
    </div>
  </div>
複製代碼

通過測試,發現此方案也能夠解決滾動穿透的問題,可是,手賤的我又一次嘗試了滑動到彈窗邊緣的狀況,然鵝,仍是觸發了底部內容的滑動,沒辦法,依然須要像方案一同樣進行邊緣檢測,才能完美解決問題。

上述討論的2種解決方案都是基於H5的,小程序中對於滾動穿透仍然沒有完美解決方案,根本緣由在於小程序中沒有DOM的概念,不能像H5中能夠任意操做DOM。

其餘解決方案缺陷
  • @touchmove.prevent 沒法解決滑動到邊緣又觸發底部滑動的問題
  • overscroll-behavior依然沒法解決上述問題,而且兼容性成迷caniuse.com/#search=ove…

上述討論都是基於H5的,在小程序中,可使用scroll-view結合上述方案解決此類問題,可是仍然會存在滑動到邊緣觸發底部滾動問題,目前沒有完美的解決方案。

相關文章
相關標籤/搜索