Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Ben Margolis
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Ben Margolis@tangon )

Using CSS Overscroll-Behavior To Prevent Scrolling Of Parent Containers From Within Overflow Containers

By Ben Nadel on
Tags: HTML / CSS

In the past, I've looked at how the scroll-wheel seems to randomly stop working in an overflow container. This phenomena is related to a browser feature called scroll chaining; and, it can be overcome if you prevent the wheel event's default behavior. Of course, tapping into the wheel and scroll events is not great for browser performance. Luckily, Derek Duncan stepped-in and told me about a CSS property called, overscroll-behavior. Supported in Chrome, Firefox, and Edge, this CSS property allows us to declaratively control what happens when an overflow container hits the edge of its scrollable content. This is a game changer!

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

If you have an overflow: auto element, the user can scroll the content contained within that element. However, when the user hits the top or the bottom of that content, the browser may start to scroll one of the ancestor elements, most commonly the body element. Once this "scroll chaining" occurs, subsequent use of the mouse-wheel may not be applied to the overflow: auto element; instead, the expression of the scrolling may continue to manifest in the ancestor element.

More often than not, this leads to an unexpected and undesired user experience (UX). Really, what we want to happen is to have the scrolling behavior always contained within the overflow: auto element. To do this (in Chrome, Firefox, and Edge), we can add the CSS property overscroll-behavior: contain to the overflow: auto element. This will prevent the "scroll chaining" behavior, which will, in turn, keep the mouse-wheel active within the target element.

To see this in action, I have two overflow: auto elements laid-over a scrollable body element. The container on the left has no modifying CSS properties while the container on the right has the CSS property overscroll-behavior: contain:

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>
		Using CSS Overscroll-Behavior To Prevent Scrolling Of Parent Containers From Within Overflow Containers
	</title>

	<style type="text/css">

		html {
			box-sizing: border-box ;
		}

		*, *:before, *:after {
			box-sizing: inherit ;
		}

		.layout {
			background-color: #ffffff ;
			border: 5px solid #cccccc ;
			height: 500px ;
			margin: -250px 0px 0px 0px ;
			position: fixed ;
			top: 50% ;
			width: 300px ;
		}

		.layout--a {
			right: 51% ;
		}

		.layout--b {
			left: 51% ;
		}

		.layout__top-panel {
			bottom: 100px ;
			left: 0px ;
			overflow: auto ; /* The panel becomes SCROLLABLE due to content overflow. */
			position: absolute ;
			right: 0px ;
			top: 0px ;
		}

		.layout--b .layout__top-panel {
			overscroll-behavior: contain ; /* Prevent SCROLL-CHAINING to parent elements. */
		}

		.layout__bottom-panel {
			border-top: 1px solid #cccccc ;
			bottom: 0px ;
			font-weight: bold ;
			height: 100px ;
			left: 0px ;
			padding: 20px 20px 20px 20px ;
			position: absolute ;
			right: 0px ;
		}

		.content p {
			margin: 0px 0px 0px 0px ;
			padding: 25px 20px 22px 20px ;
		}

		.content p:nth-child( even ) {
			background-color: #f0f0f0 ;
		}

	</style>
</head>
<body>

	<h1>
		Using CSS Overscroll-Behavior To Prevent Scrolling Of Parent Containers From Within Overflow Containers
	</h1>

	<!-- BEGIN: Layout-A. -->
	<section class="layout layout--a">
		<div class="layout__top-panel">

			<div class="content">
				<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
				<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
				<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
				<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
			</div>

		</div>
		<div class="layout__bottom-panel">

			No Behavior Modification

		</div>
	</section>
	<!-- END: Layout-A. -->

	<!-- BEGIN: Layout-B. -->
	<section class="layout layout--b">
		<div class="layout__top-panel">

			<div class="content">
				<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
				<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
				<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
				<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
			</div>

		</div>
		<div class="layout__bottom-panel">

			Using Overscroll-Behavior

		</div>
	</section>
	<!-- END: Layout-B. -->


	<!-- BODY content. -->
	<div class="content">
		<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
		<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
		<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
		<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
		<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
		<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
		<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
		<p>Content</p><p>Content</p><p>Content</p><p>Content</p><p>Content</p>
	</div>

</body>
</html>

Notice that the only difference is that the overflow: auto container on the right has the overscroll-behavior directive. Now, if we run this HTML and CSS code in the Chrome browser, we get the following output:

Overflow-behavior: contain prevents scroll chaining in overflow: auto element using CSS.

As you can see, when users scrolls to a local maxima within the scrollable container on the left, subsequent use of the scroll wheel causes the body element to scroll. This is "scroll chaining" in action. However, with the scrollable container on the right, which uses overscroll-behavior: contain, the scrolling is contained within the element - no "scroll chaining" occurs and the mouse wheel never affects the body scroll.

This is awesome! I will literally be shipping this to production today. The overscroll-behavior CSS property is perfect for things like rich HTML dropdown menus, modal windows, and fly-out panels. A true game-changer for creating a more positive user experience (UX) in my Angular, Single-Page Applications (SPA). Much thanks to Derek Duncan for learning me some new CSS hawtness!



Reader Comments

Awesome! I can use this TODAY! Love it when something like this falls right into my lap, solving a problem I didn't realize had a solution!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.