Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scrolling on Mobile Devices #788

Open
mtom55 opened this issue Oct 6, 2024 · 3 comments
Open

Scrolling on Mobile Devices #788

mtom55 opened this issue Oct 6, 2024 · 3 comments
Labels
focus-area-proposal Focus Area Proposal

Comments

@mtom55
Copy link

mtom55 commented Oct 6, 2024

Description

The inability to reliably block body scroll in Safari/Webkit and have a consistant scrolling experience across browsers on mobile devices is a very significant long-standing issue which greatly increases the difficulty of building advanced user interfaces and remains a significant compatibility issue between browsers.

This is a repeat of last year’s interop request. Given that the majority of issues remain unresolved, we would like to re-raise it as a candidate for interop 2025.

The desired outcome for this focus area would be a set of tests that cover the behaviour, and to ensure that it works consistently across all the major browsers.

Most important bug reports:
https://bugs.webkit.org/show_bug.cgi?id=240859
https://bugs.webkit.org/show_bug.cgi?id=240860
https://bugs.webkit.org/show_bug.cgi?id=237961
Specification:
https://drafts.csswg.org/css-overflow/#overflow-properties
Tests:
None as yet (as far as we know)

Safari/WebKit - Body Scroll Issues

A Deep Dive into a Key Developer Pain Point:

  1. Introduction
  2. Use Cases
  3. Technical Details
  4. When and Why this Doesn’t Work
    4.1. Incomplete Fix of the Initial Bug
    4.2. “overflow: hidden” purposefully made ineffective in Safari iOS when the browser UI is collapsed
    4.3. “overflow: hidden” purposefully made ineffective when the visual viewport is smaller than the layout viewport
  5. How to fix: Summary
  6. Conclusion

1. Introduction

For more than 8 years, one of the biggest issues Web Developers have had to deal with in Safari and other WebKit-based browsers on iOS (and in a lesser extent on macOS), is the inability to reliably block body scroll, or, said in other words, to remove the ability to scroll the current page in certain situations.

2. Use Cases

Such behaviour is needed in many situations where the user should not be able to scroll the page, whether on purpose, or by accident. It is essential when producing specific types of User Interfaces.
Specific examples of when it is needed include:

  • when displaying temporarily any kind of overlay over the page: a popup, a centred dialog, a bottom sheet, a sidebar navigation, a top menu, an entire page on top of the page, etc.;
  • for use cases where relying on page scroll is not possible, as more control over the scroll container is needed, and therefore element scroll containers need to be used instead;
  • when implementing advanced gestures animations which should not trigger body scroll while occurring;
  • when implementing high-fidelity drag and drop interfaces;
  • when developing components that have their own scroll and zoom functionality (e.g. a map component or an E.R. Diagram Visualizer).

As you can see, there are plenty of use cases where such a feature is required. Yet, over the past 8 years, WebKit has not allowed developers to safely rely on it, as it has been broken in many ways. A search with the terms “disable body scroll safari” will reveal that a large number of developers have been grappling with this issue.

3. Technical Details

Blocking body scrolling is achieved by setting the value “hidden” to the CSS property “overflow” on the HTML element. Doing so should prevent further manual scrolling by the user, while maintaining the current scroll position, and still allowing programmatic scrolling.

This is per the “CSS Overflow Module Level 3” draft: “hidden: This value indicates that the box’s content is clipped to its padding box and that the UA must not provide any scrolling user interface to view the content outside the clipping region, nor allow scrolling by direct intervention of the user, such as dragging on a touch screen or using the scrolling wheel on a mouse.”
And had been validated by Simon Fraser from WebKit here: w3c/csswg-drafts#666 (comment)

The recently added “overflow” value “clip” should also prevent manual scrolling, as well as any programmatic scrolling. When setting back the value of “overflow” to “auto” or “scroll”, body scrolling should be re-enabled.

Although there were some initial concerns about making content overflowing the body inaccessible when this behaviour was introduced, the behaviour is now widely known, accepted and used by web authors in other browsers. It is all the more an issue since this works as intended in Firefox and all Chromium-based browsers since 2016, and has been an interoperability issue between them and Safari since then.

4. When and Why this doesn’t Work

Over the years, several bugs have prevented developers from disabling body scrolling. Some have been fixed, some haven’t and some new ones have appeared. The oldest WebKit issue related to this problem dates back to February 2016:

  • Bug 153852 (FIXED) - with overflow:hidden CSS is scrollable on iOS

Many other issues related to this problem have been filed after that:

  • Bug 220908 (FIXED) - with overflow:hidden CSS is scrollable on iOS standalone web app
  • Bug 214781 (DUPLICATE FIXED) - Inconsistent scroll behavior when using overflow:hidden on body if added to home screen
  • Bug 222654 (NEW) - Scrolling in home screen apps incorrectly latches to document
  • Bug 236561 (NEW) - REGRESSION?: overflow:hidden on documentElement behaves inconsistently in iOS Safari 15.3.1
  • Bug 237961 (NEW) - Standalone with viewport-fit cover causes overscroll issues, breaks position fixed and -webkit-fill-available
  • Bug 240859 (NEW) - <body> with overflow: hidden CSS is scrollable on iOS when Safari’s UI is collapsed
  • Bug 240860 (NEW) - <body> with overflow: hidden CSS is scrollable when the visual viewport is smaller than the layout viewport

The situations where this works and where it doesn’t have changed several times over past WebKit/Safari versions, and these situations depend on multiple factors so it is hard to list them. However, the reasons why this doesn’t work properly have been outlined over time in the aforementioned WebKit issues. Problems are related to either:

  • incomplete fix of the initial bug;
  • “overflow: hidden” purposefully made ineffective in Safari iOS when the browser UI is collapsed (bug 240859);
  • “overflow: hidden” purposefully made ineffective when the visual viewport is smaller than the layout viewport (bug 240860).

In addition to these three reasons, bugs related to them make things even worse.

Let’s dig into each problem:

4.1. Incomplete fix of the initial bug

Considering the fix to Bug 153852, excluding the situations where the two other problems come in, body scroll should be prevented. However, as we can see with Bug 237961 and Bug 222654 in standalone mode on iOS (that is when a website is added to the homescreen), the fix doesn’t work.

Bug 237961 seems to be fixed but has yet to be confirmed by WebKit. It’s worth noting that it is only “overscrolling” which seems to have issues here, and not actual scrolling. Still, it should not happen if “overflow: hidden” was working effectively.

4.2. “overflow: hidden” purposefully made ineffective in Safari iOS when the browser UI is collapsed

This issue is covered by bug 240859.

This exception concerns Safari on iOS and is the one causing the most problems as it is the most frequent situation for users to be in.

In Safari on iOS, the browser UI can be in two possible states: collapsed, or revealed. To go from collapsed to reveal, you can either scroll up, or tap on the collapsed UI. To go from revealed to collapsed, you can either scroll down, or go through Safari’s settings and use the option “Hide toolbar”. Note that in the latter case the only option to then reveal the UI is to tap on it.

In order to always allow the browser UI to be revealed by a scroll up (except when it has been collapsed through the “Hide toolbar option”), the decision has been made by the Safari team to not allow “overflow: hidden” to prevent body scrolling when the browser UI is collapsed. As mentioned by Simon Fraser in the initial bug report: This is by design; scrolling is necessary to reveal the browser UI. This could be improved in future (bug 153852). (Note that “overflow: hidden” is also made ineffective as a consequence of this exception when the browser UI has been collapsed through the “Hide toolbar” option, although it then cannot be revealed by a scroll up.)

As mentioned in the WebKit issue another solution would allow Safari’s UI to collapse/reveal without interfering with body scrolling. Specifically, leaving Safari’s UI in its current state when “overflow: hidden” is set. If it is collapsed, let the user tap on it to expand it (as already possible).

4.3. “overflow: hidden” purposefully made ineffective when the visual viewport is smaller than the layout viewport

This issue is covered by bug 240860.

As indicated by Simon Fraser: We allow scrolling when the visual viewport is smaller than the layout viewport (this is necessary to avoid getting locked in when zoomed), but we allow scrolling to the entire document. Ideally we'd only allow panning around in the layout viewport (bug 236561).

To our knowledge, this exception to the correct behaviour for “overflow: hidden” materialises in two situations:

  • when a virtual keyboard is shown (on iOS);
  • when the page is zoomed (on iOS and macOS).

In both these situations, making “overflow: hidden” ineffective to block body scrolling is not the correct behaviour.

The correct behaviour would be to stop making that exception, and indeed block body scrolling when the visual viewport is smaller than the layout viewport. Therefore, when a virtual keyboard is shown, no scrolling would be possible, as expected. However, as mentioned by Simon Fraser above and by David Bokan in the initial bug report, if the page is zoomed, the user should be able to scroll around inside the area which would be visible if the page was not zoomed, without scrolling the page further.

Implementing this would not only help solve the body scroll blocking problem, it would also fix an interoperability issue, as both Firefox and all Chromium-based browsers behave that way, on all platforms (mobile and desktop).

5. How to fix: summary

To summarise, here is what needs to be done to fully fix the body scroll issue in Safari/WebKit:

  • fix bug 237961 and other specific bugs that could arise;
  • remove the exception preventing body scroll blocking when Safari’s UI is collapsed (bug (bug 240859);
  • remove the exception preventing body scroll blocking when the visual viewport is smaller than the layout viewport and, only when the page is zoomed, implement scrolling inside the layout viewport clipped to its scroll position (bug 240860);
  • add tests that check if (over)scrolling is working correctly to identify any lingering issues and prevent future regressions.

6. Conclusion

This issue has been mentioned countless times by web developers as one of the major issues with Safari over the past 8 years. Fixing it would bring many benefits to web developers, web users and Safari. Specifically, it would eventually allow developers to drop heavy JavaScript libraries and build good user experiences for very common patterns, such as sidebars, dialogs, bottom sheets and many others.

Specification

https://drafts.csswg.org/css-overflow/#overflow-properties

Additional Signals

Dozens of articles have been published on the subject:
https://medium.com/@wangyazh0u/disable-body-scroll-on-mobile-safari-21270f61f9db
https://markus.oberlehner.net/blog/simple-solution-to-prevent-body-scrolling-on-ios/
https://pqina.nl/blog/how-to-prevent-scrolling-the-page-on-ios-safari/
https://medium.com/react-camp/how-to-fight-the-body-scroll-2b00267b37ac
https://dev.to/snowleo208/how-to-fix-popups-scrolling-on-safari-3og6
https://alanocoder.com/prevent-body-scrolling-safari-css-js/
https://futurefoundry.co/blog/disable-body-scroll-ios/
https://css-tricks.com/prevent-page-scrolling-when-a-modal-is-open/

Dozens of questions have been asked on forums:
https://stackoverflow.com/questions/3047337/how-to-disable-scrolling-on-mobile-safari?noredirect=1&lq=1
https://stackoverflow.com/questions/77345537/body-overflowhidden-equivalent-for-ios?noredirect=1&lq=1
https://stackoverflow.com/questions/41594997/ios-10-safari-prevent-scrolling-behind-a-fixed-overlay-and-maintain-scroll-posi?noredirect=1&lq=1
https://stackoverflow.com/questions/59193062/how-to-disable-scrolling-on-body-in-ios-13-safari-when-saved-as-pwa-to-the-hom
https://stackoverflow.com/questions/16889447/prevent-full-page-scrolling-ios?rq=3
https://stackoverflow.com/questions/56351216/ios-safari-unwanted-scroll-when-keyboard-is-opened-and-body-scroll-is-disabled?rq=3
https://www.reddit.com/r/reactjs/comments/xhjnuc/how_to_lock_body_scroll_properly_on_ios_when_a/
https://forum.bootstrapstudio.io/t/prevent-over-scrolling-on-safari-and-ios/9090
https://discourse.webflow.com/t/prevent-body-scrolling-on-mobile-navigation-open/61298

Dozens of libraries have been built to work around (poorly) the problem, adding many kilobytes of JS to millions of websites and web apps:
https://www.npmjs.com/package/body-scroll-lock
https://www.npmjs.com/package/@custom-react-hooks/use-lock-body-scroll
https://www.npmjs.com/package/@os-design/use-body-scroll
https://www.npmjs.com/package/body-scroll-lock-upgrade
https://www.npmjs.com/package/body-scroll-lock-enhanced
https://www.npmjs.com/package/@michellocana/react-scrolllock
https://www.npmjs.com/package/@zag-js/remove-scroll
https://www.npmjs.com/package/@js4y/lock-scroll
https://www.npmjs.com/package/tua-body-scroll-lock

Please also see rejhhadella's comment

@mtom55 mtom55 added the focus-area-proposal Focus Area Proposal label Oct 6, 2024
@rejhgadellaa
Copy link

Some related links that show interest for being able to reliably prevent scrolling:

Consider preventing page scroll when modal dialog is visible

The workaround mentioned here does not reliably work on iOS Safari (for example, when the visual viewport is smaller than the layout viewport), since it uses overflow: hidden:

body:has(dialog[open]) {
  overflow: hidden;
}

body-scroll-lock

This NPM package has 824,257 weekly downloads

@theres-waldo
Copy link

theres-waldo commented Oct 23, 2024

  • remove the exception preventing body scroll blocking when the visual viewport is smaller than the layout viewport and, only when the page is zoomed, implement scrolling inside the layout viewport clipped to its scroll position (bug 240860);

Just to clarify my understanding of the current practice in Chromium and Firefox: the visual viewport should always be scrollable within the layout viewport, whether the reason for the visual viewport being smaller than the layout viewport is that you're zoomed in, or that the keyboard is shown.

The keyboard case (which was recently fixed in Firefox) is important because otherwise, the keyboard could cover up and make inaccessible something in the bottom half of the layout viewport that's important for the user to see / interact with while they're typing.

@brunostasse
Copy link

brunostasse commented Oct 24, 2024

If I may help clarify this further:

The way this behaves in Chromium and Firefox is that when the <body> element has overflow: hidden and the visual viewport is smaller than the layout viewport (as a result of pinch-zooming or the on-screen keyboard being presented) then the visual viewport becomes a scroll container inside of which the layout viewport can be scrolled.

As an example, if you set overflow: hidden on the <body> element on github.com, you cannot scroll the page at all. But if you pinch-zoom in Chromium or Firefox, you'll be able to then scroll around in order to see the part of the layout viewport which are now hidden as a result of the pinch-zoom, but no further.

Safari behavior is more simplistic. Instead of creating a scroll container in such situation, it makes the overflow: hidden on the <body> element ineffective. That way, you end up being able to scroll the entire page, which includes the hidden part as a result of the pinch-zoom, but also the rest of the page, which you shouldn't be able to see. The behavior is the same when the on-screen keyboard is presented.

This causes a variety of issues, because quite often we need to block body scrolling while displaying a fixed dialog on top of the page. In that case, the user should not be able to scroll the page underneath. However, in Safari, as soon as the user pinch-zooms or displays the on-screen keyboard, all scroll gestures performed over the fixed dialog end up causing scroll on the whole page, creating a terrible experience.

Chromium and Firefox behavior is much more reasonable, and it would be good to see Safari align on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
focus-area-proposal Focus Area Proposal
Projects
Status: No status
Development

No branches or pull requests

4 participants