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

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

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

这很好地禁用overscroll,但我的应用程序有几个可滚动的div, 上面的代码阻止他们滚动

我只针对iOS 5及以上版本,所以我避免了像iScroll这样的hacky解决scheme。 相反,我正在使用这个CSS滚动div:

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

这工作没有文件overscroll脚本,但不解决div滚动问题。

没有jQuery插件,有没有办法使用overscroll修复,但免除我的$('。scrollable')div?

编辑:

我发现这是一个体面的解决scheme:

  // Disable overscroll / viewport moving on everything but scrollable divs $('body').on('touchmove', function (e) { if (!$('.scrollable').has($(e.target)).length) e.preventDefault(); }); 

当您滚动div的开头或结尾时,视口仍会移动。 我想find一种方法来禁用,以及。

当您滚动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(); } }); 

使用Tyler Dodge的优秀答案在我的iPad上保持不变,所以我添加了一些限制代码,现在非常stream畅。 有时在滚动时有一些最小的跳过。

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

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

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

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

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

这个系统似乎比在所有的触摸动作上计算class级更加自然和密集。 dynamic生成的元素使用.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" /> 

你可以在你的overscroll禁用代码中添加一些更多的逻辑,以确保有问题的目标元素不是你想要滚动的元素吗? 像这样的东西:

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

检查可滚动元素是否在试图向上滚动时滚动到顶部,在试图向下滚动时滚动到底部,然后阻止默认操作停止整个页面的移动。

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

我正在寻找一种方法来防止所有的身体滚动,当有一个可滚动区域的popup(一个“购物车”popup,有一个可滚动的购物车视图)。

我使用最简单的javascript编写了一个更加优雅的解决scheme,当您想要滚动的popup窗口或div(而不是“overscroll”整个页面主体)时,只需在您的身体上切换类“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(); }); 

我已经工作了一些没有jQuery的workarround。 不是完美的,但工作正常(特别是如果你有一个滚动-x在一个scoll-y) https://github.com/pinadesign/overscroll/

随时可以参与并改进它

最好的解决scheme是css / html:创build一个div来包装你的元素,如果没有它已经设置它的位置固定和溢出隐藏。 可选的,如果你想要填充整个屏幕而不是整个屏幕,那么将高度和宽度设置为100%

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

这个解决scheme并不要求你在所有的可滚动div上放置一个可滚动类,所以更一般。 在INPUT元素contenteditables和overflow scroll或autos的所有元素或子元素上都允许滚动。

我使用自定义select器,并且还将检查结果caching在元素中以提高性能。 无需每次都检查相同的元素。 这可能只有几个问题,但只是想我会分享。

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

虽然禁用所有的“touchmove”事件似乎是一个好主意,只要你需要页面上的其他可滚动元素,它会导致问题。 最重要的是,如果你只在某些元素上禁用“touchmove”事件(例如,如果你希望页面不可滚动,则为body),只要在其他任何地方启用它,IOS会在Chrome中导致不可阻挡的传播酒吧切换。

虽然我无法解释这种行为,但似乎唯一的防范方法似乎是将机构的立场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); } } 

使用这个解决scheme,你可以有一个固定的文档,页面中的任何其他元素可以通过使用简单的CSS overflow: scroll; (例如, overflow: scroll; )。 不需要特殊的课程或其他任何东西。

这是一个zepto兼容的解决scheme

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

这个为我工作(纯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> 

尝试一下这将工作完美。

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

我用简单的方式碰到了令人惊讶的运气:

 body { height: 100vh; } 

它很好地禁用popup窗口或菜单的overscroll,它不会强制浏览器栏像使用position:fixed时出现。 但是 – 在设置固定高度之前需要保存滚动位置,并在隐藏popup窗口时将其恢复,否则浏览器将滚动到顶部。