如何测试上下文菜单是否打开或显示?

How to test if contextmenu is open or displayed?

我有一个页面,其中包含一些包含计算机代码的 pre 标签。我有一个 mouseover 事件侦听器,它突出显示 pre 标记中的所有代码。我还让它删除了 mouseout 事件的突出显示。如果您使用键盘复制 (ctrl-C),效果非常好。

但是如果你想从上下文菜单中右键单击并复制,那就有问题了。鼠标进入上下文菜单的那一刻,会触发pre标签的mouseout事件。

我需要一种方法来测试上下文菜单当前是否打开或显示。然后我可以取消删除突出显示。有没有办法测试上下文菜单是否打开或显示?

我什么都不要jquery,拜托了。

我对这个问题的最终选择可能是 oncontextmenu,但我不知道如果它关闭我将如何发现。除非我尝试为上下文菜单的 mouseout 事件监听事件,如果可能的话。

到目前为止,这是我的代码:

window.onload = function(){

    function selectText(element) {
        var range, selection;

        if(window.getSelection) {
            selection = window.getSelection();
            range = document.createRange();
            range.selectNodeContents(element);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }

    function unSelectText() {
        window.getSelection().removeAllRanges();
    }

    preTags = document.getElementsByTagName('PRE');

    for(var i = 0; i < preTags.length; i++) {
        preTags[i].onmouseover = function() {selectText(this)};
        preTags[i].onmouseout = function() {unSelectText(this)};
    }

    codeTags = document.getElementsByTagName('CODE');

    for(var i = 0; i < codeTags.length; i++) {
        codeTags[i].onmouseover = function() {selectText(this)};
        codeTags[i].onmouseout = function() {unSelectText(this)};
    }
};

我知道这并不是您具体问题的真正答案,但从用户体验的角度来看,悬停时选择文本可能会造成混淆。用户可能认为这只是悬停效果。最好在文本旁边有一个“复制”按钮或单击要复制的文本。这带来了用户只需单击一次的额外好处。

document.getElementById("code").addEventListener("click", function () {
  navigator.clipboard.writeText(this.innerText);
});
#code {
  cursor: pointer;
  display: inline-block;
  padding: 15px;
  border: solid 1px black;
}
<pre id="code">
print("hello, world")
print("hello, world")
print("hello, world")
</pre>
<p>Click to copy.</p>

由于 JS 中没有事件触发上下文菜单框的关闭操作,也没有可靠的解决方法:据我从不同的研究中得知,您的问题的答案是否定的。

但是如果您考虑类似的方法,可以使用自定义上下文菜单解决您的问题。

简短说明

  1. codepre 元素添加自定义上下文菜单。在您的情况下,只需要一项 copy(在示例中快速!非常!简化!演示为简单框)。
  2. 打开菜单时停用右键单击以避免停止选择您的代码(据我了解,您的 question/issue 是这样)
  3. 打开菜单时禁用右键单击其他元素,这样就不会打开其他上下文菜单
  4. 单击菜单项时开始复制操作关闭并重置
  5. 点击外部菜单时关闭并重置

简化示例

请注意:该示例是一个快速、不完整且非常简单的示例,用于演示该技术。我肯定需要适应你的特殊项目。

window.onload = function(){

    // general vars
    let isOpenContextMenu = false;
    const $contextMenu = document.getElementById('contextMenu');
    // all code/pre elements in one object to use in one loop
    const $codeElements = document.querySelectorAll('pre, code');
    let $actualCodeElement = {};



    // methods
    function selectText(element) {
        var range, selection;

        if(window.getSelection) {
            selection = window.getSelection();
            range = document.createRange();
            range.selectNodeContents(element);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }

    function unSelectText() {
        window.getSelection().removeAllRanges();
    }



    // listeners


    // block right clicke when context menu on code/pre element is open
    function listenerContextMenuBlocked(evt){
        evt.preventDefault();
    }


    // clicks when context menu on code/pre elements is open
    function listenerMenuClick(ev){ 

        let $clickedElement = ev.target;
        do{

            if($clickedElement == $contextMenu){

                // clicked on context menu

                // --> copy action
                let codeToCopy = $actualCodeElement.innerText;
                let temporaryInput = document.createElement('input');
                temporaryInput.type = 'text';
                temporaryInput.value = codeToCopy;
                document.body.appendChild(temporaryInput);
                temporaryInput.select();
                document.execCommand('Copy');
                document.body.removeChild(temporaryInput);

                // --> close menu and reset
                $contextMenu.classList.remove('contextMenu--active');
                isOpenContextMenu = false;
                window.removeEventListener('contextmenu', listenerContextMenuBlocked);
                return;
            }
            $clickedElement = $clickedElement.parentNode;

        } while($clickedElement)


        // clicked outside context menu
        // --> close and reset
        $contextMenu.classList.remove('contextMenu--active');
        isOpenContextMenu = false;
        window.removeEventListener('contextmenu', listenerContextMenuBlocked);

    }


    // open custom context menu when right click on code/pre elements
    function listenerOpenContextMenuCodeBlock(e) {

        e.preventDefault();
    
        // used to copy conten in listenerMenuClick() 
        $actualCodeElement = e.target;
            
        if(false === isOpenContextMenu){
            
            // open context menu
            $contextMenu.style.top = e.clientY + 2 + 'px';
            $contextMenu.style.left = e.clientX + + 2 + 'px';
            $contextMenu.classList.add('contextMenu--active');
            
            isOpenContextMenu = true;
            window.addEventListener('click', listenerMenuClick);
            window.addEventListener('contextmenu', listenerContextMenuBlocked);

        }

    }



    for(var i = 0; i < $codeElements.length; i++) {

        //$actualElement = $codeElements[i];  // 
        $codeElements[i].addEventListener('contextmenu', listenerOpenContextMenuCodeBlock);
        $codeElements[i].onmouseover = function() {
            if(false === isOpenContextMenu){
                selectText(this)
            }
        };
        $codeElements[i].onmouseout = function() {
            if(false === isOpenContextMenu){
                unSelectText(this)
            }
        };
    }

};
/* styles needed for custom context menu */
html {
    position: relative;

}
#contextMenu {
    display: none;
    position: absolute;
    font-family: sans-serif;
    font-size: 11px;
    line-height: 12px;
    padding: 2px 5px;
    background-color: #eeeeee;
    border: 1px solid #a5a5a5;
    box-shadow: 2px 3px 1px -1px rgba(0,0,0,0.4);;
    cursor: context-menu; 
    z-index: 10;
}

#contextMenu:hover {
    background-color: #aad7f3;
}
#contextMenu.contextMenu--active {
    display: block;
}
<!-- SIMPLIFIED custom context menu for code/pre elements = hidden on page -->
<nav id="contextMenu" style="top: 50px; left: 30px" >
    <div>Copy Codeblock</div>
</nav>


<!-- example elements for code presentation / testing -->
<pre>
    Lorem, ipsum dolor sit amet consectetur adipisicing elit. 
    Provident magni blanditiis, ea necessitatibus esse nihil, 
    quae iste explicabo beatae perspiciatis quibusdam tempora minima, 
    eos molestias illum voluptatum voluptate ipsum perferendis!
</pre>

<code>
    Li Europan lingues es membres del sam familie. Lor separat existentie 
    es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. 
    Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. 
    Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar 
    custosi traductores. 
</code>