用纯 CSS 做个提示气泡,不用引任何库

举报
柠檬-夏日清爽 发表于 2026/09/30 13:51:56 2026/09/30
【摘要】 鼠标悬停时弹出个小提示,这种 Tooltip 很多人第一反应是找组件库。其实用 data 属性加 ::after 伪元素,十几行 CSS 就能做出来,还能顺便做个小箭头。这篇讲完整实现,以及被父元素裁剪、超出屏幕边界、触屏上不生效、屏幕阅读器读不到这几个真实的限制。

鼠标停在一个图标上,弹出一行小字说明——这种 Tooltip 几乎每个项目都要用。很多人第一反应是引个 UI 库,但其实纯 CSS 十几行就能搞定,还能顺便做个小箭头。

这篇讲完整实现,以及几个"做出来发现有毛病"的常见问题。

一、最简版本

用一个自定义属性存提示内容,再用伪元素把它显示出来:

<button class="tip" data-tip="删除后不可恢复">删除</button>
.tip {
  position: relative;          /* 关键:给伪元素提供定位参照 */
}

.tip::after {
  content: attr(data-tip);     /* 把属性值当作内容显示 */
  position: absolute;
  bottom: 100%;                /* 贴在元素上方 */
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 8px;

  background: #2f3a4d;
  color: #fff;
  padding: 6px 10px;
  border-radius: 4px;
  font-size: 12px;
  white-space: nowrap;

  opacity: 0;                  /* 默认隐藏 */
  pointer-events: none;        /* 不挡鼠标 */
  transition: opacity .15s;
}

.tip:hover::after {
  opacity: 1;                  /* 悬停时显示 */
}

几个关键点:

  • content: attr(data-tip) 是核心,直接把 HTML 属性当作伪元素的内容,不用在 CSS 里写死文案。
  • position: relative 加在宿主元素上,否则伪元素的 absolute 会相对更外层的定位元素跑掉。
  • pointer-events: none 必须加,不然伪元素虽然透明但仍会拦截鼠标事件,可能导致 hover 状态闪烁。
  • 用 opacity 而不是 display,因为 display 没法做过渡动画。

二、加个小箭头

用另一个伪元素 ::before 画个三角形:

.tip::before {
  content: '';
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 2px;

  /* 用边框画三角形 */
  border: 6px solid transparent;
  border-top-color: #2f3a4d;

  opacity: 0;
  pointer-events: none;
  transition: opacity .15s;
}

.tip:hover::before {
  opacity: 1;
}

三角形是 CSS 的经典技巧:一个宽高为 0 的元素,给四条边框不同颜色,相邻边框的交界就是斜线。这里只要顶边有颜色、其余透明,就得到一个朝下的三角。

三、换个方向

上面的提示在元素上方。想要下方、左方、右方,改定位就行:

/* 下方 */
.tip-bottom::after {
  bottom: auto;
  top: 100%;
  margin-bottom: 0;
  margin-top: 8px;
}
.tip-bottom::before {
  bottom: auto;
  top: 100%;
  margin: 0;
  margin-top: 2px;
  border-top-color: transparent;
  border-bottom-color: #2f3a4d;
}

一般我会定义四个方向的 class(.tip-top / .tip-bottom / .tip-left / .tip-right),用的时候选一个。

纯 CSS 方案有四个绕不开的限制,心里要有数:

限制 表现 应对
被裁剪 祖先有 overflow: hidden 时只显示一半 换方向,或改用 JS 方案
超出屏幕 贴右边的元素,提示溢出看不见 边缘元素改用侧向,或限制宽度换行
触屏无效 手机上没有 hover 提示只能是补充信息
读屏读不到 伪元素不在 DOM 里 同时加 aria-label 或 title

四、第一个坑:被父元素裁剪

做出来发现提示只显示一半、或者完全看不见——多半是某个祖先元素设了 overflow: hidden。

伪元素是绝对定位的,会超出宿主元素的范围,祖先的 overflow: hidden 会把超出的部分裁掉。

几种解法:

  • 如果那个 overflow: hidden 只是用来清除浮动或裁剪圆角,换成 overflow: clip 试试(有些场景不影响)。
  • 把提示改成朝另一个方向(比如改到下方就不被上边界裁了)。
  • 实在不行只能用 JS 方案:把提示元素挪到 <body> 下,用 JS 计算位置。这是 UI 库的做法,但引入了复杂度。

五、第二个坑:超出屏幕边界

元素在页面最右边时,居中的提示会溢出屏幕,右边那截看不见。

纯 CSS 没有完美的自适应解法,几个折中:

  • 边缘的元素单独用 .tip-left(提示朝左展开)。

  • 限制宽度让它换行:

    .tip::after {
      max-width: 200px;
      white-space: normal;      /* 允许换行 */
      text-align: left;
    }
    
  • 或者干脆接受这个限制——大多数情况下元素不会正好贴边。

六、第三个坑:触屏上没有 hover

手机上没有悬停状态,所以纯 CSS 的 hover 提示在触屏设备上基本不生效(部分浏览器点一下会触发一次 hover,行为很不一致)。

所以:

  • 提示内容只能是补充信息,不能是必需信息。用户看不到也得能正常用。
  • 关键信息别放 tooltip 里,直接显示出来或者用可展开的说明。
  • 真要支持触屏点击弹出,那就得加 JS 了。

七、第四个坑:屏幕阅读器读不到

::after 生成的内容不在 DOM 里,大多数屏幕阅读器读不到(虽然规范上应该读,但实现参差不齐)。

所以正确做法是:同时用 aria-label 或 title 提供一份同样的信息。

<button class="tip" data-tip="删除后不可恢复" aria-label="删除(不可恢复)">
  删除
</button>

或者更简单——直接用原生 title 属性做兜底,浏览器会显示系统默认的提示(虽然样式不好看、还有延迟,但无障碍是好的)。

几个补充

  • 别在提示里放交互元素。纯 CSS 的 tooltip 没法点,放链接、按钮都没用(而且 pointer-events: none 挡着)。

  • 内容长的话别用属性。超长文案塞在 HTML 属性里很难维护,那种场景考虑用子元素 + JS 方案。

  • z-index 要给够。提示要盖在其他内容上面,但注意别盖住更重要的浮层(比如弹窗)。

  • 延迟显示。鼠标扫过就立刻弹出会有点烦,可以加个延迟:

    .tip::after {
      transition: opacity .15s .4s;    /* 延迟 0.4 秒再出现 */
    }
    .tip:hover::after {
      transition-delay: 0s;            /* 移出时立刻消失?其实这里是移入时无延迟 */
    }
    

    嗯这个写法要小心,通常想要的是"移入延迟、移出立即",写法是默认状态设延迟、hover 状态设 0。

小结

纯 CSS 的 Tooltip 实现成本很低——data-tip 属性存内容、::after 显示、::before 画三角、hover 控制 opacity,加起来二十行 CSS,不用引任何库。但要知道它的边界:祖先有 overflow 会被裁剪、贴边元素会溢出屏幕、触屏上无效、屏幕阅读器读不到。所以定位应该是"锦上添花的补充说明",关键信息别依赖它,同时用 aria-label 补一份给辅助技术。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。