107

我正在开发一个基于 iPad 的网络应用程序,并且需要防止过度滚动,使其看起来不像网页。我目前正在使用它来冻结视口并禁用过度滚动:

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

这非常适合禁用过度滚动,但我的应用程序有几个可滚动的 div,并且上面的代码阻止它们滚动

我只针对 iOS 5 及更高版本,所以我避免了像 iScroll 这样的 hacky 解决方案。相反,我将这个 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 的开头或结尾时,视口仍会移动。我也想找到一种方法来禁用它。

4

15 回答 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 没有溢出时阻止整个页面滚动,这将不起作用。要阻止它,请使用以下事件处理程序而不是上面的事件处理程序(改编自这个问题):

$('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();
    }
});
于 2013-01-09T19:12:47.947 回答
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 修复了一些渲染故障(source):

.scrollable {
    overflow: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
}
.scrollable * {
    -webkit-transform: translate3d(0,0,0);
}
于 2013-01-21T22:25:50.057 回答
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" />
于 2013-03-02T15:19:20.067 回答
7

您是否可以在禁用过度滚动的代码中添加更多逻辑,以确保所讨论的目标元素不是您想要滚动的元素?像这样的东西:

document.body.addEventListener('touchmove',function(e){
     if(!$(e.target).hasClass("scrollable")) {
       e.preventDefault();
     }
 });
于 2012-05-02T04:16:09.693 回答
7

对此的最佳解决方案是 css/html:如果您还没有它,请制作一个 div 来包装您的元素并将其设置为固定位置并隐藏溢出。可选的,如果你想让它填满整个屏幕并且只填满整个屏幕,将高度和宽度设置为 100%

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

于 2017-01-05T08:21:33.433 回答
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();
    }
});
于 2012-05-31T14:15:47.927 回答
4

当出现带有可滚动区域的弹出窗口(具有购物车可滚动视图的“购物车”弹出窗口)时,我正在寻找一种方法来防止所有正文滚动。

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

虽然桌面浏览器观察到溢出:隐藏——iOS似乎忽略了这一点,除非你将位置设置为固定......这会导致整个页面的宽度变得奇怪,所以你也必须手动设置位置和宽度。使用这个 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();
});
于 2013-09-20T17:57:07.877 回答
3

我在没有 jquery 的情况下做了一些工作。不完美但工作正常(特别是如果你在 scoll-y 中有一个 scroll-x)https://github.com/pinadesign/overscroll/

自由参与并改进它

于 2013-06-10T21:41:59.433 回答
1

虽然禁用所有“touchmove”事件似乎是个好主意,但一旦您需要页面上的其他可滚动元素,它就会导致问题。最重要的是,如果您只在某些元素上禁用“touchmove”事件(例如,如果您希望页面不可滚动,则为 body),一旦在其他任何地方启用它,IOS 将在 Chrome 中导致 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;)溢出。不需要特殊课程或其他任何东西。

于 2017-02-12T17:33:41.577 回答
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();
        }
    });
}
于 2016-04-21T10:15:25.707 回答
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;
    }
于 2013-02-18T05:34:35.007 回答
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>
于 2015-05-12T06:00:34.910 回答
0

我很幸运很简单:

body {
    height: 100vh;
}

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

于 2017-10-21T08:38:17.273 回答
0
overscroll-behavior: none;

过度滚动

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

于 2022-01-24T16:12:09.207 回答
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');
});
于 2016-06-28T11:57:44.570 回答