Tooltip 还在算 getBoundingClientRect?CSS Anchor Positioning 已经能用了,我把 Tooltip、Popover、下拉菜单全用纯 CSS 写了一遍

Tooltip 还在算 getBoundingClientRect?CSS Anchor Positioning 已经能用了,我把 Tooltip、Popover、下拉菜单全用纯 CSS 写了一遍

导语

做 Tooltip、Popover、下拉菜单这些”附着在某个元素旁边”的组件,你是不是也烦透了?每次都拿 getBoundingClientRect() 算坐标,监听 scroll 和 resize 防位置漂移,响应式一改就容易崩。CSS Anchor Positioning API 来了,以后这些组件定位全靠浏览器自己算,JavaScript 只需要管内容。这篇文章我把 3 个最常见的场景用纯 CSS 写了一遍,你可以直接抄。

正文

问题:定位为什么非得 JavaScript 出手

Web 开发里有一个永恒的需求:某个元素(比如 Tooltip、下拉菜单、Popover)要”附着”在另一个元素(按钮、输入框、卡片)的旁边。

传统做法是用 JavaScript:

const rect = button.getBoundingClientRect();
tooltip.style.top = `${rect.bottom + 8}px`;
tooltip.style.left = `${rect.left}px`;

// 还要监听 scroll 和 resize 更新位置
window.addEventListener('scroll', updatePosition);
window.addEventListener('resize', updatePosition);

这套方案的问题很明显:

  • 代码量大:至少 30 行 JS + CSS 配合
  • 容易忘:scroll 监听加了吗?resize 呢?键盘 focus 呢?
  • 性能坑:scroll 事件频繁触发,不做 debounce 就会卡
  • 响应式难维护:断点变了位置逻辑要重写

如果浏览器能自动完成”元素 A 相对于元素 B 定位”这件事,那该多好。CSS Anchor Positioning 就是这个”好”。

方案:4 个核心属性 + 2 个函数

CSS Anchor Positioning 的核心思路是:给锚点元素声明一个名字,让浮层元素用这个名字做相对定位。整个过程不需要 JS 参与位置计算。

#### 第一步:给锚点起个名字

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

anchor-name 接受一个 值,以 -- 开头。这个属性让元素变成一个”锚点”,其他元素可以引用它。

#### 第二步:让浮层绑定锚点

.tooltip {
  position-anchor: --my-button;
}

position-anchor 声明这个元素要相对于哪个锚点定位。也可以直接用 HTML 属性:

提示信息

#### 第三步:用 anchor() 函数定位

这是最核心的部分。anchor() 函数让你引用锚点的各个边:

.tooltip {
  position: absolute;
  /* 浮层的顶部 = 锚点底部 + 8px 间距 */
  top: anchor(bottom 8px);
}

anchor() 支持的方向值:

含义
top 锚点的顶部边缘
bottom 锚点的底部边缘
left 锚点的左边缘
right 锚点的右边缘
start / end 逻辑方向(适配 RTL)
50% 锚点的中心点

#### 第四步:用 anchor-size() 做自适应

有时候你不想写死浮层宽度,想让它的宽度跟着锚点走:

.tooltip {
  width: anchor-size(width); /* 和锚点一样宽 */
  /* 或者 */
  min-width: anchor-size(width, 200px); /* 至少 200px,不超过锚点 */
}

anchor-size() 还支持 heightblockinline 等参数,基本覆盖了尺寸继承的所有场景。

实战 1:纯 CSS Tooltip

.has-tooltip {
  anchor-name: --btn;
  position: relative;
}

.tooltip {
  position-anchor: --btn;
  position: absolute;
  bottom: anchor(top);        /* 浮层底部对齐锚点顶部 */
  left: anchor(center);       /* 浮层左边缘对齐锚点中心 */
  translate: -50% 0;          /* 自身水平居中偏移 */
  padding: 6px 12px;
  background: #1a1a1a;
  color: #fff;
  border-radius: 6px;
  font-size: 14px;
}

对比传统 JS 方案,这里没有一行 JS 代码。浏览器自动处理滚动、resize 时的位置更新。

实战 2:纯 CSS 下拉菜单

.menu-wrapper {
  anchor-name: --menu-btn;
  position: relative;
  display: inline-block;
}

.dropdown {
  position-anchor: --menu-btn;
  position: absolute;
  top: anchor(bottom 8px);    /* 在按钮下方 8px */
  left: anchor(left);
  min-width: anchor-size(width, 180px); /* 至少 180px */
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  list-style: none;
  padding: 4px 0;
}

.dropdown li {
  padding: 8px 16px;
  cursor: pointer;
}

.dropdown li:hover {
  background: #f3f4f6;
}

这个下拉菜单的特点是:按钮宽度变了,菜单自动跟着变——因为用了 anchor-size(width, 180px),菜单宽度等于按钮宽度,最小 180px。

实战 3:Popover 自动防溢出

Anchor Positioning 还有个隐藏大招:position-try-fallbacks。当浮层空间不够时,浏览器会自动尝试其他位置。

.tooltip {
  position-anchor: --btn;
  position: absolute;
  position-try-fallbacks: flip-block, flip-inline;
  /* 先放下面 → 放不下就翻到上面 → 再不行翻到右边 */
}
/* 定义备选位置 */
@position-try --flip-up {
  top: auto;
  bottom: anchor(top 8px);
}

@position-try --flip-right {
  left: auto;
  right: anchor(left 8px);
  top: anchor(top);
}

这样你的 Tooltip 在屏幕边缘就不会被截断了——浏览器自动选一个能完整显示的位置。

兼容性:现在能用吗

截至 2026 年 7 月,CSS Anchor Positioning 的浏览器支持情况:

  • Chrome 125+:完整支持(2024 年 5 月)
  • Edge 125+:跟随 Chromium
  • Firefox:尚未支持(flag 后可启用)
  • Safari:尚未支持

Chrome 系已经稳定运行超过一年了。如果你的用户以桌面端为主,现在就是上车的时机。Safari 和 Firefox 的兼容方案也很简单——降级用 JS 定位,用 @supports 检测:

/* 降级:JS 控制显隐 */
.tooltip {
  display: none;
}

/* 支持 Anchor Positioning 时启用纯 CSS 方案 */
@supports (position-anchor: --x) {
  .tooltip {
    display: block;
    position-anchor: var(--anchor);
  }
}

收尾

CSS Anchor Positioning 不是”另一个定位方案”,它是浏览器终于把”元素之间的相对位置”这个逻辑接了过去。以前你写 50 行 JS 才能搞定的事,现在 10 行 CSS 就够了。

如果你手头有 Tooltip、Popover、下拉菜单、悬浮卡片这些组件,建议用这个周末把定位逻辑从 JS 迁到 CSS 上。一个判断标准:如果你的组件定位代码超过 20 行,那现在就是重构它的好时机。

评论区

0 条评论

登录后可评论。

查看完整榜单
查看完整榜单
查看完整榜单