Nesting The pointer-events Property In CSS
The pointer-events
CSS property can be used to disable mouse interactions on a given DOM (Document Object Model) element. When an element has pointer-events
set to none
, it instructs the browser to let the mouse events "fall through" the given DOM element and target / effect whichever next element is stacked below the user's cursor. I've barely ever used this particular CSS property because—for the most part—when I create a UI (User Interface) element, I want the user to have access to it. Yesterday, however, I learned that you can nest the pointer-events
property within the DOM tree. This creates an interesting confluence of interaction behaviors.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
By nesting elements with different pointer-events
settings, it allows us to use an outer element purely for layout purposes without disrupting the normal behavior of the page. Granted this is somewhat of a strange edge-case; but, consider positioning something in the center of the user's screen.
If we wanted to do this without blocking the background content, we might try to position the element at 50%
of the top
/left
; and then, use something like translate3D(-50%,-50%,0)
to move the element into the center. But, this is usually not a viable option due to the fact that a 50% translation will often create blurry, sub-pixel rendering.
CSS Flexbox makes centering elements significantly more simple. However, in order to use Flexbox to center an element on the screen, we'd have to create a fixed-position parent element that covers the entire viewport. Which, as a second order effect, would create a "mouse sink" that essentially blocks access to the background page.
And this is where pointer-events
comes into play. We can disable pointer-events on the Flexbox parent, allowing the user to click-through to the background page; and then, enable pointer-events on the Flexbox child in order to make the centered element interactive and user-consumable:
.flexbox-parent {
pointer-events: none ; /* No clicking! */
}
.flexbox-child {
pointer-events: auto ; /* Yes clicking! */
}
To see this in action, I've put together a demo in which there is a semi-modal element centered in the user's viewport. Both the background page and the semi-modal window have <button>
elements with a jazzy :active
state. I'm using CSS Flexbox to center the element; but, the Flexbox parent doesn't receive mouse events, which allows the user to click on the buttons in both the semi-modal window and the background page:
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="./main.css" />
<style type="text/css">
.semi-modal {
display: flex ;
inset: 0px ;
overflow: auto ;
overscroll-behavior: contain ;
position: fixed ;
z-index: 2 ;
/* The modal will NOT intercept mouse events. */
pointer-events: none ;
}
.semi-modal .content {
margin: auto ;
/* HOWEVER, the content of the modal WILL intercept mouse events. */
pointer-events: auto ;
}
</style>
<script type="text/javascript" src="../../vendor/alpine/3.13.5/alpine.3.13.5.min.js" defer></script>
</head>
<body x-data>
<h1>
Nesting The pointer-events Property In CSS
</h1>
<!--
This "modal" window is going to be overlaid on the page. However, the "backdrop"
of the modal window WILL NOT intercept clicks (and other mouse events) to the main
page content due to the pointer-events:none setting.
-->
<section class="semi-modal">
<!--
The content of the overlaid window WILL be clickable. Its pointer-events:auto
setting will override the parent settings; but, only for this local DOM branch.
-->
<div class="content">
<p> <button>Click Me</button> </p>
<p> This is kind of a modal,<br /> but not quite. </p>
</div>
</section>
<!-- Filler content for the page so that we have plenty of scrolling text. -->
<template x-for="i in 30">
<p>
<button>Click Me</button> Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Natoque penatibus et magnis dis parturient montes nascetur ridiculus.
Neque laoreet suspendisse interdum consectetur libero id faucibus nisl.
</p>
</template>
</body>
</html>
Notice that the modal parent has pointer-events: none ;
and the modal child has pointer-events: auto ;
. If we now render this page, we can see that the modal is visually centered; and, that the entire page remains responsive to the user's touch:
As you can see, while the semi-modal window remains visually centered and interactive, the user can still click-through to the background page, interact with its content, and scroll the viewport.
Aside: The modal window also has
overflow:auto
, which allows the modal window itself to be scrolled on small screens. This works fine everywhere except Safari. Safari won't scroll the modal via the scroll-wheel; however, it will allow the modal to be scrolled using the scrollbars.
Granted, I think this is a strange edge-case. But, this is an edge-case that I ran into; and, it's great to see that CSS pointer-events
can greatly reduce the complexity of the solution.
Want to use code from this post? Check out the license.
Reader Comments
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →