自定义ArcGIS Map Popup
ArcGIS 自己的 Popup 组件样式太过单一,且使用WebComponent实现,无法满足或修改为工作中UI所设计的样式,利用 Vue 的 createApp 方法来实现 Popup 的完全自定义。
1. 手动开启 Popup
ArcGIS Map 的 Popup 组件可以手动开启的,开启方式如下:
import Map from '@arcgis/core/Map' import MapView from '@arcgis/core/views/MapView' onMounted(() => { // 创建 Map 实例 const map = new Map({ basemap: 'topo-vector' }) // 创建 MapView 实例 const view = new MapView({ container: 'viewDiv', map, center: [120.38, 36.06], zoom: 13 }) view.when(() => { // 禁用弹出窗口自动出现,并使用单击事件手动打开弹出窗口。 view.popupEnabled = false view.on('click', (e) => { view.openPopup({ location: e.mapPoint, title: 'Popup Title', content: 'Popup Content' }) }) }) })
2. 替换 content
使用 createApp 方法创建 Vue 实例,然后将实例的 div 元素作为 Popup 的 content 属性值,即可实现自定义的 Popup 组件。
createApp方法的第一个参数为 Vue 组件createApp方法的第二个参数为 Vue 组件的 props,我们可以把需要的参数传递给自定义的 Vue 组件,如 MapView 的Popup实例,你也可以根据自己的需求,传递其他props。- 在第二个参数中,也可以传递方法,但需要符合写法规范,需要以
on开头,如onEventEmit,这样在 Vue 组件中,就可以通过emit('eventEmit', arg)来触发该方法并进行传参,这样就实现了与父组件的交互。
父组件示例:
import { createApp } from 'vue' import CustomPopup from './CustomPopup.vue' const content = document.createElement('div') createApp(CustomPopup, { view, onEventEmit: (arg: any) => { alert(arg) }, }).mount(content) view.openPopup({ title: 'custom popup', content, location: e.mapPoint })
子组件示例:
<template> <div class="h-200px flex-col-center bg-#fff"> <div mb-10>自定义子组件</div> <div> <el-button type="primary" @click="onClick"> 触发父组件事件 </el-button> <el-button type="primary" @click="view.closePopup()"> 关闭弹窗 </el-button> </div> </div> </template> <script setup lang='ts'> import type MapView from '@arcgis/core/views/MapView' const props = defineProps<{ view: MapView }>() const emit = defineEmits<{ eventEmit: [arg: string] }>() console.log(props.view) function onClick() { emit('eventEmit', '我是子组件传递的参数') } </script>
3. 注意事项
第一次打开 Popup 组件时,会比较慢,因为需要加载 Dom 的相关资源,后续打开就会很快了。你可以在触发开启 Popup 的地方,将鼠标样式改为
progress,在 CustomPopup 组件中,当元素展示在视口中时,将鼠标样式改为default。
// 由于我使用了 VueUse 的 useElementVisibility 方法,所以这里的代码是这样的 // CustomPopup 组件的根元素 ref const popupRef = ref() const targetIsVisible = useElementVisibility(popupRef) watch(targetIsVisible, (visible) => { if (visible) document.body.style.cursor = 'default' })
当然你可能存在多个自定义的 Popup 组件,并且可能是开启另一后,再去直接开启另一个,这样就会出现鼠标样式不对的情况,所以触发开启 Popup 的地方加上这段代码,就可以解决这个问题:
// 把title当做id,通过title来判断是否是当前自定义的Popup组件 if (!view.popup.visible && view.popup.title !== 'custom popup') { document.body.style.cursor = 'progress' }
本次使用的 ArcGIS 版本为4.27,如果你使用的版本不同,可能会出现一些问题,如:
view.openPopup无法打开 Popup,这时你可以使用view.popup.open来打开,其次使用view.popupEnabled属性可能会报错,这时你可以使用view.popup.autoOpenEnabled属性来代替,具体的属性和方法,可以查看:ArcGIS API for JavaScript。
4. 4.28版本的 Popup
在4.28版本中,ArcGIS 修改了 Popup 的实现方式,使用了 WebComponent,无法再通过上面的方式来实现自定义的 Popup 组件,因为上面的方式要通过 css 来隐藏一些不需要的元素,但是 4.28 版本的 Popup 是通过 WebComponent 实现的,且没有暴露出来,所以无法通过 css 来隐藏一些不需要的元素,所以只能采用另一种方式来实现自定义的 Popup 组件,直接上代码:
<template> <div id="map" full bg="light dark:dark" overflow-hidden /> </template> <route lang="yaml"> meta: name: 自定义弹窗 4.28 </route> <script setup lang='ts'> import type { App } from 'vue' import { createApp } from 'vue' import Point from '@arcgis/core/geometry/Point' import Graphic from '@arcgis/core/Graphic' import SimpleMarkerSymbol from '@arcgis/core/symbols/SimpleMarkerSymbol' import CustomPopup from './components/CustomPopup.vue' const { view } = useArcgis('map') // 创建点 const point = new Point({ x: 120.38, y: 36.06 }) const graphic = new Graphic({ geometry: point, symbol: new SimpleMarkerSymbol({ color: [226, 119, 40], outline: { color: [255, 255, 255], width: 2, }, }), attributes: { name: '自定义弹窗', }, }) // 定义 Popup 组件 let popup: App<Element> | null = null onMounted(() => { // 创建 Popup 组件的 content const content = document.createElement('div') content.style.position = 'absolute' // 将 constent 添加到地图容器中 document.getElementById('map')!.appendChild(content) // 创建点的 Graphic view.graphics.add(graphic) // 监听点击事件 view.on('click', (event) => { view.hitTest(event).then((response) => { const res = response.results[0] if (res?.type === 'graphic' && res?.graphic?.attributes?.name === '自定义弹窗') { if (!popup) { popup = createApp(CustomPopup, { view, attributes: res.graphic.attributes, onClosePopup: () => { popup!.unmount() popup = null }, }) popup.mount(content) } } }) }) // 监听地图中心点变化 view.watch('center', () => { // 将Popup点坐标转为屏幕坐标 const screenPoint = view.toScreen(point) // content 的定位改为屏幕坐标 content.style.left = `${screenPoint.x - 200}px` content.style.top = `${screenPoint.y - 200}px` }) }) </script>
总结下来就是:
- 创建一个
div元素,作为 Popup 的content属性值 - 将
div元素添加到地图容器中 - 监听地图的点击事件,判断点击的是否是自定义的点
- 如果是自定义的点,就创建 Popup 组件,并将
div元素作为 Popup 的content属性值 - 监听地图中心点的变化,将
div元素的定位改为屏幕坐标
