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.
Top comments (6)
I don't think you've understood stacking contexts correctly. Both elements, and the ::before pseudo-element are all participating in the same stacking context. While it's true that the ::before pseudo-element is creating a new stacking context, that context isn't being used at all.
Hey Nicholas,
I'll try to come up with a better example, showing the actual stacking context example working.
Thanks for pointing this out!
why not simply increasing the z-index of the button itself? jsfiddle.net/et52gp7c/
Hey, thanks for your response!
You're right, we could have done so.
But while it might totally work and apply to a particular case, it might not work for other case, where you would not
button
itself to create a new stacking context.JS solution nice.
thanks!