Search code examples
javascripthtmlcssleafletz-index

z-index not working as intended


I am currently working with Leaflet and Mapbox to create a custom map. All has been working fine until I started to work on the popups.

My situation: I have a custom navigation/control panel that will be used but it needs to be behind any open popups (which it currently is not).

I have prepared a JSFiddle and this screenshot

enter image description here

Part of my CSS is

#map-nav {
  position: absolute;
  left: 10px;
  top: 10px;
  z-index: 2;
}
.leaflet-popup-pane, .leaflet-popup {
  z-index: 1000 !important;
}

The default leaflet classes have z-index of 7 (I think), I just overwrote it to be 100% sure. When inspecting the code in browser (FF/Chrome), I see that the z-index is set properly and no other element in the .leaflet-popup has any z-index set.

I have read that negative z-index may solve this, but as I am working with multiple elements I'd really much like a less "hacky" solution - if there is one. (JavaScript solutions are welcome as well, hence the tag)

As I can see from the code inspection, Leaflet sets some attributes to .leaflet-popup which in terms of position are: transform: translate3d(..), bottom and left.

Any help is appreciated.

Edit:

I can put the #map-nav inside #map-content and it still won't work fiddle. However, when I try to imitate this without alle the map stuff, it does work.


Solution

  • The problem is that #map-nav exists in an entirely different stacking context than the marker and its popup.

    The way you have it set up right now, the entire map is behind #map-nav. So, changing the z-index on either #map-nav or the .leaflet-popup-pane won't really help.

    Disclaimer: This is a terrible, terrible hack. If you look into Leaflet's documentation and find a way to add your own widgets, I'd highly recommend doing that instead of this.

    That said, I have something which sort of works (your mileage may vary):

    1. Move #map-nav into .leaflet-map-pane
    2. Create a MutationObserver and observe changes to the attributes of .leaflet-map-pane.
    3. Whenever the style attribute of .leaflet-map-pane changes, grab its style.transform property and extract the x and y values. Invert those values and apply them to #map-nav.

    var map = L.map('map-content', {
      zoomControl: false
    }).setView([51.505, -0.09], 13);
    L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', {
      attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>',
      maxZoom: 18,
      id: 'rauschundrausch.cih3gisks00r8rlly0ob5s2me',
      accessToken: 'pk.eyJ1IjoicmF1c2NodW5kcmF1c2NoIiwiYSI6ImNpaTYyeW14MTAwNml2eW0zcmR6aHIzdDcifQ.6T8nzYq49rFnvcqk6lgYPg'
    }).addTo(map);
    
    var marker = L.marker([51.5, -0.09]).addTo(map);
    marker.bindPopup("this is a<br>large<br>popup<br>in<br>terms of height");
    
    var mapNav = document.getElementById("map-nav");
    
    var mapPane = document.querySelector(".leaflet-map-pane");
    
    var rxTranslate = /translate(?:3d)?\(([^\)]+)\)/i;
    
    mapPane.appendChild(mapNav);
    
    var observer = new MutationObserver(function(mutations) {
      if (mutations.some(m => m.attributeName === "style")) {
        // apply an inverse transform
        mapNav.style.transform = "translate(" + mapPane
          .style
          .transform
          .match(rxTranslate)[1]
          .split(",")
          .slice(0, 2) /* extract only x and y; discard z */
          .map(n => parseFloat(n) * -1 + "px") /* invert values */ + ")";
      }
    });
    
    observer.observe(mapPane, {
      attributes: true
    });
    #map {
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
    }
    #map-nav {
      position: absolute;
      left: 10px;
      top: 10px;
      width: 100px;
      height: 100px;
      background-color: #ccc;
      z-index: 200;
      box-shadow: inset 0 0 0 1px #f00;
    }
    #map-content {
      width: 100%;
      height: 100%;
    }
    .leaflet-popup-pane {
      z-index: 3;
    }
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
    <div id="map">
      <div id="map-content">
        <div id="map-nav"></div>
      </div>
    </div>

    This may lag on some clients while dragging. You could use a timeout to defer the update but that would mean the nav menu would move around until you stop dragging.