Stop Overscroll When Using "-Webkit-Overflow-Scrolling: Touch"

Prevent body scrolling but allow overlay scrolling

Theory

Looking at current implementation of the pinterest site (it might change in the future), when you open the overlay, a noscroll class is applied to the body element (setting overflow: hidden) making the body no longer scrollable.

The overlay created on-the-fly or already injected in the page and made visible via display: blockit makes no difference – has position : fixed and overflow-y: scroll, with top, left, right and bottom properties set to 0: this style makes the overlay fill the whole viewport (but now we are in 2022, so you may use inset: 0 instead).

The div inside the overlay is in position: static so the vertical scrollbar is related to that element. This is resulting in a scrollable but fixed overlay.

When you close the overlay, you have to hide it (using display: none) and you could even remove the node via javascript (or just the content inside, it's up to you but also depends on the nature of the content).

The final step is to also remove the noscroll class applied to the body (so the overflow property gets back to the value it had previously)


Code

Codepen Example

(it works by changing the aria-hidden attribute of the overlay in order to show and hide it and to increase its accessibility).

Markup

(open button)

<button type="button" class="open-overlay">OPEN LAYER</button>

(overlay and close button)

<section class="overlay" aria-hidden="true" tabindex="-1">
<div>
<h2>Hello, I'm the overlayer</h2>
...
<button type="button" class="close-overlay">CLOSE LAYER</button>
</div>
</section>

CSS

.noscroll { 
overflow: hidden;
}

.overlay {
position: fixed;
overflow-y: scroll;
inset: 0; }

[aria-hidden="true"] { display: none; }
[aria-hidden="false"] { display: block; }

Javascript (vanilla-JS)

var body = document.body,
overlay = document.querySelector('.overlay'),
overlayBtts = document.querySelectorAll('button[class$="overlay"]'),
openingBtt;

[].forEach.call(overlayBtts, function(btt) {

btt.addEventListener('click', function() {

/* Detect the button class name */
var overlayOpen = this.className === 'open-overlay';

/* storing a reference to the opening button */
if (overlayOpen) {
openingBtt = this;
}

/* Toggle the aria-hidden state on the overlay and the
no-scroll class on the body */
overlay.setAttribute('aria-hidden', !overlayOpen);
body.classList.toggle('noscroll', overlayOpen);

/* On some mobile browser when the overlay was previously
opened and scrolled, if you open it again it doesn't
reset its scrollTop property */
overlay.scrollTop = 0;

/* forcing focus for Assistive technologies but note:
- if your modal has just a phrase and a button move the
focus on the button
- if your modal has a long text inside (e.g. a privacy
policy) move the focus on the first heading inside
the modal
- otherwise just focus the modal.

When you close the overlay restore the focus on the
button that opened the modal.
*/
if (overlayOpen) {
overlay.focus();
}
else {
openingBtt.focus();
openingBtt = null;
}

}, false);

});

/* detect Escape key when the overlay is open */
document.body.addEventListener('keyup', (ev) => {
if (ev.key === "Escape" && overlay.getAttribute('aria-hidden') === 'false') {
overlay.setAttribute('aria-hidden', 'true');
body.classList.toggle('noscroll', false);
openingBtt.focus();
openingBtt = null;
}
})

Finally, here's another example in which the overlay opens with a fade-in effect by a CSS transition applied to the opacity property. Also a padding-right is applied to avoid a reflow on the underlying text when the scrollbar disappears.

Codepen Example (fade)

CSS

.noscroll { overflow: hidden; }

@media (min-device-width: 1025px) {
/* not strictly necessary, just an experiment for
this specific example and couldn't be necessary
at all on some browser */
.noscroll {
padding-right: 15px;
}
}

.overlay {
position: fixed;
overflow-y: scroll;
inset: 0;
}

[aria-hidden="true"] {
transition: opacity 1s, z-index 0s 1s;
width: 100vw;
z-index: -1;
opacity: 0;
}

[aria-hidden="false"] {
transition: opacity 1s;
width: 100%;
z-index: 1;
opacity: 1;
}

Prevent overscrolling of web page

The accepted solution was not working for me. The only way I got it working while still being able to scroll is:

html {
overflow: hidden;
height: 100%;
}

body {
height: 100%;
overflow: auto;
}

Prevent overflow / rubberband scrolling on iOS

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.

-webkit-overflow-scrolling: touch; not working on iPhone 6+ in Safari

IOS 8 does not read -webkit-overflow-scrolling: touch so you should also insert overflow:scroll to fix this problem on IOS 8.

in this case the versions which before 8 will read "-webkit-overflow-scrolling: touch" and the 8th version will read "overflow:scroll".

I faced the same problem and I fixed it in this way.



Related Topics



Leave a reply



Submit