-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Consider preventing page scroll when modal dialog is visible #7732
Comments
Thank you Scott for raising the issue here. If scrolling the underlying document is to be prevented, I think to be thorough it would also need to be prevented in any child scrollable element not in the modal dialog. Scott's demo includes an option, Enable A well-designed modal dialog should include whatever information is needed to complete the intended tasks within. Nevertheless, there could be situations where it's beneficial to users who can see the underlying document to be able to still scroll it: A product in a dialog shopping cart can be compared to products listed in the underlying page; while filling out a form in a dialog, someone with difficulty recalling information from the page may be able to scroll it into view. All of this is dependent on how visible the document is through the |
Is blocking scrolling something we should special case for I think forcing Special casing |
@nt1m good point about not wanting to special case, completely agree. There definitely could be benefit to doing this for other inert nodes, especially if they represent other types of 'popups' where focus navigation should be trapped to the popup so long as it is invoked - and thus accidentally scrolling the underlying document in those cases could either result in an unwanted dismissal of the popup, or a visual separation of the popup from its invoking element. But, maybe this is more an explicit opt in for authors? Whether that be an html feature to do so, or author guidance on how to do this to in a standardized way. I can think of both situations where I'd absolutely want this, and others where it'd be less than ideal. |
Just hit this issue and wondered what the current consensus is? Arrived here having done what I consider the 'common sense' thing and applied Is the current situation that there is no 'proper' way to prevent overscroll with dialog elements? |
This has also come up for me today. I followed Adam Argyles excellent article : https://web.dev/building-a-dialog-component/ Although it;s come back that they expected the whole page to scroll rather than content inside the modal/dialog. I guess the default behaviour is inert which blocks focus and click events. It could be argued you are no longer interacting with whats behind the modal so it shouldn't scroll. Also fromthe w3c
Adam just responded on twitter
|
CSS provides a way to do this: dialog {
overflow: auto;
overscroll-behavior: contain;
} This should work in browsers, but it doesn’t because they all have the same bug: w3c/csswg-drafts#3349 (comment). I think the best path forwards is for browsers to fix this bug. |
But will it work also, when the pointer is over the backdrop area and the mouse wheel is used? Also for scrolling with keyboard it doesn't seem to work at all. |
@pepkin88 When I do this: dialog, ::backdrop {
overscroll-behavior: contain;
} attempting to scroll while the cursor is over the backdrop:
Test page: https://output.jsbin.com/puwojoy/quiet I assume Chrome’s implementation is acceptable. In that case, we should investigate why the other browsers behave differently and maybe try to get them to align with Chrome. Regarding scrolling with the Arrow Up and Down keys while the dialog is open (same test page):
This seems to be a five-year-old Chromium bug: https://bugs.chromium.org/p/chromium/issues/detail?id=824555 |
Interesting. |
I can confirm. The same happens when hovering the ”tab stop” inputs. This could be another Chromium bug. I updated the test page from |
@simevidas can confirm using your https://output.jsbin.com/puwojoy/quiet test page the following when on Firefox (Linux):
|
This is a comment from the Accessible Platform Architectures (APA) Working Group. The APA WG supports this proposal; it would improve accessibility for people who are experiencing a range of vision and cognition barriers. In our discussion, one of our members (@AutoSponge) suggested that animations that are happening on the page behind the dialog should be paused; we feel that would help accessibility too. (That would be a separate discussion, but I mention it here to gauge interest; we'd be happy to file a new issue.) |
This is a very common issue in the webdev with dozens of hacks and no good solution. I was hoping that once we will be able to use native |
Want to add a +1 to this idea. Every time I've ever implemented modal dialogs I've had to add JavaScript to add overflow: hidden to the html element when open and remove it when closed. It'd be nice if this was automatically handled by dialog (or inert more generally) |
I tried to build with There is no robust reliable way prevent the issue (at least in Chrome). f.e.
The only way to do it is non-robustly: f.e. set The out-of-the-box behavior (at least in Chrome) is not a great UX. Is there a Chromium issue tracking this specifically? I didn't see one, but maybe I didn't search well enough. |
I’m linking the Chromium bug re https://bugs.chromium.org/p/chromium/issues/detail?id=813094 |
u can fix this scrolling with the following CSS in the global CSS file: |
Yes, it's a partial fix, but it's not perfect, because it hides the scrollbar, which may cause layout shifts. |
I can confirm layout shifts when the scrollbar is hidden, which I was hoping to get rid of using the native dialog component. I'm honestly surprised theres no way yet to control the scroll behavior. |
To fix that issue you can use this trick for now: .main-wrapper {
padding-left: calc(100vw - 100%);
} It applies the same padding on the left, as the scrollbar does on the right when present. Unfortunately most of the time you can not apply this directly to body, as full width children like navbars would no longer work out of the box. Hence the |
Something that came up from a discussion on this recently is this won't work if the dialog is inside a shadow tree as the has won't pierce the boundary. |
I will add, just because it is rarely mentioned, that this also may cause a jump to the vertical beginning of the document. If a model is opened further down the document and A reliable way to prevent scrolling in place in case that a |
I'm using this function I found in melt-ui, and it works great in my case |
Bit of a hack, but thought I'd share what. I ended up doing. body:has(dialog[open]) {
overflow: hidden;
} |
The |
This worked for me.
|
Clearly this feature is needed. Although, Like what are we waiting on for this to be standardised |
To be fair, the proper fix for this issue is known. Here are the steps we need to address this:
EDIT: Call me crazy, but I address this issue in my code by adding a little wrapper around my actual dialog content, making it scrollable with hidden scrollbar and then making my dialog content sticky inside so this scroll is not noticeable for the user. This way I mitigate all the issues above and lock scroll inside the dialog even if there's not enough content to overflow. |
Hi! Firefox engineer here. This comment prompted me to have a look at what it would take to fix this Firefox bug, and I realized something: when we rolled out our Site Isolation feature, we inadvertently changed our behaviour to align with the spec (i.e. respect We promptly got bug reports of the following form: "I'm scrolling an article with some Twitter embeds. If my mouse happens to land on top of a Twitter embed, I can no longer scroll the page (without moving the mouse away from the embed)", and had to patch this so we continued to ignore This is making me wonder whether it's realistic / web compatible to change this behaviour to align exactly with what the current spec says. Maybe we need to revisit the spec and have a carve-out for respecting |
@theres-waldo Would it be possible to avoid this issue by changing how the mouse cursor interacts with iframes in the following way:
I’m suggesting this because it’s a behavior that I wanted for a long time. Not just for iframes but also for elements with hover actions. For example, if I scrolled my Twitter timeline and after the scrolling stopped, my mouse cursor happened to end up being over a user avatar, Twitter would show the user info popup. That’s just annoying. I didn’t hover the avatar intentionally. I just scrolled the page. My point is that moving the cursor and scrolling the page are two different user actions with completely different intentions. If the mouse cursor ends up over an element after a scroll operation, there should be no action. So if the user scrolls the page and the mouse cursor is temporarily over an iframe, there should be no interaction with the iframe. Because the user did not intend to interact with the iframe. The user’s intention was to scroll the page, so the page should continue scrolling. |
Firefox actually does a time-limited version of this called "wheel transactions". Wheel events that occur in succession without a mouse-move in between are grouped into a "transaction", and the scroll target chosen for the first event in the transaction is propagated to subsequent events Transactions do "time out" after 1.5 seconds though, so if more time than that elapses between wheel events then the new event will pick up a new scroll target even if no mouse-move has occurred in between. So, what you describe already works if you continue scrolling relatively quickly after the mouse lands on the iframe. Did you envision this behaviour, but without a timeout (so, mouse lands on iframe, you get up from your desk to get a coffee, you come back and continue scrolling, and the browser still remembers that the page scrolled last so it should scroll rather than the iframe)? Or with a timeout that's significantly longer than 1.5s?
I frequently find accidental hovers like this to be annoying as well. However, in this case, our hands may be tied by the spec. If I understand correctly, such hovers are typically triggered by
|
This may simply be a matter of perspective then. You could posit that the pointer doesn't exist while panning/scrolling in the same way that it doesn't exist while dragging a scrollbar manually. |
GENIUS! That fixed it, sending thanks! The only slight issue still, is that this removes the scrollbars when the modal is open, so there is a brief layout shift whilst it opens. |
@theres-waldo The 1.5 second timeout sounds good. I’ve had problems in Firefox in the past where scrolling would stop immediately when the mouse cursor got over a specific element on the page, such as a Google map. Maybe that issue was fixed in the meantime. |
Ah, yeah, that's a known bug, and it's being fixed (bug 1888946). |
Maybe I please request in future that when there is a option, e.g. to allow content behind the backdrop to be scrolled or not scrolled, allow both behaviors to be achieved by somemeans. There are people on both sides of the fence who will need both situations. As web developers often we have our hands tied. The dialog and popover are prime examples of APIs/Element that could be so helpful, but due to various gotchas and assumptions, are not usable beyond toy examples. |
The top layer is a special case. Doesn't get much more special case than the top layer.
Well, how about we fix the bugs instead of treating them as features, and the spec progresses. Backwards compatiability is important, but when relying on certain bugs as features, thats going back to IE6/7/8/9 days. How about a new
|
I also just ran into this issue and was surprised there isn't a native way to prevent the background from scrolling (yet). The following function is inspired by Brad Wu's article here: https://css-tricks.com/prevent-page-scrolling-when-a-modal-is-open/ as well as the code from melt-ui here: https://github.com/melt-ui/melt-ui/blob/f15cd300735c1abe95286d32956dfe3b403f9f0d/src/lib/internal/helpers/scroll.ts#L39. I believe this should work for most use cases until we have a native solution. This function could be improved by storing the current CSS props/values of the body element before locking. You would then restore these props/values when unlocking. Call
|
@RubenAhlhaus your solution works great for me! The only change I'd make is |
Here some code to hide the browser scrollbar, it was taken from Bootstrap: // https://github.com/twbs/bootstrap/blob/33827d2/js/src/util/scrollbar.js#L37
function hideScrollbar() {
// https://github.com/twbs/bootstrap/blob/33827d2/js/src/util/scrollbar.js#L31
// Should be computed *before* body overflow is set to hidden
const width = Math.abs(
window.innerWidth -
// https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
document.documentElement.clientWidth
);
// https://github.com/twbs/bootstrap/blob/33827d2/js/src/util/scrollbar.js#L59
document.body.style.overflow = 'hidden';
// Give padding to element to balance the hidden scrollbar width
document.body.style.paddingRight = `${width}px`;
}
// https://github.com/twbs/bootstrap/blob/33827d2/js/src/util/scrollbar.js#L47
function resetScrollbar() {
document.body.style.removeProperty('overflow');
document.body.style.removeProperty('padding-right');
} |
This is what I do, looks similiar to the bootstrap solution @tkrotoff posted above: getScrollbarWidth() {
return window.innerWidth - document.body.clientWidth;
}
backupBodyStyles() {
this.overflowBak = document.body.style.overflow;
this.rightPadBak = document.body.style.paddingRight;
}
disablePageScroll() {
backupBodyStyles();
const computedStyles = getComputedStyle(document.body);
const rightPadNum = parseInt(computedStyles.paddingRight);
document.body.style.paddingRight = `${rightPadNum + this.getScrollbarWidth()}px`;
document.body.style.overflow = 'hidden';
}
enablePageScroll() {
document.body.style.overflow = this.overflowBak;
document.body.style.paddingRight = this.rightPadBak;
} |
Another UX/a11y improvement for the
dialog
element would be to ensure that, when in the modal state, the underlying document cannot be scrolled. This will help ensure that the invoking element for the dialog will remain in the visible viewport, and when focus is returned to the document, the viewport will not have to re-scroll to ensure this element is in view. This topic was originally surfaced by Curtis Wilcox on the web a11y slack channel.I have created a quick demo to show how the underlying document is presently scrollable while the modal dialog is visible: https://codepen.io/scottohara/full/YzYGpNy
One caveat I can think of with this idea is that if an author makes a dialog that is too large for the current viewport, then the underlying document will need to be able to scroll so as to allow someone to view all the content of the dialog.
Though, one could also argue that this could be, at least partially, mitigated by setting a max-height/width to the dialog so that it does not extend beyond the bounds of the browser viewport. Authors will need to do something like this to some degree to meet wcag success criterion 1.4.10: Reflow.
The text was updated successfully, but these errors were encountered: