真正靠谱的 tooltip 需用 aria-describedby + role="tooltip" 实现语义化,JS 动态定位并监听 mouseenter/focus/blur/keydown 事件,兼顾键盘导航、屏幕阅读器支持与边界检测。
纯 CSS 实现的 title 属性提示太简陋、不支持样式和交互;用 JS 手动监听 mouseenter / mouseleave 容易漏掉焦点管理、键盘可访问性差——真正靠谱的 tooltip 要兼顾语义、样式可控、键盘导航(Tab 进出)、屏幕阅读器识别,且避免 z-index 溢出或位置错位。
aria-describedby + role="tooltip" 做语义化基础浏览器原生 title 属性无法定制样式、不触发 focus 事件、对屏幕阅读器支持不稳定。正确起点是用 WAI-ARIA 标准标记关联关系:
<button aria-describedby="my-tooltip">保存</button><div id="my-tooltip" role="tooltip" aria-hidden="true">点击后数据将同步至云端</div>
关键点:
aria-describedby 必须指向 tooltip 元素的 id,屏幕阅读器会自动读出内容role="tooltip" 告诉辅助技术这是浮动提示,不是普通 divaria-hidden="true" 初始隐藏,JS 控制显隐时同步切换该属性值position: absolute + getBoundingClientRect() 算定位CSS 的 top/left 固定值无法适配不同尺寸按钮或滚动位置。必须用 JS 动态计算:
立即学习“前端免费学习笔记(深入)”;
const btn = document.querySelector('button');const tooltip = document.getElementById('my-tooltip');function updateTooltipPosition() { const rect = btn.getBoundingClientRect(); tooltip.style.left = `${rect.left + window.scrollX}px`; tooltip.style.top = `${rect.bottom + window.scrollY + 4}px`; // 下方 4px 间距}
注意几个坑:
offsetTop/offsetLeft:它们受父级 position 影响,不可靠window.scrollX/window.scrollY,否则页面滚动后 tooltip 错位transform 容器内,getBoundingClientRect() 仍准确,但需额外处理缩放偏移(少见,先忽略)requestAnimationFrame(updateTooltipPosition) 防抖,避免频繁重排:hover 的缺陷CSS :hover 无法响应键盘焦点(Tab 进入按钮时 tooltip 不出现),也不支持延迟显示/隐藏。必须用 JS 绑定三类事件:
btn.addEventListener('mouseenter', showTooltip);btn.addEventListener('focus', showTooltip); // 键盘用户btn.addEventListener('mouseleave', hideTooltip);btn.addEventListener('blur', hideTooltip); // 失焦时隐藏
实际中还要加防抖和延迟:
setTimeout(如 300ms),防止快速划过误触发setTimeout + clearTimeout,鼠标移出后短暂悬停仍保持显示mouseleave 并取消隐藏定时器,否则鼠标从按钮移到 tooltip 上会闪退Esc 键应关闭 tooltip(绑定 document 的 keydown)tooltip 经常被其他元素(如 header、modal)盖住,或鼠标无法移入 tooltip 内容区域:
z-index 值不能写死,推荐用 CSS 变量统一管理:z-index: var(--z-tooltip, 1000)
pointer-events: auto(默认是 auto,但若父级设了 none 就会继承)pointer-events: none
hover,需监听 touchstart 触发显示,并在 touchend 后延迟隐藏(避免遮挡操作)最麻烦的其实是 tooltip 边界检测:当按钮靠近视口边缘时,tooltip 会溢出。这需要判断 rect.right 是否超窗宽,再动态切到左/上/下方位。真要用,别手写,引入 @floating-ui/dom 库更稳——它专治定位、碰撞、虚拟元素、滚动跟随这些事,比自己算 margin 和 transform 可靠得多。