Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS `overscroll-behavior: contain` when target element doesn't overflow

I am looking for a CSS solution to implement the exact behavior of "overscroll-behavior: contain" but for when the target element has no overflow.

I have a page with a pop-in sidebar/menu that, on mobile, takes up 100vw and 100vh (minus bottom navigation bar) and does not overflow (there is not enough content to need scrollbars). Currently when this sidebar is open on mobile, if the user tries to scroll it, the main page in the background scrolls, which in this app can result in unwanted database calls due to lazy loading/infinite scroll.

Here is a minimal codesandbox demo:

Edit Consecutive Material-UI Snackbars

On line 55 of demo.js I have added the overscrollBehavior: "contain" property to the JSS, but as you can see, it does not contain the scroll chain, unless you shrink the vertical height of the browser to force the content of the sidebar to overflow first. (On Chrome the overscrollBehavior seemed to have the expected behavior in the codesandbox editor, but not when popped out in its own window.)

Surely there is a CSS solution to get this behaviour without the element having to be scrollbable first?

Interestingly, on Firefox at least, if you shrink the vertical height to force scroll on the side bar, once you resize the browser back to normal, the overscroll-behavior property continues containing the scroll chain until you refresh the page, which is the behaviour I'm looking for, though obviously on initial page load.

Here is a simple codepen showing the difference in overscroll-behavior for elements which do and do not overflow, if it's not already clear. I also found another post on the CSS Tricks forum from 2018 of someone asking about this behaviour, but with no solution.

like image 397
benmneb Avatar asked Sep 11 '25 10:09

benmneb


2 Answers

A nice CSS-only workaround for this problem is to set the ::before pseudo-element to 101% of the div's height, which ensures that the div has some overflow. This does come at the cost of a few pixels' worth of scroll wiggle, but I have found that this works very well on touch devices because the user just sees an overscroll bounce. And in browsers that don't do overscroll, the slight wiggle-scroll can be a very acceptable compromise depending on where it's used.

Example:

div { 
  overflow: scroll; 
  overscroll-behavior: contain; 
  height: 300px;
  background-color: lightgray;
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
}

div::before { 
  content: '';
  display: block;
  position: absolute;
  height: 101%;
  width: 100%;
  top: 0;
  z-index: -1;
}
<div>Overscroll is contained</div>
like image 84
Shawn Avatar answered Sep 14 '25 00:09

Shawn


Now that the functional :has() pseudo-class is relatively good supported for ~90% of users, we can use it create a somewhat similar functionality.

Note: From your codepen example, I gave the second <p> the class .scrollContain for easier selection.

body:has(.scrollContain:hover) {
  overflow-y: hidden;
}

What's happening here?

  • body:has(.scrollContain:hover): Selects the body element, but only if it has a child element of class .scrollContain which is being hovered. (Note: you can replace body with any other element that should not scroll)
  • overflow-y: hidden in order to disable scrolling on body element

The functionality is not 100% the same, as the outer elements scroll is completely disabled, therefore the scrollbar will disappear. But the behaviour of the scroll itself is the same. As soon as one is overscrolliing the inner element (therefore hovering with the mouse over it), the outer element will not scroll.

Additional note: Using position: fixed together with overflow: scroll would also disable scrolling while forcing the scrollbar to still be shown (although disabled). But in most cases the body will jump since it's removed from the normal page flow. But this might work in some cases where the outer container is already fixed with a given top position.


body:has(.scrollContain:hover) {
  overflow-y: hidden;
}

p {
  height: 180px;
  width: 100px;
  overflow: scroll;
  margin: 20px;
}

p:nth-child(2) {
  overscroll-behavior: contain;
}

body {
  display: flex;
  padding: 20px;
  min-height: 200vh;
}
<p>
  <strong>Default!</strong> Lorem ipsum, dolor sit amet consectetur adipisicing elit. Fugiat placeat qui praesentium. Earum itaque architecto laboriosam voluptates beatae dolore molestiae quas magni quo non vel doloribus, accusamus repellendus quaerat aut!
</p>
<p>
  <strong>Contain! (overscroll)</strong> put more text here to see it stop the scroll chain when it overflows
</p>
<p class="scrollContain">
  <strong>Contain! (:has)</strong> put more text here to see it stop the scroll chain when it overflows
</p>
<p class="scrollContain">
  <strong>Contain! (:has)</strong> Lorem ipsum, dolor sit amet consectetur adipisicing elit. Fugiat placeat qui praesentium. Earum itaque architecto laboriosam voluptates beatae dolore molestiae quas magni quo non vel doloribus, accusamus repellendus quaerat aut!
</p>
like image 41
Mugentoki Avatar answered Sep 14 '25 01:09

Mugentoki