I'm working on an iPad-based web app, and need to prevent overscrolling so that it seems less like a web page. I'm currently using this to freeze the viewport and disable overscroll:
document.body.addEventListener('touchmove',function(e){ e.preventDefault(); });
This works great to disable overscroll but my app has several scrollable divs, and the above code prevents them from scrolling.
I'm targeting iOS 5 and above only so I've avoided hacky solutions like iScroll. Instead I'm using this CSS for my scrollable divs:
.scrollable { -webkit-overflow-scrolling: touch; overflow-y:auto; }
This works without the document overscroll script, but doesn't solve the div scrolling problem.
Without a jQuery plugin, is there any way to use the overscroll fix but exempt my $('.scrollable') divs?
EDIT:
I found something that's a decent solution:
// Disable overscroll / viewport moving on everything but scrollable divs $('body').on('touchmove', function (e) { if (!$('.scrollable').has($(e.target)).length) e.preventDefault(); });
The viewport still moves when you scroll past the beginning or end of the div. I'd like to find a way to disable that as well.
Handling iOS Safari For non-scrollable elements, you can prevent scroll chaining by simply turning off touch gestures. You can do that with a CSS property that is supported by Safari: touch-action: none . But for scrollable elements, JavaScript will be required.
You can use overscroll-behavior to get rid of unwanted scroll chaining and the browser's Facebook/Twitter app-inspired "pull to refresh"-type behavior. Note that this property only applies to scroll containers.
To do this (in Chrome, Firefox, and Edge), we can add the CSS property overscroll-behavior: contain to the overflow: auto element. This will prevent the "scroll chaining" behavior, which will, in turn, keep the mouse-wheel active within the target element.
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); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With