Make pointer events bubble through overlapping grid items

Viewed 336

In my application I have two grid items that overlap. In consequence, pointer events such as click or hover are only triggered on the upper element (according to z-index or order of appearance).

I am wondering if there is a way (react/js or css wise) to make both items detect the pointer events or at least simulate that hehavior:

Here is my example:

export default function App() {
  function onClick() {
    console.log("click");
  }

  return (
    <div className="grid">
      <div className="item-1" />
      <div className="item-2" onClick={onClick}>
        Click
      </div>
    </div>
  );
}
.grid {
  display: grid;
  grid-template-columns: 100px 100px 100px;
}

.item-1 {
  grid-row: 1;
  grid-column: 1/-1;
  background-color: deeppink;
}

.item-1:hover {
  background-color: lime;
}

.item-2 {
  grid-row: 1;
  grid-column: 2;
  border: 1px solid black;
  cursor: pointer;
}

function App() {
  function onClick() {
    console.log("click");
  }

  return (
    <div className="grid">
      <div className="item-1" />
      <div className="item-2" onClick={onClick}>
        Click
      </div>
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById("root")
);
.grid {
  display: grid;
  grid-template-columns: 100px 100px 100px;
}

.item-1 {
  grid-row: 1;
  grid-column: 1/-1;
  background-color: deeppink;
}

.item-1:hover {
  background-color: lime;
}

.item-2 {
  grid-row: 1;
  grid-column: 2;
  border: 1px solid black;
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

<div id="root"></div>

See this codesandbox.

item-1 changes its color on hover and item-2 triggers a function on click. Obviously, when hovering over item-2, it captures the hover event and item-1 doesn't change it's color. What I'm looking for is a way such that hovering (and clicking) on item-2 is also redirected to item-1 or is somehow "bubbling" through both elements.

What I don't want to do is to change the html-structure, i.e. the two elements must be parallel children of a grid container.

1 Answers

For click:

There is a way to simulate this. It's best to look at it in this code sandbox, but the principle is easy.

Register a click event handler on the upper element. In the handler, do whatever you need to do and decide if you want the click to go through to the lower element. If yes, then apply pointer-events: none to the upper element and call document.elementFromPoint(event.pageX, event.pageY).click(). This will search for the element at the position of the original click and since upper element is out of the way, will return the lower element or whatever is inside the lower element at that position and then simulate a click. Then simply apply pointer-events: initial to restore everything.

Since DOM elements naturally bubble you can attach the upper event handler at the top of the "upper structure" and cancel on an inner event handler. So it retains normal event semantics as far as I can tell.

document.getElementById("upper").addEventListener("click", (event) => {
  document.getElementById("upper").style.pointerEvents = "none";
  document.elementFromPoint(event.pageX, event.pageY).click();
  document.getElementById("upper").style.pointerEvents = "initial";
});

The only caveat is, that the lower element will only receive the fake click event, so properties like modifier keys and position won't be available. If you need them, it's best to pass this data separately.

For hover:

EDIT: I've thought about the hover part and came up with this solution:

Add a mousemove and mouseout handler to the upper element and check in the mousemove if the pageX / pageY coordinates are within the bounding box(es) of the other element(s) using getBoundingClientRect. If they are, add a class to the other element(s) to indicate that they're being hovered. Then add matching hover class selectors where there are :hover selectors. Clear the hover classes on mouseout (and unmount, etc.).

I modified your sandbox to show the hover method.

This method has a caveat as well, though. If you create a new element on top of the upper one, then mouseout won't be called. So keep that in mind.

Related