林秀栋的技术博客

滚动穿透

滚动穿透:移动端弹出fixed弹窗的话,在弹窗上滑动会导致下层的页面跟着滚动。

解决方案一

当我们在模态框内部滚动的时候,底部内容也会跟着滚动,那么如果禁止掉遮罩层的滚动事件,底部内容也就自然不会滚动了。(本文假设模态框和遮罩层 都处在同一级)

<div
  class="popup"
  v-if="showPopDialog" 
  @click="closePopDialog"
  @touchmove="touchForbidden"
></div>
touchForbidden(e){
  e.preventDefault()
}

按照上面这样操作以后,模态框里边的内容是正常滚动的,触摸背景也不会随着滚动,这么看来,好像我们的问题已经成功的解决了,但是实际情况并没有 这么乐观,经过反复测试,发现当在模态框的顶部或者底部边缘随意滑动时,仍然能触发底部内容的滑动。

当打开模态框时,我们可以进行边缘检测,当用户手贱滑动到模态框顶部或者模 态框最底部时,我们就禁止滑动,这样应该就可以解决上述问题了,继续看代码:

<div
  class="content"
  v-if="showPopDialog" 
  @touchmove="touchMove"
  id="canmove"
  @touchstart="touchStart"
>
  我是来进行测试的我是来进行测试的我是来进行测试的我是来进行测试的
  我是来进行测试的我是来进行测试的我是来进行测试的我是来进行测试的
</div>

我们在模态框上首先注册touchstart事件,得到用户首次触摸的y坐标值

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

然后,当用户在模态框滑动的时候,得到其滑动过程中的y坐标值,当滑动过程中触点的clientY > stratY的时候表明滑动方向向下,在用户往下滑的过程中,滑动距离为0则表明为用户的滑动位置为模态框的最顶部,此时就得到了用户滑动到模态框顶部的边缘条件。

同样的道理,当clientY < startY时表明滑动方向为向上,scrollTop + offsetHeight >= scrollHeight则表明已经滑动到了模态框最底部。

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
    }
  }
}

解决方案二

在打开弹窗的时候,可以通过为底部内容区域增加动态class来阻止底部内容滑动,但是这样导致的问题是会丢失底部内容区域的滚动距离,可以在丢失滚动距离之前记录下来,关闭弹窗的时候再将之前记录的scrollTop设置回去

当打开模态框的时候,需要为底部内容区域增加一个动态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
}

经过测试,发现此方案也可以解决滚动穿透的问题,但是,尝试滑动到弹窗边缘的情况,还是触发了底部内容的滑动,没办法,依然需要像方案一一样进行边缘检测,才能完美解决问题。

上述讨论的2种解决方案都是基于H5的,小程序中对于滚动穿透仍然没有完美解决方案,根本原因在于小程序中不能像H5那样可以任意操作DOM。