iOS Safari – How to disable overscroll but allow scrollable divs to scroll normally?

Jeff picture Jeff · Apr 20, 2012 · Viewed 133.4k times · Source

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.

Answer

Tyler Dodge picture Tyler Dodge · Jan 9, 2013

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