配了八年浮动层,每次定位都要靠 JS 算坐标——今天 CSS anchor() 把这件事彻底原生化了

每次给浮层(tooltip、下拉菜单、弹出卡片)定位,都是同一套流程:先调 getBoundingClientRect() 拿到触发器和浮层各自的尺寸,再算相对位置,然后挂一个 ResizeObserver 监听变化,滚动的时候还要再算一遍。二十年了,这个流程几乎没变过。

CSS 锚点定位(CSS Anchor Positioning)把这个流程彻底原生化了。一个元素声明为「锚点」,另一个元素声明「跟着这个锚点走」,浏览器自己算坐标、自己在滚动和 resize 的时候更新。

anchor() 核心语法

触发器用 anchor-name 声明自己是锚点,浮层用 position-anchor 声明跟着谁走:

/* 触发器:声明自己是一个锚点 */
.trigger {
  anchor-name: --my-tooltip;
}

/* 浮层:声明跟着哪个锚点走,并描述相对位置 */
.tooltip {
  position-anchor: --my-tooltip;
  /* 浮层底边对齐触发器顶边,水平居中 */
  bottom: calc(anchor(top) + 8px);
  left: anchor(center);
  translate: -50% 0;
}

anchor() 函数接受两个参数:锚点名和边(top/bottom/left/right/center/start/end),返回锚点对应边的坐标。浮层定位全程浏览器自动处理,零 JS。

溢出交给 position-try-fallbacks

光定位还不够,浮层贴着视口边缘的时候浏览器怎么处理?

position-try-fallbacks 定义一组备选位置,浏览器自己判断当前是否会溢出、应该用哪个:

.tooltip {
  position-anchor: --my-tooltip;
  bottom: calc(anchor(top) + 8px);
  left: anchor(center);
  translate: -50% 0;

  /* 浏览器自动判断:如果上方溢出,就翻转到底部 */
  position-try-fallbacks: flip-block;
}

flip-block 翻转块轴方向(上变下),flip-inline 翻转内联轴方向(左变右),两者连用对角线翻转。如果预置关键字不够用,可以用 @position-try 自定义完整回退规则:

@position-try --below {
  top: calc(anchor(bottom) + 8px);
  bottom: unset;
}

@position-try --right {
  left: calc(anchor(right) + 8px);
  bottom: unset;
  top: anchor(center);
  translate: 0 -50%;
}

.tooltip {
  position-try-fallbacks: --below, --right;
}

浏览器按顺序尝试,第一个完全放进视口的就是最终位置,一个都不合适就保留原始位置。

配合 Popover 如虎添翼

CSS 锚点定位和 Popover API 是天然搭档:

.trigger {
  anchor-name: --menu-trigger;
}

#menu {
  position: absolute;
  position-anchor: --menu-trigger;
  top: calc(anchor(bottom) + 4px);
  left: anchor(left);
  position-try-fallbacks: flip-block;
}

不需要任何 JS 就能做到:点击按钮打开菜单、浮层跟着按钮走、贴近视口边缘自动翻转。Popover 负责开/关状态、焦点陷阱和轻关闭行为,anchor 定位负责找准自己的位置。

浏览器支持情况

Baseline 2026(2026 年 1 月起),Chrome 125+、Edge 125+、Safari 26+、Firefox 147+ 支持,约 83% 全球覆盖率。Safari 26 之前需要 @supports 降级,Firefox 暂不支持 named @position-try 规则。

/* 降级方案 */
.tooltip {
  position: absolute;
  top: 100%;
  left: 50%;
  translate: -50% 0;
}

@supports (anchor-name: --x) {
  .tooltip {
    position-anchor: --my-tooltip;
    position-area: bottom;
    position-try-fallbacks: flip-block;
  }
}

三个坑

命名必须以 — 开头。 anchor-name 的值是 CSS 自定义属性格式,必须是 –xxx,不是 –xxx 浏览器会静默当这个锚点不存在。

flip-block 不够用就用组合。 浮层贴在角落的时候,单轴翻转可能还是溢出一个方向,组合 flip-block flip-inline 才是全解。

Firefox 暂不支持 named @position-try。 如果你的回退逻辑很复杂,用的是自定义 @position-try 规则而不是 flip 关键字,Firefox 用户会看到原始位置,可能溢出。

下一步

三步落地:

  1. 打开 DevTools,在任意一个 tooltip 上试一行 anchor-name: --test,确认浏览器解析正常
  2. 选一个不复杂的浮层(如下拉菜单),用锚点定位替代现有 JS 逻辑,不要贪多,先跑通一个
  3. 如果你在用 Popper.js,可以开始计划迁移路径了,锚点定位能覆盖它 80% 的核心功能

CSS 把 JS 干了这件事,这几年一直在发生。这次是二十年最难解决的问题之一。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 12 阅读