如何将context-menu事件转发至同级div元素
问题描述
我开发了一个Web应用,用户可在div内创建可视化元素。其中一个场景是:用户创建了一个可点击且带有hover事件的div(class为me的元素),但它与带有context-menu事件的元素是同级关系,且显示在这些元素上方。
请问是否可以让位于上层的me元素保留自身的点击事件,同时将context-menu事件透明地转发到鼠标光标下方的元素(如class为cousin的子元素),即便它们是同级元素?
我曾尝试在editor div中捕获context-menu事件,找到鼠标光标下方的所有直接子元素并派发新的context-menu事件,但由于目标元素嵌套在同级元素内部,事件无法传递到它们。
原始代码示例
HTML
<header id="header">Header text<br/> x: ?<br/> y: ?</header> <div id="editor"> <div class="sibling"> <div class="nephew"> <div class="cousin"> <div class="text" oncontextmenu="alert('context-menu on \'Value 1\'')">Value 1</div> </div> <div class="cousin"> <div class="text" oncontextmenu="alert('context-menu on \'Value 2\'')">Value 2</div> </div> </div> </div> <div class="me" onclick="alert('clicked \'me\'')" oncontextmenu="alert('context-menu on \'me\'')"></div> </div>
CSS
#header { padding: 25px; border: 1px solid black; } #editor { position: relative; } .sibling { position: absolute; background-color: red; border: 5px solid blue; opacity: 0.3; width: 250px; height: 250px; top: 50px; left: 75px; } .me { position: absolute; opacity: 0.3; width: 300px; height: 300px; top: 25px; left: 50px; border: 5px solid black; } .me:hover { border-color: red; background-color: green; } .nephew { height: calc(100% - 30px); width: calc(100% - 30px); margin: 9px; padding: 5px; border: 1px solid white; } .cousin { color: yellow; align-content: center; text-align: center; margin: auto 0; height: 50%; width: 100%; border: 1px solid blue; }
JavaScript(原始尝试)
let editor = document.getElementById("editor"); editor.oncontextmenu = e => { // 阻止浏览器默认右键菜单 e.preventDefault(); for (let i = 0; i < editor.children.length; i++) { let currentChild = editor.children[i]; if (currentChild == e.target) { // 跳过触发事件的元素本身 continue; } let currentRect = currentChild.getBoundingClientRect(); if (currentRect.left < e.offsetX && currentRect.top < e.offsetY && currentRect.right > e.offsetX && currentRect.bottom > e.offsetY) { // 问题:事件只会派发给同级元素,无法传递到内部的text元素 // 因为text元素才有contextmenu事件,同级元素没有,所以此方法无效 let ev = new MouseEvent('contextmenu', { screenX: e.screenX, screenY: e.screenY, clientX: e.clientX, clientY: e.clientY, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, altKey: e.altKey, metaKey: e.metaKey, button: e.button, }); currentChild.dispatchEvent(ev); } } } let header = document.getElementById("header"); document.onmousemove = e => { header.innerHTML = 'Header text<br/>x: ' + e.pageX + '<br/>y: ' + e.pageY; }
解决方案
可以通过临时让上层元素不接收指针事件,再获取鼠标位置下的底层元素,最后转发contextmenu事件来实现,且无需依赖jQuery。
修改后的JavaScript代码如下:
const meElement = document.querySelector('.me'); const header = document.getElementById("header"); // 保留me元素的点击事件,无需修改 // 处理me元素的contextmenu事件 meElement.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); // 临时禁用me元素的指针事件,让底层元素能被检测到 meElement.style.pointerEvents = 'none'; // 获取鼠标坐标对应的最底层元素 const targetElement = document.elementFromPoint(e.clientX, e.clientY); // 恢复me元素的指针事件 meElement.style.pointerEvents = ''; // 给目标元素派发contextmenu事件 if (targetElement) { const contextMenuEvent = new MouseEvent('contextmenu', { bubbles: true, cancelable: true, clientX: e.clientX, clientY: e.clientY, screenX: e.screenX, screenY: e.screenY, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, altKey: e.altKey, metaKey: e.metaKey, button: e.button }); targetElement.dispatchEvent(contextMenuEvent); } }); // 原有的鼠标移动监听不变 document.onmousemove = e => { header.innerHTML = `Header text<br/>x: ${e.pageX}<br/>y: ${e.pageY}`; };
核心逻辑说明
- 临时禁用指针事件:通过设置
pointerEvents: none,让me元素暂时不拦截鼠标事件,这样document.elementFromPoint()就能准确获取到鼠标下方的嵌套元素(比如Value 1对应的div)。 - 事件转发:复制原contextmenu事件的所有属性,派发给找到的底层元素,确保其事件处理逻辑正常触发。
- 保留原有交互:仅在右键触发时临时修改指针属性,处理完成后立即恢复,不影响
me元素的点击和hover事件。
内容的提问来源于stack exchange,提问作者Jabbl
相关产品推荐
相关产品推荐

