A simple stacking context problem HOWTO
Anton Baranov
Posted on October 22, 2021
So recently i was asked to solve this simple problem.
The problem goes as follows: given this markup, how can we make a click to div on a button area to trigger button's on click event, with out changing the markup (i.e. changing tags order is not allowed)?
<div id="app">
<button>button</button>
</div>
<div id="overlay"><div>
div#overlay {
position: absolute;
background: rgba(255, 85, 23, 0.5);
top: 0;
bottom: 0;
left: 0;
right: 0;
}
button {
position: relative;
margin: 10px;
width: 100px;
height: 30px;
border: none;
}
You might notice that div tag is positioned below button tag in DOM tree and has position: absolute property which means that it will always be displayed on top of a button.
So how can we solve this? There are at least 2 solutions that i know of.
Javascript solution
To solve this with just javascript, we would need to be familiar with couple of basic DOM api's, such as element.getBoundingClientReact() and mouse event.
First we would need to attach an event handler to root element's click event, next we will need to figure out if user is clicking on area, where our button sits under overlay and finally, if user does click in proper area, call button's on click event.
The key thing to note here, is that top left corner of viewport will always have coordinates of x = 0, y = 0, that's why we add width & height to relative axis, when validating click area.
const button = document.querySelector("button");
button.onclick = () => {
console.log("im clicked");
};
const { x, y, width, height } = button.getBoundingClientRect();
document.onclick = ({ clientX, clientY }) => {
const validY = clientY <= y + height && clientY >= y;
const validX = clientX <= x + width && clientX >= x;
return validX && validY && button.click();
};
CSS solution
If you are familiar with stacking context, you probably guessed already, that this problem can be solved using it. Here's MDN page on stacking context.
Here's what we gonna add to our css:
button:before {
content: "";
position: absolute;
display: block;
z-index: 1;
width: 100px;
height: 30px;
left: 0;
top: 0;
}
Since we'r using z-index & position property on :before element, it will create a new stacking context button's before element above the overlay.
One thing to note here: we could use position:relative on :before element, that would also work, but in that case, the :before node would not be positioned "above" button, but rather inside, moving all of its children nodes and we don't want that at all.
EDIT: as mentioned in comment section, we could have change z-index on a button itself, that would have created a new stacking context and would also work.
here's a link to a sandbox with both solutions.
Posted on October 22, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.