Search code examples
leafletremarkjs

creating lealetjs map in an invisible div


I am adding a leafletjs map in a remarkjs slideshow. The map works fine if the slide containing the map div is visible on initial load of the web page. However, if the slide with the map div is not the visible slide then the map div is invisible, so the leaflet map doesn't work properly as all the tiles are not loaded in the div. What I'd like to do is load all the map divs in the slideshow, no matter which slide they may be on, and then have them show up properly when the slide containing the maps comes up.

Update: the suggested answer does seem like it should answer my question, but I am still stuck and not sure if I am on the right track. Below are more details.

Since I could be adding many maps to a slideshow, I am using a class name for the map divs.

// css
.map { width: 100vh; height: 100vh; }

// html
<div class="map" data-lat="47" data-lon="106"></div>
<div class="map" data-lat="0" data-lon="0"></div>
<!-- possibly on a different slide -->
<div class="map" data-lat="30" data-lon="28"></div>

// js
var maps = []; // for storing map elements
var mapDivs = document.getElementsByClassName('map');

for (var i=0, j=mapDivs.length; i<j; i++) {
    var map = L.map(mapDivs[i]).setView(
        [mapDivs[i].dataset.lat, mapDivs[i].dataset.lon], 
        13
    );

    // store map for later
    maps.push(map);
    L.tileLayer(…).addTo(map);
}

slideshow.on('showSlide', function (slide) {
    for (var i=0, j=maps.length; i<j; i++) {
        maps[i].invalidateSize();
    }
});

My logic says the above should work, but it doesn't. What am I doing wrong?

Update2: The map.invalidateSize() answer was indeed a part of the solution. My fault was that I was calling slideshow.on('showSlide', fn) but I should really have been calling slideshow.on('afterShowSlide', fn). The latter gives remarkjs an opportunity to actually create the current slide which may have the map div which in turn allows map.invalidateSize() to correctly fire and repaint the map.


Solution

  • leafletjs requires a div of width and height > 0px to exist, that is, be visible, not hidden, to download the required map tiles and show a map. Adding map divs to a remarkjs slideshow means one or more of those divs would be hidden when the slideshow is launched. Consequently, leafletjs would not be able to show a map in those divs (random tiles would be missing) until the browser is resized forcing leafletjs to recalc the size of the map divs and download the correct tiles. The solution is to fire the map.invalidateSize() method when a slide with a map div is displayed. Here is how I did it finally

    /* css ******************************/
    .map { width: 100vh; height: 100vh; }
    
    
    <!---------------------------------------------
    html
    map divs possibly on different slides, with a
    `map: true` property on each slide with a map
    ----------------------------------------------->
    
    ---
    map: true
    <div class="map" data-lat="47" data-lon="106"></div>
    ---
    map: true
    <div class="map" data-lat="0" data-lon="0"></div>
    ---
    map: true
    <div class="map" data-lat="30" data-lon="28"></div>
    
    // js /////////////////////////////////////////
    var maps = []; // for storing leaflet map elements
    var mapDivs = document.getElementsByClassName('map');
    
    for (var i=0, j=mapDivs.length; i<j; i++) {
    
        // store map for later
        maps.push(L.map(mapDivs[i]).setView(
            [mapDivs[i].dataset.lat, mapDivs[i].dataset.lon], 
            zoomLevel
        ));
    
        // add map to the tileLayer
        L.tileLayer(…).addTo(maps[i]);
    }
    
    // use the 'afterShowSlide' event to ensure the 
    // map div has been created
    slideshow.on('afterShowSlide', function (slide) {
    
        // only if the slide has a 'map' property
        if (slide.properties.map) {
            for (var i=0, j=maps.length; i<j; i++) {
                maps[i].invalidateSize();
            }
        }
    });