上下文菜单
ContextMenu上下文菜单用于右键弹出菜单界面显示。
常用方法
菜单项类型
| 类型 | 描述 |
|---|---|
| seperator | 分隔线 |
| custom | 自定义菜单项 |
| multi | 多个按钮 |
| submenu | 子菜单项 |
| hovermenu | 悬浮菜单项 |
| normal | 缺省正常菜单项 |
常见坑位
opts.items是必填:构造器直接opts.items.forEach无判空,new ContextMenu({width:...})不传 items 必抛forEach of undefined。- 构造即显示:构造器内部立即
document.body.appendChild(menu)并定位——不需要调show();要"先建好稍后显示"只能hide()收起。 add(item)对外基本不可用:它期望ContextMenuItem实例(读.element),而该类未导出——传{type:'normal',...}options 对象会崩。菜单项只能在构造items里一次性给全。- 覆盖
document.onclick/onmousedown:构造器用赋值(非 addEventListener)挂全局点击关闭,会清掉宿主先前绑在 document 上的 onclick。 opts.isSticky在构造器里被读,但ContextMenuOptions接口没声明——粘附定位传position:[x,y]更可靠。opts.position只在构造期生效:ContextMenuOptions虽声明了position?: [number, number],但show()/toggle()内部调PositionMenu时不传 position——只传position不传event时,构造能定位显示,但随后的show()会因读event.clientX抛Cannot read properties of undefined (reading 'clientX')。要可重复 show/toggle,必须传opts.event(带clientX/clientY的对象即可,如{clientX, clientY, stopPropagation(){}})。
示例
下面示例演示了如何给应用、场景中的实体对象、Marker增加菜单
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
container: "map", // 容器id
stat: { show: true, left: "0" },
scene: {
gridHelper: { visible: true } // 是否显示坐标网格
},
camera: { // 相机设置
viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
}
})
let box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
color: '#0ff'
});
let mesh = new THREE.Mesh(box, material);
mesh.position.set(0, 0, 0)
let meshEnt = vjmap3d.Entity.fromObject3d(mesh);
meshEnt.pointerEvents = true; // 实体允许事件,这样在此实体上面弹右键菜单才可以响应
meshEnt.addTo(app);
//
let marker2d = new vjmap3d.Marker2D({
color: vjmap3d.randHtmlColor()
});
marker2d.setPosition([2, 0, 1]);
marker2d.addTo(app);
//
let isAllowShowMenu = true
app.setMenu((event, object) => {
if (!isAllowShowMenu) return;
if (object) {
// 如果点击在一个实体对象上面时
return new vjmap3d.ContextMenu({
event: event.originalEvent,
theme: "dark", //light
width: "250px",
items: [
{type: 'custom', markup: `<span style="color: #00ffff; padding-left: 30px">实体右键菜单</span>`},
{
label: '编辑',
onClick: () => {
app.transformObject({
target: object.target.node,
clickNoSelfEntityExit: true,
clickNoEntityExit: true,
});
}
},
{
label: '删除',
onClick: () => {
object.target.remove()
}
},
]
});
} else {
// 如果没有点击到东西时,则弹出应用本身的菜单
return new vjmap3d.ContextMenu({
event: event.originalEvent,
theme: "dark", //light
width: "250px",
items: [
{type: 'custom', markup: `<span style="color: #ffff00; padding-left: 30px">应用右键菜单</span>`},
{type: 'multi', items: [
{label: '放大', onClick: () => { app.cameraControl.dolly(3); }},
{label: '缩小', onClick: () => { app.cameraControl.dolly(-3); }},
]},
]
});
}
})
// marker属于div,右键菜单事件需要单独增加响应
marker2d.getElement().addEventListener("contextmenu", event => {
// @ts-ignore
if (event.button != 2) return;//不是右键
// 阻止地图默认右键菜单触发
event.preventDefault();
event.stopPropagation();
new vjmap3d.ContextMenu({
event: event,
theme: "dark", //light
width: "250px",
items: [
{type: 'custom', markup: `<span style="color: #ff00ff; padding-left: 30px">标注Marker的右键菜单</span>`},
{
label: '允许拖动',
onClick: () => {
marker2d.setDraggable(true)
}
},
{
label: '不允许拖动',
onClick: () => {
marker2d.setDraggable(false)
}
}
]
});
})
app.logInfo("请在相应的位置点击右键弹出右键菜单")
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
container: "map", // 容器id
stat: { show: true, left: "0" },
scene: {
gridHelper: { visible: true } // 是否显示坐标网格
},
camera: { // 相机设置
viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
}
})
let box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
color: '#0ff'
});
let mesh = new THREE.Mesh(box, material);
mesh.position.set(0, 0, 0)
let meshEnt = vjmap3d.Entity.fromObject3d(mesh);
meshEnt.pointerEvents = true; // 实体允许事件,这样在此实体上面弹右键菜单才可以响应
meshEnt.addTo(app);
//
let marker2d = new vjmap3d.Marker2D({
color: vjmap3d.randHtmlColor()
});
marker2d.setPosition([2, 0, 1]);
marker2d.addTo(app);
//
let isAllowShowMenu = true
app.setMenu((event, object) => {
if (!isAllowShowMenu) return;
if (object) {
// 如果点击在一个实体对象上面时
return new vjmap3d.ContextMenu({
event: event.originalEvent,
theme: "dark", //light
width: "250px",
items: [
{type: 'custom', markup: `<span style="color: #00ffff; padding-left: 30px">实体右键菜单</span>`},
{
label: '编辑',
onClick: () => {
app.transformObject({
target: object.target.node,
clickNoSelfEntityExit: true,
clickNoEntityExit: true,
});
}
},
{
label: '删除',
onClick: () => {
object.target.remove()
}
},
]
});
} else {
// 如果没有点击到东西时, 则弹出应用本身的菜单
return new vjmap3d.ContextMenu({
event: event.originalEvent,
theme: "dark", //light
width: "250px",
items: [
{type: 'custom', markup: `<span style="color: #ffff00; padding-left: 30px">应用右键菜单</span>`},
{type: 'multi', items: [
{label: '放大', onClick: () => { app.cameraControl.dolly(3); }},
{label: '缩小', onClick: () => { app.cameraControl.dolly(-3); }},
]},
]
});
}
})
// marker属于div,右键菜单事件需要单独增加响应
marker2d.getElement().addEventListener("contextmenu", event => {
// @ts-ignore
if (event.button != 2) return;//不是右键
// 阻止地图默认右键菜单触发
event.preventDefault();
event.stopPropagation();
new vjmap3d.ContextMenu({
event: event,
theme: "dark", //light
width: "250px",
items: [
{type: 'custom', markup: `<span style="color: #ff00ff; padding-left: 30px">标注Marker的右键菜单</span>`},
{
label: '允许拖动',
onClick: () => {
marker2d.setDraggable(true)
}
},
{
label: '不允许拖动',
onClick: () => {
marker2d.setDraggable(false)
}
}
]
});
})
app.logInfo("请在相应的位置点击右键弹出右键菜单")
按钮弹出菜单
const resetSettings = '<svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%;" fill="currentColor" viewBox="0 0 24 24">\n' +
' <path fill="currentColor" d="M19 8L15 12H18C18 15.31 15.31 18 12 18C11 18 10.03 17.75 9.2 17.3L7.74 18.76C8.97 19.54 10.43 20 12 20C16.42 20 20 16.42 20 12H23M6 12C6 8.69 8.69 6 12 6C13 6 13.97 6.25 14.8 6.7L16.26 5.24C15.03 4.46 13.57 4 12 4C7.58 4 4 7.58 4 12H1L5 16L9 12M14 12C14 13.11 13.11 14 12 14S10 13.11 10 12 10.9 10 12 10 14 10.9 14 12Z" />\n' +
'</svg>'
const trash = '<svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%;" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">\n' +
' <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />\n' +
'</svg>'
let control = new vjmap3d.ButtonGroupControl({
buttons: [
{
id: 'reset-settings',
icon: resetSettings,
title: 'Reset All Settings',
// round: true,
onclick: async(e, element) => {
let rect = element.getBoundingClientRect()
let position = [rect.left - 6, rect.bottom + 8]
new vjmap3d.ContextMenu({
position,
theme: "dark", //light
width: "150px",
items: [
{
label: 'Menu1',
onClick: () => {
app.logInfo("Menu1", "success");
}
},
{
label: 'Menu2',
onClick: () => {
app.logInfo("Menu2", "success");
}
}
]
});
},
},
{
id: 'clear-scene',
icon: trash,
title: 'Clear Scene',
// checked: true,
// toggle: true,
//round: true,
onclick: async(e, element) => {
let rect = element.getBoundingClientRect()
let position = [rect.left - 6, rect.bottom + 8]
new vjmap3d.ContextMenu({
event: e,
position,
theme: "dark", //light
width: "150px",
items: [
{
label: '菜单一',
onClick: () => {
app.logInfo("菜单一", "success");
}
},
{
label: '菜单二',
onClick: () => {
app.logInfo("菜单二", "success");
}
}
]
});
},
},
],
direction: "row",
barSelectMode: true,
barCurSelectId: "reset-settings"
// lightTheme: true
});
app.addControl(control, "top-left")
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
container: "map", // 容器id
scene: {
gridHelper: { visible: true } // 是否显示坐标网格
},
camera: { // 相机设置
viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
}
})
const resetSettings = '<svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%;" fill="currentColor" viewBox="0 0 24 24">\n' +
' <path fill="currentColor" d="M19 8L15 12H18C18 15.31 15.31 18 12 18C11 18 10.03 17.75 9.2 17.3L7.74 18.76C8.97 19.54 10.43 20 12 20C16.42 20 20 16.42 20 12H23M6 12C6 8.69 8.69 6 12 6C13 6 13.97 6.25 14.8 6.7L16.26 5.24C15.03 4.46 13.57 4 12 4C7.58 4 4 7.58 4 12H1L5 16L9 12M14 12C14 13.11 13.11 14 12 14S10 13.11 10 12 10.9 10 12 10 14 10.9 14 12Z" />\n' +
'</svg>'
const trash = '<svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%;" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">\n' +
' <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />\n' +
'</svg>'
let control = new vjmap3d.ButtonGroupControl({
buttons: [
{
id: 'reset-settings',
icon: resetSettings,
title: 'Reset All Settings',
// round: true,
onclick: async(e, element) => {
let rect = element.getBoundingClientRect()
let position = [rect.left - 6, rect.bottom + 8]
new vjmap3d.ContextMenu({
position,
theme: "dark", //light
width: "150px",
items: [
{
label: 'Menu1',
onClick: () => {
app.logInfo("Menu1", "success");
}
},
{
label: 'Menu2',
onClick: () => {
app.logInfo("Menu2", "success");
}
}
]
});
},
},
{
id: 'clear-scene',
icon: trash,
title: 'Clear Scene',
// checked: true,
// toggle: true,
//round: true,
onclick: async(e, element) => {
let rect = element.getBoundingClientRect()
let position = [rect.left - 6, rect.bottom + 8]
new vjmap3d.ContextMenu({
event: e,
position,
theme: "dark", //light
width: "150px",
items: [
{
label: '菜单一',
onClick: () => {
app.logInfo("菜单一", "success");
}
},
{
label: '菜单二',
onClick: () => {
app.logInfo("菜单二", "success");
}
}
]
});
},
},
],
direction: "row",
barSelectMode: true,
barCurSelectId: "reset-settings"
// lightTheme: true
});
app.addControl(control, "top-left")