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
下面这个对话框没有一行脚本:
<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 和自定义命令
<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:
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 适合解决哪些浮层布局问题?》。