下拉菜单和 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。
评论区
登录后可评论。