Firefox 中的 HTML5 上下文菜单(屏幕截图和代码)

您可能不知道,HTML5 规范不仅限于我们在页面中添加的内容,还定义了浏览器的一些部分如何通过 HTML、CSS 和 JavaScript 供开发者使用。这些规范中的一部分是 上下文菜单,或“右键菜单”。使用 HTML5 和菜单元素,您可以添加新的选项,而无需编写浏览器插件。Firefox 8(当前版本)支持这些功能。请查看以下屏幕截图以了解 上下文菜单演示

图像示例非常简单,实际上是由 Paul Rouget 编写的一个演示 在最初的 Firefox 错误请求中。其核心是 HTML 代码

HTML5

如您所见,您可以通过其 ID 将 menu 元素链接到一个元素。contextmenu 属性指向此元素。每个菜单可以有多个 menuitems。每个项目都有一个文本标签和一个可能的图标。您还可以嵌套 menu 元素以创建多层菜单。在这里,我们添加内联onclick处理程序以指向不同的 JavaScript 函数,并在菜单项被激活时调用。生成的上下文菜单如下所示

image with a context menu

功能很简单,所有 rotate()resize() 函数所做的就是使用 querySelectorclassList 向图像添加类名

function rotate() {
  document.querySelector('#menudemo').classList.toggle('rotate');
}
function resize() {
  document.querySelector('#menudemo').classList.toggle('resize');
}

真正的效果在于 CSS 变换和过渡。由于图像的 ID 为 menudemo,因此在 CSS 中需要以下代码来旋转和调整大小

#menudemo {
  -moz-transition: 0.2s;
  width:200px;
}
#menudemo.rotate {
  -moz-transform: rotate(90deg);
}
#menudemo.resize {
  -moz-transform: scale(0.7);
}
#menudemo.resize.rotate {
  -moz-transform: scale(0.7) rotate(90deg);
}

请注意,在实际产品中,我们当然应该添加其他浏览器前缀并取消前缀,但由于该功能目前仅在 Firefox 中有效,因此对于此演示来说足够了。

检测支持和视觉提示

现在,由于这扩展了浏览器中正常的用户功能,因此我们需要明确指出存在一个右键菜单。在 CSS3 中,有一个 context-menu 光标可供我们使用。当上下文菜单可用时,应显示此光标

.contextmenu #menudemo, .contextmenu .demo {
  cursor: context-menu;
}

我们通过检查 body 元素上的 contextmenu 和窗口中的 HTMLMenuItemElement 来测试浏览器的支持(这也已作为拉取请求添加到 Modernizr 中)。

if ('contextMenu' in document.body && 'HTMLMenuItemElement' in window) {
  document.documentElement.classList.add('contextmenu');
} else {
  return;
}

HTMLMenuItemElement 不够吗?是的,但真正的上下文菜单仅在合理的情况下提供功能,这就是 contextMenu 发挥作用的地方。

根据功能打开或关闭菜单项

作为一个稍微复杂一点的示例,让我们向文档中添加“统计字数”功能。为此,我们生成一个计数器元素,当统计完字数时,它将成为一个工具提示

var counter = document.createElement('span');
counter.id = 'counter';
counter.className = 'hide';
document.body.appendChild(counter);

counter.addEventListener('click', function(ev){
  this.className = 'hide';
},false);

默认情况下隐藏此元素,并在删除 hide 类时显示。为了使其平滑,我们使用过渡

#counter{
  position: absolute;
  background: rgba(0,0,0,0.7);
  padding:.5em 1em;
  color: #fff;
  font-weight:bold;
  border-radius: 5px;
  -moz-transition: opacity 0.4s;
}
#counter.hide{
  opacity: 0;
}

我们从两个带有上下文菜单的部分开始

然后,我们循环遍历所有具有 wordcount 类的 menuitems 并应用功能。

var wordcountmenus = document.querySelectorAll('.wordcount'),
    i = wordcountmenus.length;

while (i--) {
  wordcountmenus[i].addEventListener('click', function(ev){
    // add functionality
  }, false);
}

我们需要找出页面中选择了什么。我们通过使用 getSelection() 并将其字符串版本在空格处拆分来实现。然后,我们通过删除 hide 类名来显示计数器。

var wordcountmenus = document.querySelectorAll('.wordcount'),
    i = wordcountmenus.length;

while (i--) {
  wordcountmenus[i].addEventListener('click', function(ev){
    var text = document.getSelection(),
        count = text.toString().split(/s/).length;
    counter.innerHTML = count + ' words';
    counter.className = '';
  }, false);
}

您可以在第二个 上下文菜单演示 中看到它的实际效果。现在,问题在于(如屏幕截图中所述),它始终统计字数,而不管用户是否选择了某些文本。我们希望菜单仅在选择了文本时处于活动状态。

context menu item available or not available depending on selection

因此,为了使我们的菜单仅在有意义时才可用,我们检查文档中是否存在选择。每个上下文菜单在打开时都会触发一个名为 contextmenu 的事件。因此,我们只需要订阅此事件即可。

当文档中选择了某些内容时,document.getSelection().isCollapsed 为 true。否则为 false,因此我们只需要相应地启用或禁用菜单项即可

document.querySelector('#interactive').addEventListener(
  'contextmenu', function(ev) {
    this.querySelector('.wordcount').disabled =
    document.getSelection().isCollapsed;
  },
false);

最后一个要解决的问题是鼠标的位置,以便放置计数器元素。由于菜单选择事件没有提供鼠标位置,因此我们需要向整个文档添加一个 contextmenu 处理程序,以便在打开菜单时将计数器隐藏在菜单后面

document.body.addEventListener(
  'contextmenu', function(ev) {
    counter.style.left = ev.pageX + 'px';
    counter.style.top = ev.pageY + 'px';
    counter.className = 'hide';
  },
false);

进一步阅读和资源

关于 Chris Heilmann

HTML5 和开放网络的布道者。让我们修复它!

更多 Chris Heilmann 的文章…


19 条评论

  1. Mårten Björk

    好消息!这有多容易访问?例如,有没有办法表明字数是上下文菜单中操作的结果?

    2011 年 11 月 24 日 03:09

  2. fpiat

    我们只能添加新选项,还是有方法/属性可以禁用或隐藏浏览器菜单选项?

    2011 年 11 月 24 日 03:20

    1. fpiat

      必须阅读“Can we only add”而不是“had”

      2011 年 11 月 24 日 03:22

    2. passcod

      我认为这很不方便,实际上可能存在安全风险。第一个愚蠢的用途可能是禁用“查看源代码”菜单项或“查看图像”/“保存图像”项以进行伪保护……但我认为它可能远不止于此,从漏洞利用的角度来看。

      2011 年 11 月 24 日 04:26

      1. Chris Heilmann

        是的,这就是为什么目前您“只能添加”新项目而不能覆盖原始项目的原因。

        2011 年 11 月 24 日 06:51

        1. Ronny

          嘿,Chris,感谢你的文章!这很酷。
          阅读本文时,我首先想到的是“嘿嘿,让我们尝试用一些自定义操作替换后退之类的默认操作,这可能很有趣”。
          所以我想“只添加”是一个不错的选择 ;-)

          2011 年 11 月 24 日 09:48

      2. Jonas Finnemann Jensen

        如果我正在编写一个 Web 应用,这将非常棒,没有理由让“查看源代码”、“保存图像”等占用上下文菜单中的空间。如果是一个 Web 应用,它们就没有用,为什么我需要能够保存按钮的图像?

        人们总能在其他地方找到这些选项,例如在菜单中或手动下载源代码。

        我的意思是,当您强制 Web 应用拥有所有这些仅在网站是文档时才有的残余内容时,您会降低 Web 应用平台的吸引力。

        相信 Web 开发者,如果他们想要隐藏图像,就像您所说的那样“愚蠢”,他们可以使用 js、css 或任何其他方法来实现。

        2011 年 11 月 24 日 11:07

        1. Jonas Finnemann Jensen

          我的意思是,从可用性的角度来看,允许用户删除默认上下文菜单项是有意义的。

          如果一些 Web 开发人员希望通过滥用此功能来破坏默认可用性,那是他们的损失。

          我同意带有“您不能在此处右键单击”的警报很愚蠢,但为什么不让那些想变得愚蠢的人变得愚蠢呢?:)

          2011 年 11 月 24 日 11:19

          1. dhinesh

            这是一个不错的理论

            2012 年 9 月 12 日 03:26

  3. Rodney Rehm

    我发现很有趣,你们链接到 Addy Osmani 的 HTML5 contextmenu polyfill 分支。他的仓库不同步。您可以在 http://medialize.github.com/jQuery-contextMenu/ 找到真正的版本 :) - 这里有原生演示:http://medialize.github.com/jQuery-contextMenu/demo/html5-polyfill-firefox8.html

    我很想知道这是如何发生的。是因为 Addy Osmani 在网络上很有名而我并不出名吗?还是寻找 polyfill 的人只是链接到 Google 上的第一个结果?

    关于您的上下文菜单内容是否有路线图?目前,无法(视觉上)区分上下文菜单中的控件、复选框和单选按钮。(除了我在原生实现中遇到的其他困难)。我应该向谁提出我的问题?

    2011 年 11 月 24 日 05:57

    1. Chris Heilmann

      实际上,那是因为当我在向他展示原生实现时,我们彼此交谈过,然后他开始关注它。他是第一个回复并开始在 Google+ 上讨论的人。与出名无关。很高兴获得您的资源和反馈。我将尝试找出谁是与您讨论此事的最合适人选。复选框问题是否存在未解决的错误?

      2011 年 11 月 24 日 06:49

      1. Rodney Rehm

        没有,还没有打开任何错误。我应该先这样做,还是等待一些 Mozilla 开发者的讨论?感谢您更新链接 :)

        2011 年 11 月 24 日 06:59

        1. Rodney Rehm

          好了,我在 Bugzilla 中找到了:https://bugzilla.mozilla.org/show_bug.cgi?id=705292

          2011 年 11 月 25 日 09:15

  4. Luke Dorny

    在我们讨论 HTML5 时,只是一个小小的细节,在 Section 元素内部没有标题。如果不是,它应该是一个 div 或其他更合适的东西。
    否则,这是一个令人着迷的新可能性领域,让您的网站感觉像是一个应用,或者更好。很棒的文章。

    2011 年 11 月 24 日 10:37

  5. Daniel Piechnick

    我可以看到很多不同的应用场景。我们能在 IE 中很快看到它吗?:)

    Daniel Piechnick

    2011 年 11 月 25 日 00:22

  6. kangax

    只是一个小的调整 - `’contextMenu’ in document.documentElement` 会更好,因为在 body 还不存在的情况下。

    此外,`”HTMLMenuItemElement” in window` 究竟应该确定什么?菜单项元素受支持?它只是让我想知道这种关系检查是否安全 - 在添加对菜单项元素的支持时,是否真的需要实现公开+全局地公开 HTMLMenuItemElement 接口。IDL 是否在任何地方提到过它?

    2011 年 11 月 26 日 17:03

  7. Isuru

    正是我正在为我正在处理的脚本寻找的!真的很酷!

    2012 年 1 月 11 日 20:07

  8. icaaq

    提醒一下,menuitem-element 尚未成为 html 规范的一部分。请关注此问题 :) https://www.w3.org/Bugs/Public/show_bug.cgi?id=13608

    2012 年 3 月 19 日 06:16

  9. pedz

    这很有趣。我在浏览时偶然发现了它,试图回答以下请求:我正在寻找如何向用户提供视觉提示以表明上下文菜单可用的建议。

    我有一个表格,就像数据库中的报告一样,“命中”一行,大约有 7 列。每个元素都有自己的上下文菜单。目前,没有向用户提供视觉提示来表明所有这些功能都可用,并且根据反馈,人们不知道它们的存在。

    而且……也许我甚至不应该在这些点上设置上下文菜单,但这是我能想到的。当前的实现大约在 2008 年,使用 javascript 和一个在右键单击时弹出的隐藏 UL 元素。

    2012 年 5 月 25 日 06:10

本文的评论已关闭。