iOS Safari –如何禁用过度滚动但允许可滚动的div正常滚动?


100

我正在使用基于iPad的Web应用程序,因此需要防止滚动过度,以使其看起来不像是网页。我目前正在使用它冻结视口并禁用过度滚动:

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

这对于禁用过度滚动非常有用,但是我的应用程序具有多个可滚动的div,上面的代码可以防止它们滚动

我仅针对iOS 5及更高版本,因此避免了像iScroll这样的恶意解决方案。相反,我将此CSS用于可滚动div:

.scrollable {
    -webkit-overflow-scrolling: touch;
    overflow-y:auto;
}

这在没有文档过度滚动脚本的情况下有效,但不能解决div滚动问题。

如果没有jQuery插件,是否有任何方法可以使用过度滚动修复程序,但可以免除我的$('。scrollable')div?

编辑:

我发现了一个不错的解决方案:

 // 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 2012年

通过显式捕获可滚动div的父级上的滚动事件并不允许其实际滚动,我可以使视口在滚动到div的末尾时保持不动。如果您使用的是jquery mobile,则应在页面级执行此操作,例如:$('div [data-role =“ page”]')。on('scroll',function(e){e.preventDefault ();});
Christopher Johnson


我发现此脚本可以解决此问题!:) github.com/lazd/iNoBounce
扬Šafránek

如果有人在您的帖子上方7个月前发布了链接,为什么还要再次发布该链接?
丹尼

Answers:


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没有溢出的情况下阻止整个页面滚动,则此方法将无效。要阻止此事件,请使用以下事件处理程序,而不是紧接在上面的事件处理程序(根据此问题改编):

$('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://stackoverflow.com/a/15033226/40352)
    if($(this)[0].scrollHeight > $(this).innerHeight()) {
        e.stopPropagation();
    }
});

如果在可滚动区域内有一个iframe,并且用户开始在该iframe上滚动,则此方法将无效。有没有解决方法?
2013年

2
效果很好-这绝对比直接定位更好.scrollable(这是我最初为解决此问题而尝试的目标)。如果您是JavaScript新手,并且想通过简单的代码删除这些处理程序,那么这两行对我来说非常有用! $(document).off('touchmove'); AND $('body').off('touchmove touchstart', '.scrollable');
Devin

它对我来说非常有效。非常感谢,您节省了我几个小时!
marcgg

1
如果div中没有​​足够的内容来滚动,则此方法不起作用。有人在这里回答了一个单独的问题:stackoverflow.com/q/16437182/40352
克里斯(Chris)

如何允许多个“ .scrollable”类?它可以与一个很好地工作,但我也需要使另一个div可滚动。谢谢!
MeV

23

使用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

1
似乎可以完美地向后拖动,但向下拖动仍会移动野生动物园。
阿巴达巴2013年

1
一个很棒的解决方案...非常感谢:)
Aamir Shah 2014年

这对我有用。谢谢!我花了超过1.5天的时间来解决这个问题。
Achintha Samindika 2014年

这太棒了,效果很好,使我避​​免了尝试解决方案的更多压力。谢谢库巴!
伦纳德

12

首先,像往常一样防止对整个文档执行默认操作:

$(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

您能否在超速滚动禁用代码中添加更多逻辑,以确保所涉及的目标元素不是您想要滚动的元素?像这样:

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

3
谢谢...似乎应该可以,但是不能。另外,它不是“可滚动”而不是“ .scrollable”(带点)吗?
杰夫

1
似乎这是接收触摸事件的嵌套最深的元素,因此您可能需要检查所有父母,以查看您是否处于可滚动的div中。
Christopher Johnson

3
如果使用jQuery,为什么要使用document.body.addEventListener?那是有原因吗?
fnagel 2014年

7

最好的解决方案是css / html:如果没有div,则将其包装为div并将其设置为固定位置并隐藏溢出。可选,如果要填充整个屏幕,则将高度和宽度设置为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的末尾,它就不会停止。
敏锐的贤者

4

我一直在寻找一种方法来防止在出现具有可滚动区域的弹出窗口(“购物车”弹出窗口具有购物车可滚动视图的情况下)的所有身体滚动。

我写了一个更优雅的解决方案,使用最少的JavaScript在您要滚动的弹出窗口或div上切换主体“ noscroll”类(而不是“过度滚动”整个页面主体)。

桌面浏览器会发现溢出:隐藏-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:左:0; 宽度:100%; 是我所缺少的元素。这对于弹出菜单也很有用。
bdanin 2015年

3

我在没有jQuery的情况下工作了一点。不愿意,但可以正常工作(特别是如果您在scoll-y中使用scroll-x)https://github.com/pinadesign/overscroll/

随意参与并改进它


1
遇到了与Jeff相同的问题,尝试了所有答案,您的工作就成功了。谢谢!
Dominik Schreiber

仅当具有.scrollable的div的内容足以导致其溢出时,可接受的答案才对我有用。如果没有溢出,则“反弹”效果仍然存在。但是,这非常有效,谢谢!
亚当·马歇尔

1

此解决方案不需要您在所有可滚动div上放置一个可滚动类,因此更为通用。允许在属于INPUT元素contenteditables和溢出滚动或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

禁用所有“ touchmove”事件似乎是一个好主意,但是,一旦页面上需要其他可滚动元素,就会引起问题。最重要的是,如果仅在某些元素上禁用“ touchmove”事件(例如主体,如果您希望页面不可滚动),则只要在其他任何地方启用它,IOS就会在URL条形切换。

尽管我无法解释这种行为,但似乎唯一的预防方法似乎是将身体的位置设置为fixed。唯一的问题是您将失去文档的位置-例如,这在模态中尤其令人讨厌。解决该问题的一种方法是使用以下简单的VanillaJS函数:

function disableDocumentScrolling() {
    if (document.documentElement.style.position != 'fixed') {
        // Get the top vertical offset.
        var topVerticalOffset = (typeof window.pageYOffset != 'undefined') ?
            window.pageYOffset : (document.documentElement.scrollTop ? 
            document.documentElement.scrollTop : 0);
        // Set the document to fixed position (this is the only way around IOS' overscroll "feature").
        document.documentElement.style.position = 'fixed';
        // Set back the offset position by user negative margin on the fixed document.
        document.documentElement.style.marginTop = '-' + topVerticalOffset + 'px';
    }
}

function enableDocumentScrolling() {
    if (document.documentElement.style.position == 'fixed') {
        // Remove the fixed position on the document.
        document.documentElement.style.position = null;
        // Calculate back the original position of the non-fixed document.
        var scrollPosition = -1 * parseFloat(document.documentElement.style.marginTop);
        // Remove fixed document negative margin.
        document.documentElement.style.marginTop = null;
        // Scroll to the original position of the non-fixed document.
        window.scrollTo(0, scrollPosition);
    }
}

使用此解决方案,您可以拥有固定的文档,并且页面中的任何其他元素都可以通过使用简单的CSS(例如overflow: scroll;)溢出。无需特殊课程或其他任何课程。


0

这是与zepto兼容的解决方案

    if (!$(e.target).hasClass('scrollable') && !$(e.target).closest('.scrollable').length > 0) {
       console.log('prevented scroll');
       e.preventDefault();
       window.scroll(0,0);
       return false;
    }

0

这个对我有用(纯javascript)

var fixScroll = function (className, border) {  // className = class of scrollElement(s), border: borderTop + borderBottom, due to offsetHeight
var reg = new RegExp(className,"i"); var off = +border + 1;
function _testClass(e) { var o = e.target; while (!reg.test(o.className)) if (!o || o==document) return false; else o = o.parentNode; return o;}
document.ontouchmove  = function(e) { var o = _testClass(e); if (o) { e.stopPropagation(); if (o.scrollTop == 0) { o.scrollTop += 1; e.preventDefault();}}}
document.ontouchstart = function(e) { var o = _testClass(e); if (o && o.scrollHeight >= o.scrollTop + o.offsetHeight - off) o.scrollTop -= off;}
}

fixScroll("fixscroll",2); // assuming I have a 1px border in my DIV

的HTML:

<div class="fixscroll" style="border:1px gray solid">content</div>

0

试试这个,它将完美工作。

$('body.overflow-hidden').delegate('#skrollr-body','touchmove',function(e){
    e.preventDefault();
    console.log('Stop skrollrbody');
}).delegate('.mfp-auto-cursor .mfp-content','touchmove',function(e){
    e.stopPropagation();
    console.log('Scroll scroll');
});

0

我的简单运气令人惊讶:

body {
    height: 100vh;
}

禁用弹出窗口或菜单的过度滚动非常有用,并且不会强制浏览器栏显示为使用position:fixed时的样子。但是-您需要在设置固定高度之前保存滚动位置,并在隐藏弹出窗口时将其还原,否则浏览器将滚动到顶部。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.