移动端滚动穿透解决方案

8,266 阅读2分钟

移动端开发时经常会碰到有全屏遮罩层的弹窗滚动穿透问题,网上大部分的解决方案是弹窗出现时给body绝对定位或者高度设置为100%,并隐藏超出部分。

但是这些解决方法,都存在这样的问题:当页面有滚动时,弹出弹窗,页面会滚动至顶部。特别是当页面有其他根据document文档滚动距离而绝对定位元素时,会导致页面元素混乱。

我经过查询相关文档和实践,发现可通过监听touchmove和touchstart事件,来达到背景不可滚动而弹出窗可滚动的目的。可看此demo,建议在移动设备上查看。

下面详细讲解一下实现方法:

阻止弹窗的touchmove默认事件

当阻止了整个弹窗的touchmove默认事件后,背景将再也不会滚动。

var node = document.querySelector('.forbid-scroll-wrap');
node.addEventListener('touchmove', function (e) {
    e.preventDefault();
}, false);

但是这样会导致弹窗内部也无法滚动。所以需要判断触发touchmove事件的元素是否为可滚动区域,代码优化如下:

var node1 = document.querySelector('.can-scroll-wrap'),
node2 = document.querySelector('.forbid-scroll-wrap');
node2.addEventListener('touchmove', function (e) {
    var target = e.target;
    if ($(target).parents(canScrollWrap).length === 0 && $(target) != node1) {
        e.preventDefault();
    }
}, false);

允许弹窗内部滚动

以上只是解决了一个问题:滑动弹窗其他地方背景页面确实未跟随滚动,但是没去弹窗可滚动区域到底部或顶部后,再滑动,背景页面仍可跟随滚动。

所以仍需要对可滚动区域的滑动事件做监听:若向上滑动时,已到底部,或向下滑动时已到顶部,则阻止滑动事件。代码如下:

node1.scrollTop = 0;
var offsetHeight = node1.offsetHeight,
    scrollHeight = node1.scrollHeight;

node2.addEventListener('touchmove', function (e) {
    var target = e.target;
    if ($(target).parents(canScrollWrap).length === 0 && $(target) != node1) {
        e.preventDefault();
    }
}, false);

node1.addEventListener('touchmove', function (e) {
    var changedTouches = e.changedTouches, canMove = false;
    var scrollTop = this.scrollTop;
    if (changedTouches.length > 0) {
        var touch = changedTouches[0] || {};
        var moveY = touch.clientY;
        if (moveY > startY && scrollTop <= 0) {
            canMove = false;
        } else if (moveY < startY && scrollTop + offsetHeight >= scrollHeight) {
            canMove = false;
        }else{
            canMove = true;
        }
        if (!canMove) {
            e.preventDefault();
        }
    }

}, false);

node1.addEventListener('touchstart', function (e) {
    var targetTouches = e.targetTouches || [];
    if (targetTouches.length > 0) {
        var touch = targetTouches[0] || {};
        startY = touch.clientY;
    }
}, false)

解除禁止

弹窗关闭后,可解除所有禁止

node1.addEventListener('touchstart',null,false);
node1.addEventListener('touchmove',null,false);
node2.addEventListener('touchmove',null,false);

点此查看源码