你以为 tooltip 只能靠 JS 定位?今天 CSS Anchor Positioning 把这件事彻底原生化了

配了五年浮层防溢出,每次新写一个 tooltip 都要装 Floating UI(或者老的 Popper.js),然后配 autoUpdate、flip、shift、arrow 一整套 middleware,监听 scroll/resize/window-resize,最后在 React 组件里 useFloating() 返回的 floatingStyles 再写到 style 里。代码写了 20 行,运行起来每帧还在主线程跑 getBoundingClientRect。今天 CSS Anchor Positioning 把这件事彻底原生化了。

核心就一件事:给触发元素起个名字(anchor-name),浮层元素声明”我要绑到哪个名字”(position-anchor),浏览器在渲染引擎里算位置,完全不占 JS 主线程。

以前用 Floating UI 写的 tooltip:

import { useFloating, autoUpdate, offset, flip, shift, arrow } from '@floating-ui/react';

function Tooltip({ children, content }) {
  const { refs, floatingStyles } = useFloating({
    placement: 'top',
    middleware: [offset(8), flip(), shift(), arrow({ element: arrowRef })],
    whileElementsMounted: autoUpdate,
  });
  return (
    <>
      <button ref={refs.setReference}>{children}</button>
      <div ref={refs.setFloating} style={floatingStyles}>{content}</div>
    </>
  );
}

现在用 CSS Anchor Positioning:

function Tooltip({ children, content }) {
  return (
    <>
      <button>{children}</button>
      <div className="tooltip">{content}</div>
    </>
  );
}
button {
  anchor-name: --my-tooltip;
}
.tooltip {
  position: absolute;
  position-anchor: --my-tooltip;
  inset-area: top center;
  margin-bottom: 8px;
}

三行 CSS,删掉一个 npm 依赖,useFloating 不需要了,floatingStyles 不需要了,whileElementsMounted 不需要了。

position-area:网格速写,比 top/left/right/bottom 更直观

inset-area: top center 意思是”浮层放在锚点上方、水平居中”。完整的网格:

top left | top | top right
———+————-+———–
left | center/span | right
———+————-+———–
bottom lt| bottom | bottom right

比 bottom: calc(anchor(top) + 8px); left: anchor(center); translate: -50% 0 短多了。

溢出自动处理:position-try-fallbacks

浮层在视口边缘会溢出,flip middleware 的能力现在用 CSS 声明:

.tooltip {
  position-anchor: --my-tooltip;
  inset-area: top center;
  position-try-fallbacks: flip-block, --flip-below;
}

@position-try --flip-below {
  inset-area: bottom center;
}

flip-block 是内置关键字(不用写 @position-try),浏览器自动尝试”上方→下方”的 fallback。–flip-below 是自定义命名 fallback,可以定义任意位置策略。

配合 Popover API 零 JS 完整方案

浮层还需要点击显示/隐藏、手动关闭、背景遮罩?配合 HTML popover 属性:

<button popovertarget="menu">Open</button>
<div id="menu" popover>
  <ul>
    <li><a href="#">Option 1</a></li>
    <li><a href="#">Option 2</a></li>
  </ul>
</div>

popover 给浮层加了 show/hide 切换、ARIA 推断、点击外部关闭。CSS Anchor 给它加上”贴在某个元素旁边”的能力。两件事分开处理,各司其职,比以前全塞在 JS 里清晰得多。

性能可测量:删包大小、减主线程占用

  • Popper.js + 依赖:~15-20KB gzipped
  • Floating UI 核心:~8-12KB gzipped
  • CSS Anchor Positioning:0KB

主线程收益:Floating UI 在每次 scroll/resize 时 JS 重新算坐标,滚动帧里大概 2-4ms。CSS Anchor 全部在浏览器渲染引擎里走,不占 JS 主线程。Chrome DevTools Performance 面板里能看到 scroll 时 floating-ui 相关脚本消失。

浏览器支持(2026 年):

  • Chrome 125+ / Edge 125+:已稳定两年
  • Firefox 131+:已稳定
  • Safari 18.2+:已稳定
  • 全球覆盖:约 94%,已是 Baseline 2026

Safari 18.2 之前(iOS 18.1 及更早)需要 @supports (position-anchor: –x) 渐进增强,或者用 tiny polyfill。

迁移步骤:

  1. 搜索项目里 useFloating / @floating-ui/react / popper.js 的使用量
  2. 挑一个最简单的 tooltip 练手:先加 anchor-name,再改 CSS
  3. whileElementsMounted: autoUpdate 可以直接删——锚点位置随滚动自动更新,不需要监听
  4. flip/shift middleware → position-try-fallbacks: flip-block, flip-inline
  5. 复杂自定义定位 → 写 @position-try –custom-name { inset-area: … }
  6. popover 属性加在根 div 上,show/hide 逻辑完全不用 JS

CSS Anchor Positioning 不会替代所有 JS 浮层场景——需要跟随鼠标、任意坐标定位、精确尺寸测量的,还是需要 JS。但 90% 的 tooltip/popover/dropdown 场景,这个 API 已经够用了,而且够得刚刚好。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 11 阅读