Search code examples
javascripthtmlcssmouseeventdom-events

Prevent absolutely positioned div from swallowing click events without disabling scrollbar


Version 1

Code

The following code is (a simplified version of) the layout I'm using in a project :

document.getElementById("map").addEventListener("click", function(e) {
  alert("Map clicked!");
});

document.getElementById("control1").addEventListener("click", function(e) {
  alert("Control 1 clicked!");
});

document.getElementById("control2").addEventListener("click", function(e) {
  alert("Control 2 clicked!");
});
body, html {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0;
}

.map {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background: #0c0;
  padding: 10px;
}

.controlgroup {
  position: absolute;
  top: 10px;
  bottom: 10px;
  right: 10px;
  width: 200px;
  overflow: scroll;
  overflow-y: auto;
  overflow-x: hidden;
}

.control {
  padding: 10px;
}

#control1 {
  background: #c00;
}

.control {
  background: #00c;
}
<div class="map" id="map">
  MAP
</div>
<div class="controlgroup">
  <div class="control" id="control1">
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
  </div>
  <div class="control" id="control2">
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
  </div>
</div>

Problem

This works as expected, except when the height of my browser is greater than the height of my control group.

Then, clicks below the controls are swallowed by my control group instead of getting passed to the map.


Version 2

Code

A naive fix of version 1 would be to add a pointer-events property to my CSS to ignore clicks on the control group and then reenable them for the control :

document.getElementById("map").addEventListener("click", function(e) {
  alert("Map clicked!");
});

document.getElementById("control1").addEventListener("click", function(e) {
  alert("Control 1 clicked!");
});

document.getElementById("control2").addEventListener("click", function(e) {
  alert("Control 2 clicked!");
});
body, html {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0;
}

.map {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background: #0c0;
  padding: 10px;
}

.controlgroup {
  position: absolute;
  top: 10px;
  bottom: 10px;
  right: 10px;
  width: 200px;
  overflow: scroll;
  overflow-y: auto;
  overflow-x: hidden;
  pointer-events: none;
}

.control {
  padding: 10px;
}

#control1 {
  background: #c00;
}

.control {
  background: #00c;
  pointer-events: auto;
}
<div class="map" id="map">
  MAP
</div>
<div class="controlgroup">
  <div class="control" id="control1">
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
    This is control 1<br />
  </div>
  <div class="control" id="control2">
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
    This is control 2<br />
  </div>
</div>

Problem

Unfortunately, this breaks the behavior of my the control group's scrollbar. Now I'm not longer able to scroll my control group with the scrollbar.


Question

In my actual project, the actual content of the controls and the number of controls is different for different URLs, but the CSS code underneath is charged for all controls and control groups. Also, the controls in my actual project are generated programmatically by JavaScript and certain controls can be hidden or shown based on user input.

So I'd rather not hardcode the height of my control group based in the total height of all the controls, as that would mean programmatically calculating the height after all controls have been loaded and then recalculating the height every single time a control is added/removed. I would like to avoid that, but I'm struggling coming up with a way to do so!

Considering these criteria, what should I do to prevent my control group from swallowing up clicks that are supposed to be applied to the map without disabling its scrollbar?

I'd prefer a CSS-only solution, but I can live with a JS based solution.


Solution

  • You can remove bottom:10px from .controlgroup, this will make controlgroup's height auto so it won't cover the entire height of page, and then add max-height: calc(100% - 20px // Reduce space from top and bottom 10px + 10px );

    document.getElementById("map").addEventListener("click", function(e) {
      alert("Map clicked!");
    });
    
    document.getElementById("control1").addEventListener("click", function(e) {
      alert("Control 1 clicked!");
    });
    
    document.getElementById("control2").addEventListener("click", function(e) {
      alert("Control 2 clicked!");
    });
    body, html {
      position: relative;
      width: 100%;
      height: 100%;
      margin: 0;
    }
    
    .map {
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      background: #0c0;
      padding: 10px;
    }
    
    .controlgroup {
        position: absolute;
        top: 10px;
        /* bottom: 10px; */
        right: 10px;
        width: 200px;
        overflow: scroll;
        overflow-y: auto;
        overflow-x: hidden;
        max-height: calc(100% - 20px);
    }
    
    .control {
      padding: 10px;
    }
    
    #control1 {
      background: #c00;
    }
    
    .control {
      background: #00c;
    }
    <div class="map" id="map">
      MAP
    </div>
    <div class="controlgroup">
      <div class="control" id="control1">
        This is control 1<br />
        This is control 1<br />
        This is control 1<br />
        This is control 1<br />
        This is control 1<br />
        This is control 1<br />
        This is control 1<br />
      </div>
      <div class="control" id="control2">
        This is control 2<br />
        This is control 2<br />
        This is control 2<br />
        This is control 2<br />
        This is control 2<br />
        This is control 2<br />
        This is control 2<br />
      </div>
    </div>