context-menu
本文回答:
vjmap.ContextMenu右键菜单用法 —— 地图 / 覆盖物 / Marker / Draw。
一、ContextMenu 基础
new vjmap.ContextMenu({
event: mouseEvent.originalEvent, // 鼠标事件对象
theme: "dark", // "dark" | "light"
width: "250px",
items: [...]
});
二、菜单项类型
| type | 说明 |
|---|---|
normal(省略 type) |
普通菜单项 |
seperator |
分割线 |
custom |
自定义 HTML markup |
multi |
横向多按钮("不倾斜/不旋转/全图") |
submenu |
子菜单(点击弹出) |
hovermenu |
悬浮子菜单 |
Button |
按钮型子项(d.ts 联合成员,用法同 normal) |
三、地图右键菜单
map.setMenu(event => {
return new vjmap.ContextMenu({
event: event.originalEvent,
theme: "dark",
width: "250px",
items: [
{ type: "custom", markup: "<span>菜单右键功能演示</span>" },
{ type: "multi", items: [
{ label: "全图", onClick: () => map.fitMapBounds() },
{ label: "不旋转", onClick: () => map.setBearing(0) },
{ label: "不倾斜", onClick: () => map.setPitch(0) }
]},
{ label: "获取此位置坐标", onClick: () => {
const p = map.fromLngLat(event.lngLat);
console.log(p.x, p.y);
}},
{ type: "seperator" },
{ type: "submenu", label: "地图缩放", items: [
{ label: "放大一级", onClick: () => map.setZoom(map.getZoom() + 1) },
{ label: "缩小一级", onClick: () => map.setZoom(map.getZoom() - 1) },
{ label: "飞至此位置", onClick: () =>
map.flyTo({ center: event.lngLat, pitch: 60, zoom: 5 })
}
]}
]
});
});
四、覆盖物图层右键菜单
// 先给 polygon 默认菜单
map.setMenu(event => { /* 地图菜单 */ });
// 再给覆盖物菜单(key = "overlay")
map.setMenu(event => {
const features = map.queryRenderedFeatures(event.point, {
layers: [polygon.layerId]
});
if (features.length === 0) return null; // 返回 null → 继续弹地图菜单
return new vjmap.ContextMenu({
event: event.originalEvent,
items: [
{ label: "查看信息", onClick: () => console.log(features[0].properties) },
{ label: "删除", onClick: () => {
const d = polygon.getData();
d.features = d.features.filter(f => f.id != features[0].properties.id);
polygon.setData(d);
}}
]
});
}, "overlay"); // key 可以自定义,多个 setMenu 按 key 倒序匹配
setMenu(fn, key):同一 map 可以挂多个;按 key 倒序匹配,空 key 排最后。回调返回 null 时继续下一级。
语义(map.ts):
- 回调不在
contextmenuDOM 事件上触发,而是在 mousedown + mouseup 且originalEvent.button === 2(右键单击序列,isClick判定位移/时长)时依次调用——合成contextmenu事件无效;打开前 firecontextMenuPreOpen,成功打开后 firecontextMenuOpened。 setMenu()/setMenu(null)(不传 key)会摘掉内部的 mousedown/mouseup 监听,之后右键不再弹任何菜单;但注册表不清空(源码if (menu)跳过赋值)——下一次setMenu(fn, key)重新挂监听时,之前所有 key 的回调全部复活。只移除某个 key 用map.setMenu(() => null, key)(覆盖为恒返回 null 的回调)。map.isInteracting()为 true 时回调不触发,除非设内部开关_menu_isInteracting_open = true。
五、Marker 右键菜单
Marker 是 DOM,不走地图 setMenu;自己监听:
marker.getElement().addEventListener("mousedown", event => {
if (event.button !== 2) return;
event.preventDefault();
event.stopPropagation();
new vjmap.ContextMenu({
event,
theme: "dark",
items: [
{ type: "custom", markup: `<span>Marker 菜单</span>` },
{ label: "查看信息", onClick: () => console.log(marker.getLngLat()) }
]
});
});
六、Draw 工具右键菜单
const draw = new vjmap.Draw.Tool();
map.addControl(draw, "top-right");
// simple_select 模式选中实体时
map.on("draw.simpleselect.contextmenu", e => {
if (e.styleId !== draw.options.styleId) return;
const feature = e.event.featureTarget;
if (!feature) return;
const selectIds = draw.getSelectedIds() || [];
if (!selectIds.includes(feature.properties.id)) return;
e.event.originalEvent.preventDefault();
e.event.originalEvent.stopPropagation();
new vjmap.ContextMenu({
event: e.event.originalEvent,
items: [
{ label: "删除", onClick: () => draw.delete(feature.properties.id) }
]
});
});
类似还有 draw.directselect.contextmenu(夹点编辑模式)——其载荷只有 {mode, state, event}、没有 styleId,过滤实例用 e.state.options.styleId !== draw.options.styleId。
七、菜单项属性
{
type?: "custom" | "multi" | "Button" | "seperator" | "submenu" | "hovermenu" | "normal";
markup?: string; // type=custom 的 HTML
items?: [...]; // type=multi/submenu/hovermenu 的子项
label?: string;
shortcut?: string; // 快捷键提示
enabled?: boolean;
cssIcon?: string; // css 类名图标
icon?: string; // 图标 URL / base64
onClick?: () => void; // 调用时不传任何参数;需要的上下文用闭包捕获(如 setMenu 回调里的 event)
}
八、常见坑
- 用了
event.originalEvent:map.setMenu回调里拿到的是 vjmap 封装事件,弹菜单要传originalEvent;draw.*.contextmenu回调里拿到的是e.event.originalEvent。 - 菜单溢出屏幕 → 设置
innerWidth/innerHeight为容器大小:const { width, height } = map.getContainer().getBoundingClientRect(); new vjmap.ContextMenu({ event, innerWidth: width, innerHeight: height, ... }); - Marker 菜单不弹出 → 忘了
preventDefault(),浏览器原生菜单先弹。 - 多个 setMenu 互相覆盖 → 传不同的 key。