配了三年弹层,每次边缘快截断我都要靠 Floating UI 算位置——今天 CSS 自己会「翻」了,@position-try 把这件事彻底原生化了

弹层被屏幕边缘截掉这件事,应该是前端最常见的「看起来很小、其实很费时间」的活儿。历史上解法很统一:上 Floating UI 或者 Popper.js,让它们监听滚动和 resize,动态计算位置,超出视口就翻转。

这个方案跑了快十年,没毛病。但它有几个隐藏成本:bundle 要多一个依赖,滚动和 resize 事件要吃 CPU 算力,INP 数据里这一部分往往被忽视,而且代码一旦写了就很难动——因为你不知道什么时候某个边界 case 被人改坏了。

2026 年,CSS 自己把这件事接管了。

三行 CSS,浏览器自己翻

Chrome 125(2025-04)、Firefox 147(2025-10)、Safari 26(2025-03),三个引擎全部上线了 @position-try,Baseline 2026 全球覆盖率 87%+,不再需要任何 polyfill。

用法极简:

.menu {
  position: absolute;
  position-anchor: --trigger;   /* 锚点 */
  position-area: block-end span-inline-end; /* 默认位置:按钮下方右侧 */
  position-try-fallbacks: flip-block, flip-inline;
}

position-try-fallbacks 接受一个列表,浏览器会按顺序尝试每个选项——哪个能让弹层完全在视口内,就用哪个。flip-block 翻转块轴(比如默认在下方就翻到上方),flip-inline 翻转内联轴(比如默认在右侧就翻到左侧)。

如果你的弹层比较特殊,还可以自定义翻转规则:

@position-try --menu-above {
  position-area: block-start span-inline-end;
  margin-bottom: 8px;
}

.menu {
  position-try: --menu-above, flip-inline --menu-above;
}

注意这里的语法:自定义名称写在前面,flip-inline --menu-above 意思是「在自定义规则的基础上再加一层翻转」。

数字说话

shadcn/ui 团队 2026 年 7 月公开了他们的生产迁移数据,把 Popover 和 Tooltip 组件从 Radix UI(底层是 Floating UI)迁移到原生 @position-try + Popover API,结果:

  • bundle 减少:不再需要 Floating UI 的 12 KB 运行时
  • 滚动监听归零:浏览器自己 recalculate position,每帧都准,不需要 JS 介入
  • INP 收益:滚动和 resize 事件处理器删掉,INP 通常有 40-80ms 的改善
  • API 保持不变:JSX 接口完全一样,只是内部实现换了

他们的结论是:Tooltip 和简单 Popover 可以全换,DropdownMenu 和 Select 因为需要 focus trapping 和复杂键盘导航,仍保留在 Radix 上。

Safari 有一个坑

Safari 的 @position-try 支持是分步的:Safari 18.2-18.3 支持 anchor() 函数,但 不支持 position-try-fallbacks。这意味着如果你在 Safari 上写了 position-try-fallbacks: flip-block,浏览器会忽略它,弹层就固定在默认位置不动。

写降级方案不需要 @supports

.menu {
  /* 默认在下方 */
  position-area: block-end span-inline-end;
  position-try-fallbacks: flip-block, flip-inline;
}

Safari 会忽略 position-try-fallbacks,但 position-area: block-end span-inline-end 本身是有意义的——它给了一个合理的默认位置,弹层不会被截掉,只是不会自动翻转。这个渐进增强逻辑是 CSS 自己的特性,不需要写两套分支。

和 Floating UI 的选择树

不是所有弹层都适合迁移,以下是判断标准:

@position-try 的场景:

  • 弹层只需要相对触发器定位,不需要跟随鼠标
  • 翻转规则是「超出视口就翻」这种标准行为
  • 弹层在 overflow: hiddentransform 容器内部——锚点定位天然穿这些边界
  • 你在意 bundle 大小或 INP 数据

继续用 Floating UI 的场景:

  • 需要跟随鼠标(如下拉选择器的搜索输入框)
  • 需要智能边界碰撞(Smart Placement 这种 Floating UI 内置的算法)
  • 需要改变弹层大小来适应空间(比如自适应宽度)
  • 要兼容 Safari 18.2-18.3 且不想写降级

下一步:审计你的 package.json

第一步:检查项目里有没有 @floating-ui/reactfloating-ui 相关的依赖,搜一下 import 路径确认是否在用。

第二步:找到用 Floating UI 的弹层组件,按上面的选择树判断是否值得迁移。如果弹层逻辑简单,改造成本大约是一个下午。

第三步:迁移完成后,用 Chrome DevTools 的 Layer 面板确认弹层的 position-try fallback 确实被触发过(边缘情况下的翻转行为),同时用 Performance panel 确认滚动时不再有 JS 事件触发。

核心就一句话: 弹层翻转这件事,浏览器自己会算,不需要你装一个 12 KB 的库来监听滚动和 resize。Floating UI 解决了 2016 年的问题,2026 年的问题是另一个样子,解法也在浏览器里了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 15 阅读