【问题标题】:How to test if contextmenu is open or displayed?如何测试上下文菜单是否打开或显示?
【发布时间】:2021-03-09 02:39:20
【问题描述】:

我有一个页面,其中包含一些 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)};
    }
};

【问题讨论】:

  • 我试过你的代码,对我来说,打开上下文菜单复制所选代码不会删除选择:jsfiddle.net/koldev/qsg2eLvc 点击复制菜单项有效,自动选择代码被复制到剪贴板。您使用哪种浏览器? -- 编辑:代码在 Chrome 89 中有效,但在 Firefox 81 中没有复制代码。
  • 我认为只有阻止上下文菜单出现 (stackoverflow.com/a/737043/600135),并使用 HTML、CSS 和 JavaScript 创建自己的上下文菜单,才能解决 Firefox 中的这个问题。网上有很多实现,例如:sitepoint.com/…

标签: javascript html contextmenu


【解决方案1】:

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

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

简短说明

  1. 为您的codepre 元素添加自定义上下文菜单。在您的情况下,只需要一项 copy (在示例中快速!非常!简化!显示为简单框)。
  2. 打开菜单时禁用右键单击以避免停止选择您的代码(我理解的是您的问题/问题)
  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>

【讨论】:

  • 到目前为止,这似乎是要走的路。但是我很好奇是否可以一起跳过菜单并通过右键单击触发副本?
  • 我不太确定我是否做对了。您的意思是在右键单击时不打开自定义上下文菜单,而只是立即启动复制操作并显示消息“元素已被复制到剪贴板”?也可以是一种方式。在那种情况下,我认为这是一个关于用户体验的问题。这是一个意外的发生......坚果也许如果你在悬停元素时显示一条消息'在右键单击时复制元素......可能是一个解决方案。乍一看,这是一个类似的代码机制,因此示例可以适应。
  • 是的,右键复制,根本不出现菜单,甚至没有状态信息。假设发生了复制。复制后可以关闭突出显示。用户体验并不重要,因为它只是我。任何使用它的人都必须自己学习!
  • 啊...粗略和肮脏的代码 sn-p 或任何要重用的集合我认为'g ...好的。没有用户 UI ...但老实说:对于我自己,我至少会添加一个副本已经完成的响应。如果没有这些反馈,我会开玩笑......将这些功能添加到自己的编码工具中很有趣;-)
  • Seemonkey ... holla ... 一个 rar 请求 ... 你有没有看过使用的浏览器引擎(版本),他们换成实际的引擎了吗?我无法通过快速查看来弄清楚。这可能不是最新的,但这只是猜测。复制时:您需要查看具有复制操作的块。实际它使用innerText 属性来复制实际元素。只是简单的标准来演示。也许你想试试innerHtml。事实上,您需要根据您的需要调整代码......谢谢 4 赏金。真的很棒:-)
【解决方案2】:

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

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>

【讨论】:

  • “这不是您具体问题的真正答案”。
  • 同样好的通知:嗯。也许这不是您要求的具体方式,因此它不是必须的。但我们在这里寻求解决方案和建议。这是一个向前思考的建议。我认为如果 UX 是 /a 项目中的一个主题,那么值得考虑一下。不选择答案的原因。但也许不是投反对票的理由......
猜你喜欢
  • 2021-11-08
  • 2010-11-01
  • 2013-08-03
  • 1970-01-01
  • 1970-01-01
  • 2013-05-01
  • 2016-07-27
  • 1970-01-01
  • 2013-07-04
相关资源
最近更新 更多