DEV Community

Cover image for A simple stacking context problem HOWTO
Anton Baranov
Anton Baranov

Posted on • Edited on

A simple stacking context problem HOWTO

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>
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
alohci profile image
Nicholas Stimpson

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.

Collapse
 
noneinnon profile image
Anton Baranov

Hey Nicholas,
I'll try to come up with a better example, showing the actual stacking context example working.

Thanks for pointing this out!

Collapse
 
afif profile image
Temani Afif

why not simply increasing the z-index of the button itself? jsfiddle.net/et52gp7c/

Collapse
 
noneinnon profile image
Anton Baranov

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.

Collapse
 
marselburdo profile image
Marsel

JS solution nice.

Collapse
 
noneinnon profile image
Anton Baranov

thanks!