发页面上某个元素或者达到某个条件时,页面弹出模态框的场景应该是很常见的了,特别是在屏幕较小的移动端,例以下面这种: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
的时候,判断一下有没有过界,若是过界了反弹回来就行