iOS Safari - 如何禁用超出滚动但允许可滚动的div正常滚动?

111

我正在开发一个基于iPad的Web应用程序,需要防止用户过度滚动以减少网页的感觉。目前,我正在使用以下代码来冻结视口并禁用过度滚动:

document.body.addEventListener('touchmove',function(e){
      e.preventDefault();
  });

这段代码很好地禁用了过度滚动,但我的应用程序有几个可滚动的

上面的代码防止它们滚动

我只针对iOS 5及以上版本进行操作,因此避免了像iScroll这样的hacky解决方案。相反,我在我的可滚动

上使用了以下CSS:

.scrollable {
    -webkit-overflow-scrolling: touch;
    overflow-y:auto;
}
这段代码可以在没有文档overscroll脚本的情况下工作,但无法解决div滚动问题。 没有使用jQuery插件,是否有办法使用overscroll修复但免除我的$('.scrollable') divs? 编辑: 我找到了一个不错的解决方案:
 // Disable overscroll / viewport moving on everything but scrollable divs
 $('body').on('touchmove', function (e) {
         if (!$('.scrollable').has($(e.target)).length) e.preventDefault();
 });
当您滚动到 div 的开头或结尾时,视口仍然会移动。我想找到一种方法来禁用它。

尝试了你的最终版本,但仍然无法工作。 - Santiago Rebella
我能够通过显式捕获可滚动div的父级上的滚动事件并阻止其实际滚动,从而使视口在滚动到div末尾时不会移动。如果您正在使用jquery mobile,则在页面级别执行此操作是有意义的,如下所示:$('div[data-role="page"]').on('scroll', function(e) {e.preventDefault(); }); - Christopher Johnson
https://github.com/lazd/iNoBounce 奇妙地发挥作用。 - David Underhill
我找到了这个脚本,可以解决这个问题! :) https://github.com/lazd/iNoBounce - Jan Šafránek
如果在你的帖子上面已经有人7个月前发布了相同的链接,为什么你还要再次发布链接呢? - Denny
15个回答

84

这解决了当您滚动到DIV的开头或结尾时的问题。

var selScrollable = '.scrollable';
// Uses document because document will be topmost level in bubbling
$(document).on('touchmove',function(e){
  e.preventDefault();
});
// Uses body because jQuery on events are called off of the element they are
// added to, so bubbling would not work if we used document instead.
$('body').on('touchstart', selScrollable, function(e) {
  if (e.currentTarget.scrollTop === 0) {
    e.currentTarget.scrollTop = 1;
  } else if (e.currentTarget.scrollHeight === e.currentTarget.scrollTop + e.currentTarget.offsetHeight) {
    e.currentTarget.scrollTop -= 1;
  }
});
// Stops preventDefault from being called on document if it sees a scrollable div
$('body').on('touchmove', selScrollable, function(e) {
  e.stopPropagation();
});

请注意,如果您想在div没有overflow的情况下阻止整个页面滚动,那么这种方法将不起作用。为了阻止它,使用以下事件处理程序代替上面的事件处理程序(从此问题改编):

$('body').on('touchmove', selScrollable, function(e) {
    // Only block default if internal div contents are large enough to scroll
    // Warning: scrollHeight support is not universal. (https://dev59.com/pWw15IYBdhLWcg3wD3jp#15033226)
    if($(this)[0].scrollHeight > $(this).innerHeight()) {
        e.stopPropagation();
    }
});

2
非常好的工作 - 这绝对比直接针对.scrollable更好(这是我最初尝试解决此问题的方法)。如果你是JavaScript新手,并且想要轻松删除这些处理程序,请使用以下两行代码:$(document).off('touchmove');$('body').off('touchmove touchstart', '.scrollable'); - Devin
1
如果 div 中没有足够的内容需要滚动,这个方法就不起作用。有人已经在这里提出了一个单独的问题来回答它:http://stackoverflow.com/q/16437182/40352。 - Chris
如何允许多个“.scrollable”类?它可以正常工作,但我还需要使另一个div可滚动。谢谢! - MeV
我发现将 $(document) 替换为 $('html,document') 可以解决 iOS 9 上的不可靠问题。此外,我发现 $('body').on('touchstart', selScrollable, function(e)... 不是必需的。 - Timo Kähkönen
我必须将它放在 $(document).ready(function() { 中才能使它工作! - gowithefloww
显示剩余2条评论

24

使用 Tyler Dodge 的优秀答案在我的 iPad 上一直有卡顿的问题,因此我添加了一些节流代码,现在非常流畅。滚动时有时会有一些最小的跳跃。

// Uses document because document will be topmost level in bubbling
$(document).on('touchmove',function(e){
  e.preventDefault();
});

var scrolling = false;

// Uses body because jquery on events are called off of the element they are
// added to, so bubbling would not work if we used document instead.
$('body').on('touchstart','.scrollable',function(e) {

    // Only execute the below code once at a time
    if (!scrolling) {
        scrolling = true;   
        if (e.currentTarget.scrollTop === 0) {
          e.currentTarget.scrollTop = 1;
        } else if (e.currentTarget.scrollHeight === e.currentTarget.scrollTop + e.currentTarget.offsetHeight) {
          e.currentTarget.scrollTop -= 1;
        }
        scrolling = false;
    }
});

// Prevents preventDefault from being called on document if it sees a scrollable div
$('body').on('touchmove','.scrollable',function(e) {
  e.stopPropagation();
});

此外,添加以下CSS代码可以修复一些渲染问题 (来源):

.scrollable {
    overflow: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
}
.scrollable * {
    -webkit-transform: translate3d(0,0,0);
}

如果可滚动区域内有 iframe 并且用户开始在该 iframe 上滚动,则此方法将无效。是否有解决方法? - Timo Ernst
1
似乎向后拖动完美运作,但向下拖动仍会移动Safari。 - Abadaba
1
一个很棒的解决方案...非常感谢 :) - Aamir Shah
太棒了,效果很好,让我省去了进一步解决问题的压力。谢谢你,Kuba! - Leonard
scrolling之间的代码将会在JavaScript中以单线程的方式一次执行一次,而不需要使用变量。 - Sanghyun Lee
显示剩余2条评论

13

首先像往常一样在整个文档上防止默认操作:

$(document).bind('touchmove', function(e){
  e.preventDefault();           
});

停止元素类的传播到文档级别。这将阻止它达到上面的函数,从而不会启动e.preventDefault():

$('.scrollable').bind('touchmove', function(e){
  e.stopPropagation();
});

这个系统似乎比在所有触摸移动上计算类更自然且不那么密集。对于动态生成的元素,请使用.on()而不是.bind()。此外,考虑使用这些元标记来防止在使用可滚动的div时发生不幸的事情:
<meta content='True' name='HandheldFriendly' />
<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0' name='viewport' />
<meta name="viewport" content="width=device-width" />

7

你能否在禁用overscroll的代码中添加一些逻辑,以确保目标元素不是你想要滚动的元素?类似于这样:

document.body.addEventListener('touchmove',function(e){
     if(!$(e.target).hasClass("scrollable")) {
       e.preventDefault();
     }
 });

3
谢谢…看起来这应该可以工作,但实际上并没有。此外,“scrollable”不应该是“.scrollable”(带点号)吗? - Jeff
1
似乎是最深嵌套的元素接收了触摸事件,所以您可能需要检查所有父级,以查看您是否在可滚动的 div 中。 - Christopher Johnson
3
如果使用jQuery,为什么还要使用document.body.addEventListener?这是有原因的吗? - fnagel

7

最好的解决方法是使用CSS/HTML: 1. 创建一个div来包裹您的元素,如果您还没有,请创建一个。 2. 将其设置为fixed定位和隐藏溢出内容。可选的,如果您想要填满整个屏幕并且只有整个屏幕,可以将高度和宽度设置为100%。

#wrapper{
  height: 100%;
  width: 100%;
  position: fixed;
  overflow: hidden;
}
<div id="wrapper">
  <p>All</p>
  <p>Your</p>
  <p>Elements</p>
</div>


5
检查当尝试向上滚动或向下滚动时,可滚动元素是否已经滚动到了顶部或底部,然后防止默认操作以停止整个页面移动。
var touchStartEvent;
$('.scrollable').on({
    touchstart: function(e) {
        touchStartEvent = e;
    },
    touchmove: function(e) {
        if ((e.originalEvent.pageY > touchStartEvent.originalEvent.pageY && this.scrollTop == 0) ||
            (e.originalEvent.pageY < touchStartEvent.originalEvent.pageY && this.scrollTop + this.offsetHeight >= this.scrollHeight))
            e.preventDefault();
    }
});

我必须检查 e.originalEvent.touches[0].pageY 而不是 e.originalEvent.pageY。这样做可以工作,但只有当您已经到达滚动 div 的末尾时才有效。当滚动正在进行中时(例如,您已经快速滚动),它不会在到达可滚动 div 的末尾后停止。 - Keen Sage

4
我在寻找一种方法来防止所有页面滚动,当有一个带有可滚动区域的弹出窗口时(例如一个“购物车”下拉框,其中包含您购物车的可滚动视图)。
我使用最少量的JavaScript编写了一种更加优雅的解决方案,只需在您想要滚动(而不是“超滚”整个页面主体)的弹出窗口或div上切换“noscroll”类。
虽然桌面浏览器会观察到overflow:hidden,但iOS似乎会忽略它,除非您将位置设置为fixed...这会导致整个页面变成奇怪的宽度,因此您还需要手动设置位置和宽度。使用以下CSS:
.noscroll {
    overflow: hidden;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
}

还有这个jquery:

/* fade in/out cart popup, add/remove .noscroll from body */
$('a.cart').click(function() {
    $('nav > ul.cart').fadeToggle(100, 'linear');
    if ($('nav > ul.cart').is(":visible")) {
        $('body').toggleClass('noscroll');
    } else {
        $('body').removeClass('noscroll');
    }
});

/* close all popup menus when you click the page... */
$('body').click(function () {
    $('nav > ul').fadeOut(100, 'linear');
    $('body').removeClass('noscroll');
});

/* ... but prevent clicks in the popup from closing the popup */
$('nav > ul').click(function(event){
    event.stopPropagation();
});

这非常有帮助,而且是一种最简化的方法,正是我所需要的。将位置设置为固定值,并使用top:0; left:0; width:100%; 这些元素是我所缺少的。这对于弹出菜单也非常有用。 - bdanin

3

我已经想出了一个没有使用jQuery的小技巧。虽然不完美,但效果很好(特别是当你在scroll-y中有scroll-x时)。https://github.com/pinadesign/overscroll/

欢迎参与和改进它。


1
和Jeff遇到了同样的问题,尝试了所有的答案,你的起作用了。谢谢! - Dominik Schreiber
当具有.scrollable的div具有足够的内容使其溢出时,接受的答案对我有效。如果它没有溢出,则“弹跳”效果仍然存在。但是这个方法完美地解决了问题,谢谢! - Adam Marshall

1
这个解决方案不要求您在所有可滚动的div上放置可滚动类,因此更加通用。允许在所有元素上滚动,这些元素是INPUT元素contenteditable和overflow scroll或autos的子元素或本身。
我使用了自定义选择器,并且还将检查结果缓存到元素中以提高性能。没有必要每次都检查相同的元素。这可能有一些问题,因为只是刚写出来,但我想分享一下。
$.expr[':'].scrollable = function(obj) {
    var $el = $(obj);
    var tagName = $el.prop("tagName");
    return (tagName !== 'BODY' && tagName !== 'HTML') && (tagName === 'INPUT' || $el.is("[contentEditable='true']") || $el.css("overflow").match(/auto|scroll/));
};
function preventBodyScroll() {
    function isScrollAllowed($target) {
        if ($target.data("isScrollAllowed") !== undefined) {
            return $target.data("isScrollAllowed");
        }
        var scrollAllowed = $target.closest(":scrollable").length > 0;
        $target.data("isScrollAllowed",scrollAllowed);
        return scrollAllowed;
    }
    $('body').bind('touchmove', function (ev) {
        if (!isScrollAllowed($(ev.target))) {
            ev.preventDefault();
        }
    });
}

1
overscroll-behavior: none;

overscroll

overscroll-behavior属性是一种新的CSS功能,它控制了当您超出容器(包括页面本身)时会发生什么行为。您可以使用它来取消滚动链接,禁用/自定义下拉刷新操作,在iOS上禁用橡皮筋效果(当Safari实现overscroll-behavior时),等等。最好的部分是使用overscroll-behavior不会对页面性能产生不利影响。


1
一个好的答案总是会包括解释为什么这样做可以解决问题,这样原帖作者和任何未来的读者都可以从中学习。 - Tyler2P
@Tyler2P 我编辑了我的回答,希望现在可以了,感谢你的建议 :) - bagli
毕竟height: 100%;它实际上起到了帮助的作用。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接