使用 Vue3 组件作为 Leaflet 弹出窗口

问题描述 投票:0回答:2

上一个 SO 问题展示了我们如何使用 Vue2 组件作为 LeafletJS 弹出窗口的内容。我无法在 Vue3 中使用它。

提取我的代码的相关部分,我有:

<script setup lang="ts">
import { ref } from 'vue'
import L, { type Content } from 'leaflet'
import type { FeatureCollection, Feature } from 'geojson'

import LeafletPopup from '@/components/LeafletPopup.vue'

// This ref will be matched by Vue to the element with the same ref name
const popupDialogElement = ref(null)

function addFeaturePopup(feature:Feature, layer:L.GeoJSON) {
  if (popupDialogElement?.value !== null) {
    const content:Content = popupDialogElement.value as HTMLElement
    layer.bindPopup(() => content.$el)
  }
}
</script>

<template>
  <div class="map-container">
    <section id="map">
    </section>
    <leaflet-popup ref="popupDialogElement" v-show="false">
    </leaflet-popup>
  </div>
</template>

当我点击地图时,这确实会产生一个弹出窗口,但它没有内容。

如果我将第 14 行更改为:

    layer.bindPopup(() => content.$el.innerHTML)

然后我do得到一个带有我期望的HTML标记的弹出窗口,但毫不奇怪的是我失去了我需要的所有Vue行为(事件处理等)。

在JS调试器中检查

addFeaturePopup
函数,
content
似乎确实是
HTMLElement
的实例,所以我不确定为什么将它传递给Leaflet的
bindPopup
方法不起作用。我认为这与 Vue3 处理引用的方式有关,但到目前为止我还没有找到解决方法。

更新2022-06-09

根据要求,这是

console.log
输出:我已将其放入要点,因为它很长

leaflet vuejs3
2个回答
0
投票

因此,为了记录我最终使用的解决方案,除了问题中概述的一般框架之外,我还需要添加额外的样式规则:

<style>
.leaflet-popup-content >* {
  display: block !important;
}
</style>

这会覆盖通过

display:none
附加到 DOM 节点的
v-show=false
。不需要
!important
就好了,但我在实验中无法使规则具有足够的选择性。


0
投票

使用Vue3的传单自定义弹出窗口

总结

使用 Vue3 的 Teleport,将基于 Vue 的弹出窗口(具有完全反应性)渲染为空的 Leaflet 弹出窗口。这或多或少只是一种更干净的方式来完成人们多年来手动做的事情。

请参阅下面的代码和演示。

一些要点

1 - 在启用 Vue 组件(以及需要 DOM 目标的 Teleport)之前,原生 Leaflet 弹出 DOM 元素需要首先存在。因此,在创建 Leaflet 弹出窗口时,我们首先绑定它,显示 Vue 组件,当一切处理完毕后,然后实际上会显示 Leaflet 弹出窗口。

2 - 在我自己的应用程序中,我必须放置战略性的

setTimeout
,否则我不会在弹出窗口和/或控制台错误中呈现任何内容。在codesandbox演示中,这些超时似乎没有必要,我将它们注释掉了。或者,Vue
nextTick()
可能是一个更优雅的东西。

3 - 解除弹出窗口的绑定很重要,否则你可能会遇到弹出窗口消失或其他奇怪的行为。

4 - 下面的工作假设一次只打开一个弹出窗口,并且仅依赖于现有的 Leaflet 弹出窗口 CSS 类。如果需要更多的特异性或处理多个弹出窗口,那么您必须在创建每个 Leaflet 弹出窗口时创建一个唯一的 id,并在 Teleport 中引用它。

https://codesandbox.io/p/devbox/leaflet-vue3-popup-using-teleport-gw4wkp

Vue3 + 打字稿

应用程序.vue

<script setup lang="ts">
import { onMounted, ref } from "vue"
import * as L from "leaflet"
import "leaflet/dist/leaflet.css"
import MyMapPopup from "./components/MapPopup.vue"
import MarkerImage from "leaflet/dist/images/marker-icon.png"

var map:L.Map
var myMarker: L.Marker
var map_id = "myMap"
const popupcard = ref()
const showTeleportContent = ref()

onMounted(() => {
    initMap()
})

const closePopup = () => {
  myMarker.closePopup()
}

const initMap = () => {

  // MAP ELEMENT
  let mapHTMLElement = document.getElementById(map_id) as HTMLElement
  map = L.map(mapHTMLElement).setView([43.6532, -79.3832], 10);

  // TILE LAYER  L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
    attribution: 'Tiles &copy; Esri &mdash; Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
  }).addTo(map)

  // MARKER
  var myIcon = L.icon({ iconUrl: MarkerImage, iconAnchor: [12, -10], popupAnchor: [0, -40] })
  myMarker = L.marker([43.7, -79.4], { icon: myIcon })
  myMarker.addTo(map)

  // MARKER CLICK
  myMarker.on('click', (e:L.LeafletMouseEvent) => {

    // Possibly need a delay here (or Vue nextTick())
    //setTimeout(() => {               

      // UNBIND ANY PREVIOUSLY OPENED POPUPS (just in case)
      myMarker.unbindPopup()

      // CREATE NEW LEAFLET POPUP
      // First value is blank ('') because Leaflet needs _something_ to initialize the popup.
      myMarker.bindPopup('', {
          'maxWidth': 300,
          'maxHeight': 100,
          'className': 'myCustomPopupClass',
          'autoPan': true,
          'closeButton': false, // Hide native Leafet Popup close button; alternatively can use CSS
          'closeOnEscapeKey': true
      })

      // SET CONTENT AND REVEAL THE POPUP
      // Possibly need a delay here (or Vue nextTick())
      //setTimeout(() => {

          // Enable the Vue content (and trigger the Teleport)
          showTeleportContent.value = true

          // Reveal the popup to the user
          myMarker.openPopup()
      //}, 100)

      // LISTENER FOR POPUP CLOSE
      myMarker.getPopup()?.on('remove', () => {
          myMarker.unbindPopup()
          showTeleportContent.value = false
      })

    //}, 100)
  })
}
</script>

<template>
  <div id="myWrapper">

    <div id="myMap"></div>

      <!-- The Vue popup is only shown (v-if) when the Leaflet popup is initialized,
        as Vue Teleport requires a DOM target to function. -->
      <div ref="popupcard" id="popupcard" v-if="showTeleportContent">

        <!-- Teleport is a Vue 3 feature; it basically appends the component
        to any DOM target (:to). Here, we point it to the content class of the Leaflet popup. Since only one popup is open at a time (presumably) this is safe. Otherwise you'd need to create a unique ID when creating the Leaflet popup. -->
        <Teleport :to="'.leaflet-popup-content'">
            <component ref="popupcardInner"
            :is="MyMapPopup"
            v-model="count"
            :message1="message1"
            :message2="message2"
            @close="closePopup()" />
        </Teleport>
      </div>
  </div>
</template>

<style scoped>
#myWrapper {
  width: 100%;
  height: 100%;
}

#myMap {
  width: 100%;
  height: 100%;
}
</style>

<style>
/* UNSCOPED CSS (ie global) */

/* ------------------------------------ */
/* RESET LEAFLET POPUP defaults */
/* ------------------------------------ */
.leaflet-popup-content-wrapper {
    padding: 0;
}

.leaflet-popup {
  width: 300px;
  height: 200px;
}

.leaflet-popup-content {
    margin: 0;
    line-height: inherit;
    font-size: inherit;
}

/*
OPTIONAL: You can use this CSS to hide the native "x" close button instead of using the popup property method.
.leaflet-popup-close-button {
    display: none;
}
*/
</style>
© www.soinside.com 2019 - 2024. All rights reserved.