Leaflet Tiles – How to Fix Loading Issues for Multiple Bootstrap Tabs

bootstrap-frameworkleaflet

I am making a web app for different areas using Leaflet library. I have different Bootstrap tabs, each tab opens a new map of a new area.

The problem I am facing is that the tiles on the first tab load but the tiles on the second tab do not. Where am I wrong?

This is how I am loading my maps for now…

/* MAP LAYERS */
var map = L.map('mapid',{
        zoomControl: true,
}).setView([24.2, 67.8], 9);

/* LOADING AND ADDING BASE MAPS */
var baseMap = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
                    attribution: '&copy; <a href="https://carto.com/attributions">CARTO</a> contributors',
}).addTo(map);

/* MAP LAYERS */
var map1 = L.map('map_id',{
        zoomControl: true,
}).setView([22.2, 63.8], 9);

/* LOADING AND ADDING BASE MAPS */
var baseMap = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
                    attribution: '&copy; <a href="https://carto.com/attributions">CARTO</a> contributors',
}).addTo(map1);

My tabs:

<div  id = "locations">
                <ul class="nav nav-pills nav-fill" role="tablist">
                    <li class="nav-item">
                        <a class="nav-link active" href="#tabA" data-toggle="pill" role="tab">I</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#tabB" data-toggle="pill">J</a>
                    </li> 

                </ul> 
            </div> <!-- End Tabs -->

The tab linkage:

<div class="tab-pane fade show active" id="tabA" role="tabpanel"> <!-- First Tab -->
<div class="col-6" id = 'map_grid'> <!-- Maps -->
                                <div id ="mapid"> </div> <!-- Div for the Maps -->
                            </div> 
   </div>


<div class="tab-pane fade" id="tabB" role="tabpanel"> <!-- Second Tab -->
<div class="col-6" id = 'map_grid'> <!-- Maps -->
                                <div id ="map_id"> </div> <!-- Div for the Maps -->
                            </div> <!-- End Maps -->
</div>

Best Answer

When Leaflet map is displayed in it's <div> element, map's size is calculated on the basis of the <div> element size at the moment the map is added. If element is hidden, as it is the case of Boostrap tab above, <div> element has no size and consequently map has no size when tab is displayed.

Leaflet map has invalidateSize() method just for such cases. When this method is called, map's size is adjusted to the current size of it's <div> element.

All that is needed then is to catch the moment (event), when tab (HTML element) is displayed. One of the possible ways is to use MutationObserver DOM object to catch style changes in HTML element that holds the map.

Below is quick simple example of that can be done.

CSS

<style>
  html, body {
    height: 100%;
  }
  .container-size {
    height: 100%;
  }
  .tab-size {
    height: calc(100% - 40px);
  }
  .map-size {
    height: 100%;
  }
</style>

HTML

<div class="container container-size">
  <ul class="nav nav-pills">
    <li class="active"><a data-toggle="pill" href="#home">Home</a></li>
    <li><a data-toggle="pill" href="#menu1">Menu 1</a></li>
  </ul>  
  <div class="tab-content tab-size">
    <div id="home" class="tab-pane fade in active map-size">
    </div>
    <div id="menu1" class="tab-pane fade map-size">
    </div>
  </div>
</div>

JS

<script>
  var map1 = L.map('home').setView([60, 30], 9);

  L.tileLayer('http://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: 'Map &copy; OpenStreetMap'
  }).addTo(map1);

  var map2 = L.map('menu1').setView([60, 30], 9);

  L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://carto.com/attributions">CARTO</a> contributors',
  }).addTo(map2);

  var homeTab = document.getElementById('home');
  var observer1 = new MutationObserver(function(){
    if(homeTab.style.display != 'none'){
      map1.invalidateSize();
    }
  });
  observer1.observe(homeTab, {attributes: true});  

  var menu1Tab = document.getElementById('home');
  var observer2 = new MutationObserver(function(){
    if(menu1Tab.style.display != 'none'){
      map2.invalidateSize();
    }
  });
  observer2.observe(menu1Tab, {attributes: true});  
</script>