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

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

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

这个工程伟大的禁用超滚动,但我的应用程序有几个可滚动的 div,和 上面的代码阻止他们滚动

我的目标是 iOS5及以上版本,所以我避免使用像 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 的开始或结束时,视图仍在移动。我也想找个办法把它关掉。

144397 次浏览

Can you just add a little more logic into your overscroll disabling code to make sure the targeted element in question is not one that you would like to scroll? Something like this:

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

Check if the scrollable element is already scrolled to the top when trying to scroll up or to the bottom when trying to scroll down and then preventing the default action to stop the entire page from moving.

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();
}
});

This solves the issue when you scroll past the beginning or end of the 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();
});

Note that this won't work if you want to block whole page scrolling when a div does not have overflow. To block that, use the following event handler instead of the one immediately above (adapted from this question):

$('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();
}
});

Using Tyler Dodge's excellent answer kept lagging on my iPad, so I added some throttling code, now it's quite smooth. There is some minimal skipping sometimes while scrolling.

// 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();
});

Also, adding the following CSS fixes some rendering glitches (source):

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

Here's a zepto compatible solution

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

First prevent default actions on your entire document as usual:

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

Then stop your class of elements from propagating to the document level. This stops it from reaching the function above and thus e.preventDefault() is not initiated:

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

This system seems to be more natural and less intensive than calculating the class on all touch moves. Use .on() rather than .bind() for dynamically generated elements.

Also consider these meta tags to prevent unfortunate things from happening while using your scrollable 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" />

I've work a little workarround without jquery. Not perfert but works fine (especially if you have a scroll-x in a scoll-y) https://github.com/pinadesign/overscroll/

Fell free to participate and improve it

I was looking for a way to prevent all body scrolling when there's a popup with a scrollable area (a "shopping cart" popdown that has a scrollable view of your cart).

I wrote a far more elegant solution using minimal javascript to just toggle the class "noscroll" on your body when you have a popup or div that you'd like to scroll (and not "overscroll" the whole page body).

while desktop browsers observe overflow:hidden -- iOS seems to ignore that unless you set the position to fixed... which causes the whole page to be a strange width, so you have to set the position and width manually as well. use this css:

.noscroll {
overflow: hidden;
position: fixed;
top: 0;
left: 0;
width: 100%;
}

and this 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();
});

this one works for me (plain 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>

This solution doesn't require you to put a scrollable class on all your scrollable divs so is more general. Scrolling is allowed on all elements which are, or are children of, INPUT elements contenteditables and overflow scroll or autos.

I use a custom selector and I also cache the result of the check in the element to improve performance. No need to check the same element every time. This may have a few issues as only just written but thought I'd share.

$.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();
}
});
}

Try this It'll work perfect.

$('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');
});

Best solution to this is css/html: Make a div to wrap your elements in, if you dont have it already And set it to position fixed and overflow hidden. Optional, set height and width to 100% if you want it to fill the whole screen and nothing but the whole screen

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

While disabling all "touchmove" events might seem like a good idea, as soon as you need other scrollable elements on the page it will cause problems. On top of that, if you only disable "touchmove" events on certain elements (e.g. body if you want the page to be non-scrollable), as soon as it is enabled anywhere else, IOS will cause unstoppable propagation in Chrome when the URL bar toggles.

While I cannot explain this behavior, it looks like the only way to prevent seems to set the body's position to fixed. The only problem doing is that you will lose the position of the document - this is especially annoying in modals for example. One way to solve it would be to use these simple VanillaJS functions:

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);
}
}

Using this solution you can have a fixed document and any other element in your page can overflow by using simple CSS (e.g., overflow: scroll;). No need for special classes or anything else.

I had surprising luck with with simple:

body {
height: 100vh;
}

It works great to disable overscroll for pop-ups or menus and it doesn't force browser bars to appear like when using position:fixed. BUT - you need to save scroll position before setting fixed height and restore it when hiding the pop-up, otherwise, browser will scroll to top.

overscroll-behavior: none;

overscroll

The overscroll-behavior property is a new CSS feature that controls the behavior of what happens when you over-scroll a container (including the page itself). You can use it to cancel scroll chaining, disable/customize the pull-to-refresh action, disable rubberbanding effects on iOS (when Safari implements overscroll-behavior), and more. The best part is that using overscroll-behavior does not adversely affect page performance.