下拉菜单和 tooltip 的坐标我算了五年,现在终于不用写了——CSS Anchor Positioning 把 JS 全删了

做下拉菜单、tooltip、浮动气泡这类 UI 元件,最麻烦的不是交互逻辑——是算坐标。元素要跟随某个按钮走,要防止跑到视口外,还要处理滚动事件下的位置更新。一套坐标计算逻辑,往往就是几十行甚至上百行 JavaScript。

CSS Anchor Positioning 这个 API,就是来删掉这堆 JS 的。

怎么用

先给「锚点」元素起个名字:

.button-anchor {
  anchor-name: --my-dropdown;
}

然后让「跟随元素」去「绑定」这个锚点:

.dropdown {
  position: absolute;
  top: anchor(bottom);
  left: anchor(left);
}

anchor() 函数就是关键。写 anchor(bottom),意味着「我的 top 边,对齐到锚点的 bottom 边」。不再需要 getBoundingClientRect(),不再需要手动算 offsetTop,浏览器自己处理。

防止跑出视口

纯靠坐标对齐还不够——如果锚点在页面最底部,dropdown 会直接被截掉。

CSS 早就想到了这个问题,position-try-fallbacks 专门处理这种场景:

.dropdown {
  position: try-fallbacks: flip-block, flip-inline, top;
}

当主位置会被截断时,浏览器会自动尝试候补方案:先翻转区块方向,再翻转行内方向,最后兜底放到顶部。全程不需要 JS 写任何碰撞检测。

之前怎么写的

简单还原一下旧方案的核心逻辑:

// 监听滚动和 resize,重新算位置
window.addEventListener(scroll, updatePosition, true);
window.addEventListener(resize, updatePosition);

function updatePosition() {
  const rect = button.getBoundingClientRect();
  const dropdownHeight = dropdown.offsetHeight;
  const viewportHeight = window.innerHeight;

  // 手动判断放上面还是下面
  let top = rect.bottom;
  if (rect.bottom + dropdownHeight > viewportHeight) {
    top = rect.top - dropdownHeight;
  }

  dropdown.style.top = top + px;
  dropdown.style.left = rect.left + px;
}

这还只是单次计算,没算滚动后位置更新、动态内容高度变化、嵌套滚动区域等各种变种。每加一个需求,if-else 就多一层。

现在怎么写

CSS 三行搞定:

.button-anchor { anchor-name: --my-dropdown; }
.dropdown {
  position: absolute;
  top: anchor(bottom);
  left: anchor(left);
  position-try-fallbacks: flip-block, top;
}

不需要 JS 监听滚动,不需要算视口边界,不需要处理 resize。滚动有更新?浏览器自己重新计算。内容高度变了?CSS 自动重排。

结论

Anchor Positioning 解决的问题很直接:把 UI 定位从 JavaScript 手里还回 CSS。这不是小优化,是架构层面的简化——省掉的代码行数可能不多,但省掉的是高频执行的计算逻辑和持续维护的心智负担。

Chrome 125、Safari 18.2、Firefox 正式支持,生产可用。下次写 dropdown 或 tooltip,先看 CSS 能不能解决,再动手写 JS。

评论区

0 条评论

登录后可评论。

Prompt 工程 2825 阅读