唯杰地图 logo唯杰地图
VJMAP
EN

上下文菜单

vjmap3d/references/ui/contextMenu.md 16.4 KB420 行
原文

ContextMenu上下文菜单用于右键弹出菜单界面显示。

常用方法

方法 描述
add 增加菜单项
show 显示菜单
hide 隐藏菜单
toggle 切换菜单状态

菜单项类型

类型 描述
seperator 分隔线
custom 自定义菜单项
multi 多个按钮
submenu 子菜单项
hovermenu 悬浮菜单项
normal 缺省正常菜单项

常见坑位

  1. opts.items 是必填:构造器直接 opts.items.forEach 无判空,new ContextMenu({width:...}) 不传 items 必抛 forEach of undefined。
  2. 构造即显示:构造器内部立即 document.body.appendChild(menu) 并定位——不需要调 show();要"先建好稍后显示"只能 hide() 收起。
  3. add(item) 对外基本不可用:它期望 ContextMenuItem 实例(读 .element),而该类未导出——传 {type:'normal',...} options 对象会崩。菜单项只能在构造 items 里一次性给全。
  4. 覆盖 document.onclick/onmousedown:构造器用赋值(非 addEventListener)挂全局点击关闭,会清掉宿主先前绑在 document 上的 onclick。
  5. opts.isSticky 在构造器里被读,但 ContextMenuOptions 接口没声明——粘附定位传 position:[x,y] 更可靠。
  6. 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")