Is there a Safari/WebKit bug for this?
foolip opened this issue · comments
I learned about this library as part of my work on https://insights.developer.mozilla.org/reports/mdn-browser-compatibility-report-2020.html, where one developer said this:
What gives me the most headaches is typically scrolling behavior of iOS. If I need to display a modal or something like a typical hamburger menu. I t annoys me that I need a JavaScript library just to get this right, so that if I get a modal, that the background doesn’t scroll on iOS. I think that’s basically the worst issue that you stumble across in almost every project. [...] The goal is always that you lock scrolling completely. There’s a good library for that, it’s not even large, it’s a small JavaScript dependency. But it annoys me that you need these workarounds for doing simple stuff like that.
Today I took a look at this repo to try to understand what difference between Safari/WebKit and other browsers makes this workaround necessary, but reading the different code paths depending on isIosDevice
I'm not sure I understand it.
I don't doubt that this library is needed and gets the job done, but would like to ask what change to WebKit or other browser engines would be required for it to no longer be necessary?
I ask because I'd like to make sure the appropriate bug is filed at https://bugs.webkit.org/, or on Chromium if changes are needed there too.
(I'm on the Google Chrome team, trying to make the web more interoperable so web developers can spend less time on workarounds.)
The difference between WebKit on iOS and all other browser engines is that it doesn't allow to reliably prevent body scrolling by setting overflow: hidden
to the body, as it should.
This is a very (very) long-standing issue with WebKit on iOS. Setting overflow: hidden
to the body used not to prevent body scrolling in any situation before recently (one or two years ago). The situation has now slightly improved, but remains quite unsatisfactory.
Based on my own (not thorough) tests, it now works when:
- the website is added to homescreen;
- the browser UI is visible.
However, it still does not work when:
- the browser UI is collapsed;
- the browser UI is hidden through the settings in Safari.
It also might not work with some metatags; when the keyboard is displayed; or in in-app browsers.
As you can gather, this is quite a mess. This is why libraries like this one, which use preventDefault()
on scroll-related events when necessary are still required and popular. This is far from being a great workaroung though, as they don't perfectly polyfill the overflow: hidden
behaviour:
- they don't prevent scroll initiated at the bottom of the screen when the browser UI is not visible (that zone is responsible for other problems in Safari);
- they use non-passive event listeners, which are bad for performance;
- they alter the overscrolling/rubberbanding behaviour;
- they can cause conflicts with pinch & zoom gestures.
There used to be several bugs filled on that issue, today only one remains: https://bugs.webkit.org/show_bug.cgi?id=214781, and does not cover all situations mentioned. The reason why there aren't more filled bugs being: I guess people got tired of filling bugs, testing fixes which only solve small parts of the problem, and arguing with the WebKit team.
Thank you for all the details, @brunostasse, that's great! Sounds like I should test various combinations of overflow: hidden
across browsers and see if I can find existing (failing) tests and browser bugs for differences, mostly WebKit bugs that is.
Assuming that overflow: hidden
worked perfectly across browsers, would JS still be required to prevent scrolling in the scenarios this library is usually used? Or is there also a CSS solution to this problem that's even further from working across browsers which would be the ultimate fix?
@foolip I imagine that overscroll-behavior
will be a great solution once widely supported.
@foolip There is at least one other case in which you'll find that overflow: hidden
is not working properly, that is in Firefox (at least in Android I think) when the page has the viewport metatag user-scalable=0
. In that case, body scroll is indeed prevented, but the body is also being scrolled to the top, which is not desired.
Regarding other scenarios where this library might still be useful if overflow: hidden
worked perfectly, to my knowledge there is only one: when you want to prevent body scroll initiated only on some elements. For instance, if you have a side menu or a bottom panel displayed only on part of the screen, you don't want scroll initiated inside this element to propagate to the body, BUT, you also don't want the body to be unscrollable if initiated outside of it.
I know of two ways to achieve that with CSS:
-
you can set
overflow: hidden
on mouseenter/touchstart on this element, and remove it on mouseleave/touchend. While this works well in Firefox and WebKit (in the case whereoverflow: hidden
does prevent body scrolling), it doesn't work properly in Chromium on touch devices (Android & Windows). I don't think we can say that FF and WebKit are right and Chromium wrong in behaving like that, as I don't think what should happen in that case is spec'ed anywhere. -
Another way of handling this is using
overscroll-behavior: contain
, as mentioned by @diachedelic. This is again not a silver bullet at the moment for two reasons though:- I noticed what appears to be a bug in Chromium, where if you scroll inside a container with
overscroll-behavior: contain
, and then scroll immediately the body, the body doesn't get scrolled, instead you keep scrolling the container. This can be mitigated by only applying it on mouseenter/touchstart and removed on mouseleave/touchend. - This only works when the scroll is initiated in scroll containers, that is elements with content overflowing and the
overflow
property set to eitherauto
orscroll
. If you want to prevent body scroll while scrolling over an element that is not scrollable, then it won't work, you'll be scrolling the body.
- I noticed what appears to be a bug in Chromium, where if you scroll inside a container with
I am not aware of all the scroll related specs, so I am not sure if this makes sense, but maybe overscroll-behavior
could be made to have effect even for elements with overflow
values equal to scroll
, auto
or hidden
, no matter if there is content overflowing or not. Considering the fact that elements with overflow: hidden
are still programmatically scrollable, as opposed to those with the values clip
or visible
, that would make sense to me. That way overscroll-behavior: contain
would indeed be a perfect way of preventing body scroll in all scenarios.
iOS seems to support overflow:hidden on body now, can it be added in?