Html – Prevent overflow / rubberband scrolling on iOS

csshtmliosoverflowscroll

There are already multiple questions on the topic of overflow/rubberband scrolling on SO but

  1. none of them provides a solution working for all cases on iOS 9.3.2
  2. none of them gives extensive and complete information on the problem itself

which is why I created this post as a body of knowledge.


The problem:

The thing that was never mentioned in any other post is that iOS overflow scrolling is actually a two part behaviour.

1. Overflow scrolling of content with overflow: auto/scroll

This is the commonly known and mostly wanted behaviour of a element with -webkit-overflow-scrolling: touch where the continuous/momentum scrolling behaviour goes past the elements container to slow the scrolled content down smoothly.

It happens when you scroll the content of an element with a momentum high enough for the momentum scrolling to go past the length of the scrolled content.

With this behaviour the element.scrollTop property changing accordingly to the elements scroll position and being less than 0 or bigger than the maximum scroll (element.scrollHeight - element.offsetHeight).

2. Overflow scrolling of <body>

This behaviour occurs if you try to scroll any element already at its minimum/maximum scroll position even further than that (an element at top upwards or element at bottom downwards). Then the scroll seems to "bubble up" up to the <body> tag and the whole viewport is scrolled.

In contrary to above here the element.scrollTop property does not change but document.body.scrollTop changes instead.

Focus lock and switching between behaviours (1.5s delay)

The most irritating thing in this context is that the switch between the two types described above does not switch instantly.

After you enter one of both you cannot switch focus onto any other element (scrollable elements, buttons, links, …) and thereby the scrolling behaviour does not change as well.

For instance: if you scroll a element already at its top position upwards you enter overflow scrolling type 2 and the most natural reaction for a user is to then try to scroll back down. Because the focus is locked to the body scroll instead of going to overflow scrolling type 1 it stays in type 2 and the whole body is scrolled downwards. The typical user then starts to arbitrarily starts to scroll up and down frequently without ever breaking out of type 2.

The switch of focus and thus the change of scrolling behaviour can only occur after the overflow animation has finished and the element stands still (even a bit longer [around 0.5s] than that).

thus going back to the previous example the correct reaction of the user would be to stop touching the screen for around 1s – 1.5s and then try to scroll downwards again.

Best Answer

The solution:

Type 1:

The most basic solution to prevent overflow scrolling on the element itself is to prevent default on touch events.

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

This method however disables the browsers native momentum scroll and is thereby not suitable for most applications. With some refinement however (only prevent if at top scrolling up or at bottom scrolling down, ...) this method fixes most problems. Many possible implementations can be found in this SO post.

Type 2:

Overflow scrolling on the body however is not prevented by methods described above.

One possible solution which seems reasonable is to prevent the element from ever being at its top or bottom position as described as best solution on mentioned question.

anElement.addEventListener('touchstart', function( event ){
    if( this.scrollTop === 0 ) {
        this.scrollTop += 1;
    } else if( this.scrollTop + this.offsetHeight >= this.scrollHeight ) {
        this.scrollTop -= 1;
    }
}

This however did not reliably work on iOS 9.3.2.

What did work however is setting position: fixed on the <body> element to prevent the body from moving. Please note however that this still does not completely stop type 2 from happening, which is why sometimes you cannot scroll/focus any element because in the background type2 with its focus lock is still happening (again, after you stop touching the screen for a moment it again works as expected).

While this is still far from being the optimal solution it seems to be the best we can get for the time speaking.

Edit: Please note that I am not sure if it is safe to put position: fixed on a <body> element. To track possible issues I have created following SO post. Apparently it might be better to create a wrapper element as child of body and set that element to position: fixed to avoid zoom problemes.


Edit 2: The definite solution

The script iNoBounce works wonders. Just load it to the page and experience a bounce-free web application. So far I have not found any problems with this solution.