Search code examples
vue.jstabsleafletbulmabuefy

Leaflet map not loading in Bulma tab using Vue.js


I have an issue with loading Leaflet map using Vue.js and Bulma tab components (via Buefy).

If map is placed inside tab then it does not load all tiles until browser window is resized.

If map is placed outside of Bulma tabs component then it loads without any issue.

Calling map.invalidateSize() seems to help, but to do it automatically when tab changes I have to call it using setTimeout and put very big delay, like 1sec - which is very ugly.

How to get this working without this invalidateSize workaround?

Example with the issue: https://codepen.io/alxxnder/pen/zyYxwd

Example without the issue: https://codepen.io/alxxnder/pen/LMYEjr

Code:

new Vue({
  el: '#app',
  data: {
    map: null,
  },
  methods: {
    invalidateSize: function() {
      this.map.invalidateSize();
    }
  },
  mounted() {
    this.map = L.map('map').setView([38.63, -90.23], 12);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(this.map);
  }
})
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Leaflet Test</title>

  <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/buefy.min.css">
  <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
</head>

<body>


  <div class="section">
    <div class="container" id="app">
      <b-tabs position="is-centered">
        <b-tab-item label="Tab 1">
          <div class="section">
            Tab 1
            <div class="map" id="map" style="height: 400px; width: 100%"></div>
            <button class="button is-info" @click="invalidateSize()">invalidateSize</button>
          </div>
        </b-tab-item>
        <b-tab-item label="Tab 2">
          <div class="section">
            Tab 2
          </div>
        </b-tab-item>
      </b-tabs>
    </div>
  </div>


</body>

<script src="https://unpkg.com/vue@2"></script>
<script src="https://unpkg.com/[email protected]/dist/buefy.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>

</html>


Solution

  • As explained in Data-toggle tab does not download Leaflet map, the issue is caused by the fact that your map container does not have yet its full size when you initialize it. You may understand it more easily if your map had been in an initially hidden tab (e.g. in tab 2).

    As for your initially active tab (i.e. tab 1), it is probable that Buefy / Bulma still takes some time to reveal the tab content.

    Since there is no event when the tab transition completes, you have to wait for the transition duration before calling the invalidateSize method. In your case, 300ms seems to be fine.

    Then you should also call it again when the user changes the tab (see Buefy tabs events), otherwise should the browser had changed size while your tab was hidden, the same issue would happen again.

    Demo with maps in the 2 tabs:

    new Vue({
      el: '#app',
      data: {
        map: null,
        map2: null,
        tabMaps: []
      },
      methods: {
        invalidateSize: function(tabIndex) {
          setTimeout(() => {
            if (typeof tabIndex === "number") {
              this.tabMaps[tabIndex].invalidateSize();
            } else {
              // invalidate all maps
              this.tabMaps.forEach(map => {
                map.invalidateSize();
              });
            }
          }, 300);
        }
      },
      mounted() {
        this.map = L.map('map').setView([38.63, -90.23], 12);
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(this.map);
    
        // map2 in tab2
        this.map2 = L.map(this.$refs.map2).setView([38.63, -90.23], 12);
        L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(
          this.map2
        );
    
        this.tabMaps.push(this.map); // 0
        this.tabMaps.push(this.map2); // 1
        this.invalidateSize();
      }
    })
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/buefy.min.css">
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
    
    <div class="section">
      <div class="container" id="app">
        <b-tabs @change="invalidateSize" position="is-centered">
          <b-tab-item label="Tab 1">
            <div class="section">
              Tab 1
              <div class="map" id="map" style="height: 400px; width: 100%"></div>
              <button class="button is-info" @click="invalidateSize()">invalidateSize</button>
            </div>
          </b-tab-item>
          <b-tab-item label="Tab 2">
            <div class="section">
              Tab 2
              <div class="map" ref="map2" style="height: 400px; width: 100%"></div>
            </div>
          </b-tab-item>
        </b-tabs>
      </div>
    </div>
    
    <script src="https://unpkg.com/vue@2"></script>
    <script src="https://unpkg.com/[email protected]/dist/buefy.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>