悬停交互不用JS了,Chrome的Interest Invoker能整啥活?

3,623字
15–23 分钟
in

还在为鼠标悬停弹窗写一堆mouseenter和mouseleave事件?Chrome 139开始整了个新活儿,叫Interest Invoker API,直接用HTML属性搞定悬停菜单、工具提示、快速操作面板这些玩意儿。这东西是Open UI社区提的方案,主打一个声明式悬停触发,浏览器自动接管鼠标进出事件,代码量直接跳水。

目录

啥是兴趣触发器

兴趣触发器就是那个被鼠标指着、或者手指长按(触摸屏)的元素。目前只支持三种标签:<a>链接、<button>按钮、以及图片热区<area>。关键属性叫interestfor,值要指向目标元素的ID。看个例子:

<!-- 链接当触发器 -->
<a interestfor="infoBox">瞅一眼详情</a>
<div id="infoBox" popover>这里是详细内容,鼠标移开就消失</div>

<!-- 按钮当触发器 -->
<button interestfor="tipBtn">悬停看提示</button>
<div id="tipBtn" popover>记得保存前先备份数据</div>

注意这里用interestfor代替了以前声明式弹窗里的popovertarget。相当于少写一个属性,浏览器就知道鼠标悬停时要打开哪个弹窗。触摸屏上长按触发器也会触发,但具体表现还有点玄学——目前最佳方案是长按后从上下文菜单选“查看更多”,体验算不上丝滑。

悬停目标咋配置

目标元素必须带上popover属性,否则悬停没反应。popover有三种玩法:

类型轻关方式对其他弹窗影响
autoESC或点击外部关闭非嵌套弹窗
hintESC或点击外部仅关闭其他hint
manual无轻关不鸟别人
<!-- auto:默认,最常用 -->
<div id="target1" popover>鼠标上来就出现</div>

<!-- hint:专治弹窗打架 -->
<div id="target2" popover="hint">跟其他hint互斥</div>

<!-- manual:头铁,自己玩 -->
<div id="target3" popover="manual">理论上不会自动消失</div>

但这里有个大坑:不管用哪种popover类型,鼠标离开触发器或目标区域后,这玩意儿都会消失。这就很迷,尤其是manual模式,说好的“不轻关”呢?目前Chrome实验版就是这么跑的,可能是为了统一悬停交互的预期行为。做项目时别指望manual能留住面板,老老实实接受“移开即消失”的设定。

延迟那点事

刚上手会发现悬停后弹窗要等半秒才出来,移开后又等半秒才消失。第一反应是“卡了?”,后来才明白这是防手滑设计。如果鼠标不小心划过一个小按钮,没延迟就直接弹脸,那网页就没法正常点了。

/* 给不碍事的提示框去掉显示延迟 */
.quick-tip[interestfor] {
  interest-show-delay: 0;
}

/* 键盘聚焦时去掉隐藏延迟,方便读屏软件 */
[interestfor]:focus-visible {
  interest-hide-delay: 0;
}

默认延迟是0.5秒,写在触发器上。用interest-show-delayinterest-hide-delay单独调,或者interest-delay: 0.2s 0.1s这种简写(先显示后隐藏)。实测发现单位写错会翻车——interest-show-delay: 0有时不生效,必须写0s0ms。这个bug在Chrome 139里还飘着呢,建议上线前多设备测一测。

键盘玩家福音

用Tab键聚焦到触发器时,浏览器会进入“部分兴趣”状态。这时候目标弹窗会出现,但里面的可聚焦元素(比如按钮、链接)默认不给焦点,方便快速跳过。用户想深入操作可以按Option+上箭头(Mac)或Alt+上箭头(Win),弹窗里的小组件才变得可点。

/* 自定义那个提示文字的背景色,但别改内容 */
:target-of-partial-interest::after {
  background: #f0f0f0;
  border-radius: 4px;
}

/* 给有键盘兴趣的触发器加个描边 */
[interestfor]:has-partial-interest {
  outline: 2px solid blue;
}

系统默认会在:target-of-partial-interest::after里显示当前设备对应的快捷键提示(比如“Press ⌥↑ to activate”)。改掉内容会让用户懵逼,但样式随便折腾。另外CSS新出了一个interactivity: not-keyboard-focusable,效果类似tabindex="-1",专门用在:target-of-partial-interest上,让弹窗内的焦点自动禁用。

完整方案一:做一个工具提示

拿最常见的鼠标悬停显示小贴士开刀。

<!-- 方案一代码 -->
<p>提交表单前记得 <span interestfor="saveTip" style="border-bottom:1px dashed">保存草稿</span></p>
<div id="saveTip" popover="hint" style="background:#ff9; padding:4px 8px; border-radius:6px">
  每隔30秒自动存一次,放心写
</div>

步骤拆开讲:

  1. <span>?不行,触发器只认<a> <button> <area>。上面例子用了<span>是错的!必须换成<a><button>。改正版:
<a href="javascript:void(0)" interestfor="saveTip" style="border-bottom:1px dashed">保存草稿</a>
<div id="saveTip" popover="hint" class="tooltip-content">每隔30秒自动存一次</div>
  1. 给目标加popover="hint",这样同时出现多个提示时后一个会关掉前一个,不会堆满屏幕。
  2. 样式自己调,但注意popover默认会生成一个顶层图层,z-index不用愁。
  3. 触摸屏上长按“保存草稿”会弹出浏览器上下文菜单,目前没法直接显示提示。一个变通做法:同时监听contextmenu事件(虽然违反无JS的初衷,但现实很骨感)。或者等Chrome后续版本完善长按映射。

完整方案二:悬停卡片带延迟微调

做一个用户头像,鼠标指上去显示个人简介卡片,要求响应快、移开别立刻消失。

<button interestfor="userCard" class="avatar">🐱 猫大仙</button>
<div id="userCard" popover="auto" class="hover-card">
  <img src="avatar.jpg" alt="" width="40">
  <div>全栈摸鱼师,热爱写bug</div>
  <a href="/profile">看详细</a>
</div>
/* 减少显示延迟到0.2秒,增加隐藏延迟到0.8秒,给手残党留条活路 */
.avatar[interestfor] {
  interest-show-delay: 0.2s;
  interest-hide-delay: 0.8s;
}

.hover-card {
  margin-top: 8px;
  background: white;
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  border-radius: 12px;
  padding: 12px;
  width: 200px;
}

操作细节:

  • 鼠标从按钮滑到卡片上时,因为有0.8秒隐藏延迟,只要手指够快就能移进卡片区域而不消失。
  • 卡片内的<a>链接在悬停状态下是可点击的,但注意悬停一离开整个卡片就会消失,所以链接要够大够好点。
  • 如果卡片里放表单输入框,用户刚准备打字,鼠标稍微抖一下就没了——这场景就别用悬停触发了,改用点击弹窗更合适。

额外收获:CSS焦点禁用新属性

这个API顺带产出了一个叫interactivity: not-keyboard-focusable的CSS值。以前想禁止键盘聚焦得写tabindex="-1",现在纯样式就能干:

/* 让所有带 partial-interest 的目标内的按钮都不可聚焦 */
:target-of-partial-interest button {
  interactivity: not-keyboard-focusable;
}

tabindex更干净,尤其适合那些“先预览再决定是否深入”的悬浮组件。不过目前兼容性还嫩,建议当作渐进增强来用。

触摸屏的悬停模拟依然是个老大难。长按触发上下文菜单再选“显示详情”这种交互,普通用户根本不知道。现阶段做移动端适配,最好额外加一个点击展开的备选方案,比如用<details>元素兜底。等Interest Invoker正式版出来再砍掉也不迟。