Search code examples
vue.jsvuejs2leaflet

Can't show the all the map on a DIV with VueJs and leaflet until resizing the screen


I am using VueJS and Leaflet to show the map with special localisation. I have added the css leaflet on index.html as told in the documentation.

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

But I have just a part of the map. I have to change the size of the screen to have all the map with the marker.

enter image description here

This is the vue where I implement the map (iMap.vue)

<template>
  <div id="professionnel">
    <b-row>
     <b-tabs>
        <b-tab title="A" >
            <div>
                <b-col class="col-12">
                    <div>Où nous joindre</div>
                </b-col>
            </div>
        </b-tab>
       <b-tab title="B">
            <div class="tab-content-active" >
                <b-col class="col-6">
                    <div>heure</div>
                </b-col>
                <b-col class="col-6 data_map">
                    <iMap1></iMap>
                </b-col>
            </div>
        </b-tab>
      </tabs>
    </b-row>
  </div>
</template>

<script>
import iMap1 from './iMap1'

export default {
  name: 'professionnel',
  components: {
    iMap1
  },
  data() {
    return {}
  }
</script>

And this is the vue of the Map (iMap.vue)

    <template>
        <div id="imap1" class="map" >
        </div>
    </template>

    <script>

    import leaflet from 'leaflet'

    export default {
      name: 'imap1',
      components: {
        leaflet
      },
      data() {
        return {
            professionnel: {},
            map1: null,
            tileLayer: null,
        }
      },
        methods: {
        initMap() {
            this.map1 = L.map('imap1').setView([47.413220, -1.219482], 12)
            this.tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
                      //'https://cartodb-basemaps-{s}.global.ssl.fastly.net/rastertiles/voyager/{z}/{x}/{y}.png',
            {
                maxZoom: 18,
                center: [47.413220, -1.219482],
                attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attribution">CARTO</a>',
            }).addTo(this.map1)

 L.marker([47.413220, -1.219482]).addTo(this.map1).bindPopup('name')
                        .openPopup()

 this.map1.invalidateSize()

  })
 },
},
created () {
 this.initMap()
}

}
</script>

Solution

  • Use the mounted lifecycle hook instead of the created one.

    created is typically to subscribe to some data / start some async processes, whereas mounted is rather when the DOM of your component is ready (but not necessarily insterted in the page IIRC).

    Then, as explained in Data-toggle tab does not download Leaflet map, you have to use invalidateSize after the Tab that contains your Map container is opened, i.e. you have to listen to an event that signals that your user has opened the Tab.

    In the case of Bootstrap-Vue, you have the <b-tabs>'s "input" event, but which signals only when the user has clicked on the Tab. But the latter is still not opened. Therefore you have to give it a short delay (typically with setTimeout) before calling invalidateSize:

    Vue.use(bootstrapVue);
    
    Vue.component('imap', {
      template: '#imap',
      methods: {
        resizeMap() {
          if (this.map1) {
            this.map1.invalidateSize();
            // Workaround to re-open popups at their correct position.
            this.map1.eachLayer((layer) => {
              if (layer instanceof L.Marker) {
                layer.openPopup();
              }
            });
          }
        },
        initMap() {
          this.map1 = L.map('imap1').setView([47.413220, -1.219482], 12)
          this.tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 18,
            center: [47.413220, -1.219482],
            attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
          }).addTo(this.map1)
    
          L.marker([47.413220, -1.219482]).addTo(this.map1).bindPopup('name')
            .openPopup() // Opening the popup while the map is incorrectly sized seems to place it at an incorrect position.
        },
      },
      mounted() {
        this.initMap()
      },
    });
    
    new Vue({
      el: '#app',
      methods: {
        checkMap(tab_index) {
          if (tab_index === 1) {
            // Unfortunately the "input" event occurs when user clicks
            // on the tab, but the latter is still not opened yet.
            // Therefore we have to wait a short delay to allow the
            // the tab to appear and the #imap1 element to have its final size.
            setTimeout(() => {
              this.$refs.mapComponent.resizeMap();
            }, 0); // 0ms seems enough to execute resize after tab opens.
          }
        }
      },
    });
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
    <script src="https://unpkg.com/[email protected]/dist/leaflet-src.js" integrity="sha512-IkGU/uDhB9u9F8k+2OsA6XXoowIhOuQL1NTgNZHY1nkURnqEGlDZq3GsfmdJdKFe1k1zOc6YU2K7qY+hF9AodA==" crossorigin=""></script>
    <script src="https://unpkg.com/vue@2"></script>
    <link rel="stylesheet" href="https://unpkg.com/bootstrap@4/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/bootstrap-vue.css" />
    <script src="https://unpkg.com/[email protected]/dist/bootstrap-vue.js"></script>
    
    <div id="app">
      <!-- https://bootstrap-vue.js.org/docs/components/tabs -->
      <b-tabs @input="checkMap">
        <b-tab title="First Tab" active>
          <br>I'm the first fading tab
        </b-tab>
        <b-tab title="Second Tab with Map">
          <imap ref="mapComponent"></imap>
        </b-tab>
      </b-tabs>
    </div>
    
    <template id="imap">
      <div id="imap1" style="height: 130px;"></div>
    </template>