前端知库

command/commandfor 怎样不写 JS 控制 dialog 和 popover?popover="hint" 和 auto、manual 有什么区别?

给 button 写 commandfor 指向目标元素的 id,再用 command 指定动作,比如 show-modal、close、toggle-popover,点击就能打开关闭 dialog 和 popover,不用写脚本。以 -- 开头的自定义命令会在目标上触发 command 事件。popover 的 auto 可点外面关闭并互斥,manual 只能手动关闭,hint 用于悬停提示,打开时不会关掉已打开的 auto popover。

参考答案

简答

以前打开一个模态对话框,至少要写一行 addEventListener 去调用 showModal()。现在给按钮写上 commandfor="对话框 id" 和 command="show-modal",点击就能打开,一行脚本都不用写。

command 内置了六个值:show-modal、close、request-close 控制 <dialog>,show-popover、hide-popover、toggle-popover 控制 popover。以 -- 开头的是自定义命令,浏览器不做内置动作,只在目标元素上触发 command 事件,由脚本处理。它不用等 JavaScript 下载执行,按钮一出现就能用,而且是 popovertarget 更通用的版本。

popover 属性有三种值。auto 是默认值,点外面或按 Esc 会关闭,打开一个会关掉其他非嵌套的 auto popover。manual 不能点外面关闭,也不会关掉别人,必须手动关。hint 适合悬停提示,可以点外面关闭,打开时不会关掉已经打开的 auto popover,只会关掉其他 hint。

要注意兼容性。Invoker Commands 是 Baseline 2025,2025 年 12 月起主流浏览器最新版都支持。popover="hint" 较新,Chrome 151、Firefox 153 起支持,Safari 正式版还没有。

用按钮直接控制 dialog

下面这个对话框没有一行脚本:

HTML
<button commandfor="confirm" command="show-modal">删除</button>

<dialog id="confirm" aria-labelledby="confirm-title">
  <h2 id="confirm-title">删除这条记录?</h2>
  <button commandfor="confirm" command="close" value="cancel">取消</button>
  <button commandfor="confirm" command="close" value="ok">删除</button>
</dialog>

commandfor 的值是要控制的元素的 id,command 是要执行的动作。show-modal 相当于调用 showModal(),对话框已经是模态时什么也不做。close 相当于调用 close(),按钮上的 value 会成为对话框的 returnValue,所以关闭以后照样可以在 close 事件里判断用户点的是哪个。

request-close 和 close 的区别在于,它会先触发 cancel 事件,再触发 close 事件。在 cancel 里调用 preventDefault() 可以阻止关闭,适合“表单还没保存,确定要关闭吗”这种情况。

对话框本身的用法、method="dialog" 和焦点规则,见《dialog 和 Popover API 应该怎样使用?》。

控制 popover 和自定义命令

HTML
<button commandfor="menu" command="toggle-popover">更多</button>
<div id="menu" popover>
  <button commandfor="menu" command="hide-popover">关闭</button>
</div>

toggle-popover、show-popover、hide-popover 分别等价于 popovertargetaction 的 toggle、show、hide。区别是 popovertarget 只能控制 popover,commandfor 还能控制 dialog 和自定义动作。

自定义命令以 -- 开头。浏览器不会做任何内置操作,只在目标元素上派发一个 CommandEvent:

JavaScript
const image = document.querySelector('#photo');
image.addEventListener('command', (event) => {
  if (event.command === '--rotate-left') image.style.rotate = '-90deg';
  if (event.command === '--rotate-right') image.style.rotate = '90deg';
});

这样做的好处是,按钮和目标的关系写在 HTML 里,脚本只监听目标元素,不用再给每个按钮各绑一个事件。event.command 是按钮上的命令值,event.source 是触发命令的那个按钮。

auto、manual 和 hint 怎么选

三种值的差别在两件事上:能不能点外面关闭(light dismiss),打开时会不会关掉别的 popover。

auto 是默认值,写 popover 或 popover="" 都是它。点外面或按 Esc 会关闭;打开一个 auto popover,一般会关掉其他已经打开的 auto popover,除非它们是嵌套关系。所以同一时间通常只有一个 auto popover,适合下拉菜单。

manual 不能点外面关闭,也不会被别的 popover 自动关掉,必须用按钮或脚本显式关闭。多个 manual popover 可以同时显示,适合通知、消息提示这类不该被随手关掉的东西。

hint 介于两者之间。它可以点外面关闭,也会响应 Esc。打开时不会关掉已经打开的 auto popover,但会关掉其他不是它祖先的 hint popover。它通常配合 mouseover、mouseout 或 focus、blur 事件用脚本打开和关闭,做悬停提示。比如菜单是 auto popover,菜单项上悬停显示的说明用 hint,说明出现时菜单不会被关掉。

这里有一个容易踩的坑:如果用点击按钮的方式打开 hint,而按钮在 auto popover 外面,这次点击本身就算点了 auto popover 的外面,会把它关掉。hint 自己不关 auto,但点击这个动作会。

常见追问

command 和 popovertarget 同时写会怎样?

MDN 没有说明哪个优先,同一个按钮只用一种写法即可。commandfor 能覆盖 popovertarget 的全部用法,新代码可以统一用 commandfor。

popover 的位置怎么定?

用锚点定位把浮层放在按钮旁边,见《CSS Anchor Positioning 适合解决哪些浮层布局问题?》。