Mapbox GL JS Popups with Vue

BeyondTracks uses Mapbox GL JS and Vue, however to quote from github.com/phegman/vue-mapbox-gl:

Popups can be a little tricky if you are trying to use Vue directives inside the popup content. This is because the popups are added to the DOM by Mapbox and not compiled by Vue.

We use Vue, we also use Mapbox GL JS Popups on our maps to show walk summaries, bushfire alerts and beach conditions.

As an aside, we only use popups on large screens, on small mobile screens a modal dialog works much better; so using Vue makes it easy to share the same content between a popup and a modal dialog depending on the screen size.

Mapbox GL JS Popup showing walk details
Mapbox GL JS Popup showing walk details

Based off the very helpful guidance at github.com/phegman/vue-mapbox-gl we were able to use Vue components within popups with this code:

// create a single file component for our Popup content and import it
import BTWalkPopup from '@/components/BTWalkPopup'

const BTWalkPopupClass = Vue.extend(BTWalkPopup)

// then to create and open a Popup...

// ...create a new Popup instance with an empty div element with a fixed id
const walkPopup = new mapboxgl.Popup()
    .setLngLat(walk.geometry.coordinates)
    .setHTML('<div id="vue-popup-content"></div>')
    .addTo(this.map)

// create a new Vue component with your props for this Popup
const popupInstance = new BTWalkPopupClass({
    propsData: {
        walk: walk
    }
})

// mount this Vue component within the empty div Mapbox GL JS has just
// created in the DOM
popupInstance.$mount('#vue-popup-content')

// since the size of the popup may have changed when mounting the Vue
// component, force an update for dynamic positioning
walkPopup._update()

Just one last point, Chrome suffers a bug where an HTML element with an odd numbered width will appear blurry over a WebGL canvas, this looks really bad. The workaround is to ensure the HTML element strictly has an even numbered width. Within your popup content component (BTWalkPopup in the above example) you can detect the width and if odd, enforce an even width.

mounted () {
    // odd width popups are blurry on Chrome, this enforces even widths
    if (Math.ceil(this.$el.clientWidth) % 2) {
        this.$el.style.width = (Math.ceil(this.$el.clientWidth) + 1) + 'px'
    }
}