鼠标一悬停就弹出小气泡,工具提示这玩意儿咋整才靠谱?

3,324字
14–21 分钟
in

搞网站的时候经常碰到那种小图标,鼠标挪上去就蹦出一行字告诉你这是干啥的,这种小气泡就是工具提示(Tooltip)。很多老铁做的时候直接甩一个title属性就完事,结果屏幕阅读器压根不鸟它,触屏设备直接翻车。这篇东西就掰扯掰扯工具提示的正确打开方式,包括两种不同的使用场景、对应的代码姿势,还有那些容易踩坑的细节。

目录

啥是工具提示

工具提示说白了就是给界面上的控件配一个简短的文字小纸条。它不是弹窗那种霸道总裁式的拦截,而是安安静静躲在角落,只有鼠标悬停或者键盘聚焦到那个控件的时候才冒出来。举个栗子,像Stripe网页右上角那个小铃铛图标,鼠标挪过去就飘出一句“通知”,这就是典型的工具提示。这玩意儿只放纯文字,绝对不能往里塞按钮、链接这些能交互的东西,否则就变味了,那得用<dialog>弹窗来搞。

两种常见场景

工具提示在实战中基本只干两件事:要么给图标起个名字(标签),要么给图标补充一句说明(描述)。这两者用的ARIA属性不一样,搞混了屏幕阅读器的朗读顺序就乱套了。

场景类型核心作用推荐属性
标签型告诉这是啥aria-labelledby
描述型补充说明aria-describedby

方案一 做标签

有些图标光秃秃的没有文字,比如一个齿轮图标代表“设置”,一个心形图标代表“收藏”。这种情况下工具提示的作用就是给这个图标提供一个可见的名字。正确的做法是用aria-labelledby把按钮和工具提示串起来。

操作步骤:

  1. 在按钮内部先放好图标(比如用SVG或者字体图标)。
  2. 在按钮下方或旁边写一个<div>,加上role="tooltip"属性和一个唯一的id
  3. 给按钮加上aria-labelledby属性,属性值填上那个工具提示的id
  4. 如果需要显示动态数字(比如未读消息条数),可以在按钮里再放一个带id<span>,然后aria-labelledby里用空格隔开多个id,朗读顺序按从左到右。

代码示范:

<!-- 标签型工具提示:小铃铛图标的标签是“通知” -->
<button aria-labelledby="bellLabel">
  <!-- 这里是铃铛图标,实际项目中换成svg或字体 -->
  🔔
</button>

<div role="tooltip" id="bellLabel">
  通知
</div>

<!-- 带数字的例子:朗读“3 通知” -->
<button aria-labelledby="msgCount msgLabel">
  🔔
  <span id="msgCount">3</span>
</button>

<div role="tooltip" id="msgLabel">
  未读消息
</div>

写这段代码的时候有个坑要留意:aria-labelledby引用的元素在页面上可以是隐藏的(比如工具提示默认不可见),但DOM结构里必须存在。另外这个属性会覆盖按钮本身可能有的文字内容,所以如果按钮里本来写了文字,那些文字就不会被读出来了。很多新手在这翻车,明明按钮里有“关闭”两个字,加了aria-labelledby后屏幕阅读器反而读不出“关闭”了。

方案二 做描述

有的图标本身已经有名字了(比如按钮上写了“消息”二字),但是还想再加一句补充说明,像“查看并管理通知设置”。这时候工具提示扮演的是辅助描述的角色,需要用aria-describedby。而且图标本身还得有一个可访问的名字,通常的做法是放一段视觉上隐藏但屏幕阅读器能读到的文字。

操作步骤:

  1. 按钮里除了图标之外,加一个<span>包着描述性的文字,并给这个<span>加上class="visually-hidden"(视觉隐藏类)。这类CSS的写法是position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0;
  2. 写工具提示的<div>,带上role="tooltip"和唯一id
  3. 给按钮加上aria-describedby,属性值填工具提示的id
  4. 屏幕阅读器的朗读顺序:先读按钮里的可见文字(包括视觉隐藏的那个名字),然后停顿一下,再读aria-describedby指向的内容。

代码示范:

<button class="notifications" aria-describedby="notiDetail">
  <!-- 铃铛图标 -->
  🔔
  <span id="msgNum">3</span>
  <span class="visually-hidden">通知</span>
</button>

<div role="tooltip" id="notiDetail">
  查看并管理通知的各项设置
</div>

这里要特别留意:aria-describedby指向的内容不要跟按钮原有的名字重复,否则屏幕阅读器会念两遍差不多的东西,听起来很傻。另外aria-describedby支持多个id,但建议只用一个,因为多个的话某些读屏软件处理得不太流畅。

触屏翻车换这个

工具提示在触摸屏上基本是废的,因为没鼠标悬停,而且手指点一下按钮就会触发点击事件,根本没机会看到那个小气泡。碰到移动端或者需要显示较多内容(甚至带按钮)的提示场景,别硬撑,换用切换提示(Toggletip)。这玩意儿长成一个小圆圈里面带个“i”字母的样子,点一下蹦出一段话,再点一下或者点外面就关闭。

实现方法:

  1. 做一个<span>作为容器,里面放一个<button>,按钮的文本或aria-label写上“更多信息”。
  2. 再放一个<span>,加上role="status",里面塞需要展示的提示文本。role="status"是一个实时区域,屏幕阅读器会在它内容变化时自动读出来。
  3. 用JavaScript控制点击按钮时切换那个提示<span>的显示和隐藏。

代码示范:

<span class="tog-container">
  <button type="button" aria-label="点我查看说明">
    ⓘ
  </button>
  <span role="status" class="tog-content hidden">
    这里填上需要解释清楚的补充信息,比如“该设置会在凌晨三点自动同步”。
  </span>
</span>

<style>
.hidden {
  display: none;
}
.tog-content {
  display: block;
  /* 其他样式:背景、圆角、阴影等 */
}
</style>

<script>
const btn = document.querySelector('.tog-container button');
const content = document.querySelector('.tog-container [role="status"]');
btn.addEventListener('click', () => {
  const isHidden = content.classList.contains('hidden');
  if (isHidden) {
    content.classList.remove('hidden');
  } else {
    content.classList.add('hidden');
  }
});
// 点页面其他地方关闭的逻辑省略,但实际要加上
</script>

写切换提示的时候记得把按钮做成可聚焦的,并且支持Escape键关闭。另外role="status"每次显示都会重新朗读内容,如果用户反复点开,读屏会反复念,可以考虑改用aria-live="polite"来控制频率。

那些年踩过的坑

title属性做出来的假工具提示千万别再用,那个东西键盘完全没法聚焦,而且屏幕阅读器的支持稀烂,长文本还显示不全。aria-haspopup属性也不要跟role="tooltip"一起出现,因为那个属性是给菜单类弹窗用的,混在一起会让辅助技术误以为工具提示里藏着交互内容。最关键的一条:永远不要把重要信息只放在工具提示里。万一哪个旧版屏幕阅读器抽风忽略掉了aria-describedby,这部分信息就彻底消失了。得保证就算工具提示完全不工作,页面的核心功能也不受影响。