手机网站表层div滑动,导致底层body滑动(touchmove的阻止)

12774次浏览

案例描述

body很长,可以滑动,body头部有一个模拟下拉的选择框。下拉选择有滚动轴,如下图。

enter image description here

我给body一个overflow:hidden和高度是没有用的。手机网站上背景还是可以滑动,然后我给body一个touchmove的preventdefault()阻止事件,body滑动阻止了,PC上面是可以了,但是手机上面滑动div还是会导致底部body的滑动,我给div 一个阻止冒泡的事件stopPropagation(),手机网站上面还是不可以。

关于preventdefault和stopPropagation,后面有时间会讲解其区别。

解决方案

我经过反复测试,发现滚动轴滚到底部的时候,会触发body的滑动,那么我就在事件滚到底部的时候对表层的div做一个touchmove的阻止。到达滚动轴底部,向下滑动,阻止事件,向上滑动,开启事件。为此就要判断touchmove的方向了。

var startX ,startY;
$("body").on("touchstart", function(e) {
    e.preventDefault();
    startX = e.originalEvent.changedTouches[0].pageX,
    startY = e.originalEvent.changedTouches[0].pageY;
});
$("body").on("touchmove", function(e) {
    e.preventDefault();
    var moveEndX = e.originalEvent.changedTouches[0].pageX,
    moveEndY = e.originalEvent.changedTouches[0].pageY,
    X = moveEndX - startX,
    Y = moveEndY - startY;

    if ( Math.abs(X) > Math.abs(Y) && X > 0 ) {
        alert("left 2 right");
    }
    else if ( Math.abs(X) > Math.abs(Y) && X < 0 ) {
        alert("right 2 left");
    }
    else if ( Math.abs(Y) > Math.abs(X) && Y > 0) {
        alert("top 2 bottom");
    }
    else if ( Math.abs(Y) > Math.abs(X) && Y < 0 ) {
        alert("bottom 2 top");
    }
    else{
        alert("just touch");
    }
});

上面的方法是判断touchmove的滑动方向。

除了上面方法判断手机端手机滑动方向,我这里再介绍一个方案,就是封装一个角度函数,通过角度函数来判断也还不错!我这里仅仅把这种方式实现上滑下滑左滑右滑列举一下!

var startx, starty;
    //获得角度
    function getAngle(angx, angy) {
        return Math.atan2(angy, angx) * 180 / Math.PI;
    };

    //根据起点终点返回方向 1向上 2向下 3向左 4向右 0未滑动
    function getDirection(startx, starty, endx, endy) {
        var angx = endx - startx;
        var angy = endy - starty;
        var result = 0;

        //如果滑动距离太短
        if (Math.abs(angx) < 2 && Math.abs(angy) < 2) {
            return result;
        }

        var angle = getAngle(angx, angy);
        if (angle >= -135 && angle <= -45) {
            result = 1;
        } else if (angle > 45 && angle < 135) {
            result = 2;
        } else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) {
            result = 3;
        } else if (angle >= -45 && angle <= 45) {
            result = 4;
        }

        return result;
    }
    //手指接触屏幕
    document.addEventListener("touchstart", function(e) {
        startx = e.touches[0].pageX;
        starty = e.touches[0].pageY;
    }, false);
    //手指离开屏幕
    document.addEventListener("touchend", function(e) {
        var endx, endy;
        endx = e.changedTouches[0].pageX;
        endy = e.changedTouches[0].pageY;
        var direction = getDirection(startx, starty, endx, endy);
        switch (direction) {
            case 0:
                alert("未滑动!");
                break;
            case 1:
                alert("向上!")
                break;
            case 2:
                alert("向下!")
                break;
            case 3:
                alert("向左!")
                break;
            case 4:
                alert("向右!")
                break;
            default:
        }
    }, false);

知道滑动方向如何判断,那么解决这个问题我们可以判断是否滑动到底部或者顶部,假如滑动到底部,再往下滑动,就阻止滑动,往上滑动,就开启滑动!滑动到顶部一个道理!总结代码如下:

      $('#haorooms底层背景').bind("touchmove", function (e) {
            e.preventDefault();
        });
        $(".滚动的父亲").bind("touchstart", function (events) {
            startY = events.originalEvent.changedTouches[0].pageY;
        });
        $(".滚动的父亲 ul").bind("touchmove", function (e) {
            var ulheight = $(this).height();
            var scrollTop = $(this).scrollTop();
            var scrollheight = $(this)[0].scrollHeight;
            if (ulheight + scrollTop + 20 >= scrollheight) { //滚到底部20px左右
                $(".滚动的父亲").bind("touchmove", function (event) {
                    moveEndY = event.originalEvent.changedTouches[0].pageY,
                            theY = moveEndY - startY;
                    if (Math.abs(theY) > Math.abs(theX) && theY > 0) { //用上面的abs()更加准确!这里是判断上滑还是下滑!可以用角度函数也可以用上面绝对值方式!
                        $(".滚动的父亲").unbind("touchmove");//滑动到底部再往上滑动,解除阻止!
                    }
                    if (Math.abs(theY) > Math.abs(theX) && theY < 0) {
                        event.preventDefault();//滑动到底部,再往下滑动,阻止滑动!
                    }
                })
            }
            if (scrollTop < 20) {//滚到顶部20px左右
                $(".滚动的父亲").bind("touchmove", function (event) {
                    moveEndY = event.originalEvent.changedTouches[0].pageY,
                            theY = moveEndY - startY;
                    if (Math.abs(theY) > Math.abs(theX) && theY > 0) {
                        event.preventDefault();
                    }
                    if (Math.abs(theY) > Math.abs(theX) && theY < 0) {
                        $(".滚动的父亲").unbind("touchmove");
                    }
                })
            }
        });

以上方法基本上能够阻止body的滚动,但是,有时候还是会有问题,期待更好的解决方案!

张鑫旭的一种解决办法

下面是张鑫旭的一个解决办法,这里我简单的借用一下!

CSS代码:

.noscroll,
.noscroll body {
    overflow: hidden;
}
.noscroll body {
    position: relative;
}

js代码:

$.smartScroll = function(container, selectorScrollable) {
    // 如果没有滚动容器选择器,或者已经绑定了滚动时间,忽略
    if (!selectorScrollable || container.data('isBindScroll')) {
        return;
    }

    // 是否是搓浏览器
    // 自己在这里添加判断和筛选
    var isSBBrowser;

    var data = {
        posY: 0,
        maxscroll: 0
    };

    // 事件处理
    container.on({
        touchstart: function (event) {
            var events = event.touches[0] || event;

            // 先求得是不是滚动元素或者滚动元素的子元素
            var elTarget = $(event.target);

            if (!elTarget.length) {
                return;    
            }

            var elScroll;

            // 获取标记的滚动元素,自身或子元素皆可
            if (elTarget.is(selectorScrollable)) {
                elScroll = elTarget;
            } else if ((elScroll = elTarget.parents(selectorScrollable)).length == 0) {
                elScroll = null;
            }

            if (!elScroll) {
                return;
            }

            // 当前滚动元素标记
            data.elScroll = elScroll;

            // 垂直位置标记
            data.posY = events.pageY;
            data.scrollY = elScroll.scrollTop();
            // 是否可以滚动
            data.maxscroll = elScroll[0].scrollHeight - elScroll[0].clientHeight;
        },
        touchmove: function () {
            // 如果不足于滚动,则禁止触发整个窗体元素的滚动
            if (data.maxscroll <= 0 || isSBBrowser) {
                // 禁止滚动
                event.preventDefault();
            }
            // 滚动元素
            var elScroll = data.elScroll;
            // 当前的滚动高度
            var scrollTop = elScroll.scrollTop();

            // 现在移动的垂直位置,用来判断是往上移动还是往下
            var events = event.touches[0] || event;
            // 移动距离
            var distanceY = events.pageY - data.posY;

            if (isSBBrowser) {
                elScroll.scrollTop(data.scrollY - distanceY);
                elScroll.trigger('scroll');
                return;
            }

            // 上下边缘检测
            if (distanceY > 0 && scrollTop == 0) {
                // 往上滑,并且到头
                // 禁止滚动的默认行为
                event.preventDefault();
                return;
            }

            // 下边缘检测
            if (distanceY < 0 && (scrollTop + 1 >= data.maxscroll)) {
                // 往下滑,并且到头
                // 禁止滚动的默认行为
                event.preventDefault();
                return;
            }
        },
        touchend: function () {
            data.maxscroll = 0;
        }    
    });

    // 防止多次重复绑定
    container.data('isBindScroll', true);
};

html如下:

<aside id="aside" class="aside">
    <i class="aside-overlay hideAside"></i>
    <div class="aside-content">
        <div class="module module-filter-list">
            <div class="module-main scrollable">
                <ul id="filters" class="sort-ul">
                    .......
                </ul>
            </div>
        </div>
    </div>
</aside>

使用:

$('#aside').addClass('active');
$.smartScroll($('#aside'), '.scrollable');
$('html').addClass('noscroll');

大家可以测试一下!

相关文章: