react弹窗Modal滚动穿透踩坑及解决(可能是全网最详细了)

react弹窗Modal滚动穿透踩坑及解决(可能是全网最详细了)

最近工作时发现,产品总喜欢做弹窗,还要弹窗里的内容可以滑动。这要是偶尔提一次就算了,我们大不了把页面加个overflow:hidden,弹窗内加scroll呗。但是经常提这种需求,作为程序员可不能重复造轮子呀,所以最近花了一周,把网上的相关文章(看来看去都是抄的),提取出了一个hooks方法,顺便记录踩坑过程。

首先是body的overflow:hidden

单独设这个属性在安卓和pc上确实能做到防止背景滚动,但是在IOS上不行,原因是IOS中,如果html没有设置overflow,body会继承html的overflow,也就是overflow:visible
那就加上呗

html, body {
  overflow: hidden;
}

很可惜IOS还是不行,需要加上高度

html, body {
  height: 100vh;
  overflow: hidden;
}

但这样就会出现,弹窗出现时,背景页面会自己滚动到顶部。因为原body的overflow是visible,没有滚动条的,加了高度和hidden(overflow:hidden下,页面也有滚动条,只是不显示),body里出现了滚动条(滚动距离为0),所以页面置顶。

这个问题可以在弹窗出现前,将html和body的overflow设为auto,这样就不会置顶了。这对于新页面可能是个不错的解决方案,不过如果是旧页面的代码(那些没加overflow:auto的页面),改动可能会很大,而且没法封装。如果有大神用这个解法封装的,请评论教一下小弟,谢谢

event.preventDefault()

这个网上很多文章都说了,会将弹窗内的滚动也禁止掉,当然,这个也是可解的,并且我最后也是基于这个来封装的
先看看坑,既然不想内部被禁止,那就滚动内部的时候不冒泡呗,

maskRef.current.addEventListener('touchmove', e => {e.preventDefault()}, {passive: false} )
contentRef.current.addEventListenner('touchmove', e => {e.stopPropagation()}, {passive: false})

会发现,内部元素滚动到顶部或者底部,再次继续滚动,背景也会滚动了。
那就加上边界判断吧

let startY
contentRef.current.addEventListener('touchstart', e=> {
  startY = e.targetTouches[0].clientY
})
contentRef.current.addEventListenner('touchmove', e => {
// 不考虑横向滑动
  const dy = e.changedTouches[0].clientY - startY
  const offsetHeight = contentRef.current.offsetHeight
  const scrollHeight = contentRef.current.scrollHeight
  const scrollTop = contentRef.current.scrollTop
  if(dy > 0 && scrollTop === 0) {
    return e.preventDefault()
  }
  if(dy < 0 && scrollTop + offsetHeight >= scrollHgeight) {
    return e.preventDefault()
  }
  e.stopPropagation()
}, {passive: false})

看起来代码没啥问题了,如果按网上其他文章,这就是完美解法了,但是实际体验后发现,根本不够,稍微认真点测试就能发现,在边界情况,如底部,先按住上划再下划,背景就会移动了,顶部也有这种情况。这可就难搞了,因为代码逻辑上已经找不到问题了,但是在浏览器的表现上出现了问题,这种情况目前发现在pc,安卓。IOS我只有一个,暂时没出现这个情况。

滚动穿透


body Position:fixed

这是这段时间网上查到文章说的最优解,我一开始先排除了这个解法,因为在仓库的老页面中,有使用window.scrollTo这个方法,将body改为position:fixed,直接导致window.scrollTo无法使用,并且看到网上的文章提到,在页面使用Swiper时会有天坑,IOS的输入框偏移(这两点我并未尝试,因为我没打算用这种方式),这里把网上的代码介绍一下

.bodyNoScroll {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  overflow: hidden;
}

// 解决页面置顶问题
// 弹窗出现调用
let scrollTop = 0
const modalVisible = () => {
  scrollTop = document.body.scrollTop
  document.body.className = 'bodyNoScroll'
  document.body.style.top = scrollTop
}

// 弹窗隐藏调用,还原body的滚动条
const modalHidden = () => {
  document.body.className = ''
  document.body.scrollTop = scrollTop
}

最终封装的结果

以上三种都有各自的小问题,愁死我了,刚好找到了旭神的文章,里面主要是结合了方法2和方法1
在event.preventDefault() 中,ISO没试出问题,安卓出现了问题, body overflow:hidden中,IOS出现问题,安卓和pc没试出问题。
这不巧了,结合一下不就好了。

因为最近离职了,原代码没法找到,如果有需要的话我再重新写一份

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值