本篇依然来自于我们的 《前端周刊》 项目!
由团队成员 嘿嘿 翻译,他的文章风格稳健而清晰,注重结构与逻辑的严谨性,善于用简洁的语言将复杂技术拆解成易于理解的知识点~
欢迎大家 进群 与他探讨 React 最新趋势,并持续追踪全球前端领域的最新动态!
锚点定位(Anchor Positioning)允许你根据另一个元素的位置来放置页面上的元素。它能让你仅用 CSS(少写点代码),更轻松地创建响应式的菜单和工具提示(tooltip)。下面我们来看看它是怎么工作的。
假设你在导航栏里有一个头像(avatar),像这样:

带有两个文本选项和一个头像的导航栏
当你点击头像时,希望菜单能显示在它正下方。点击交互部分可以用 Popover API 仅通过 CSS 来 实现。但点击之后,菜单到底该出现在哪里呢?
以前这通常需要 JavaScript 来计算。但现在,有了锚点定位,只需要几行 CSS 就能搞定。锚点定位会根据头像的位置来决定菜单该放哪。
比如,你可能想让菜单出现在头像下方,左对齐,就像这样:

导航栏头像菜单展开并左对齐
或者你想让它挂在头像旁边,在右边开个派对,也行:

导航栏头像菜单展开到头像右侧
你可以把它放在多个位置,不过我觉得第一个例子比较常见,很多网站和 Web 应用里都能见到。我们来看看怎么写代码实现它。
第一步是让菜单知道它对应的头像。
一种很好的理解方式是:把菜单想象成锚定(anchor)在头像上的。这样我们就称头像为 锚点(anchor) ,菜单为 目标(target) 。
你需要在头像元素上声明一个 anchor-name
来命名锚点。因为头像表示你的用户信息按钮,所以我们给它一个 profile-button
的类名。
代码如下:
css
.profile-button {
anchor-name: --profile-button;
}
接着,在菜单(目标)上声明 position-anchor
,它的值就是你刚才声明的 anchor-name
。这就建立了连接,让目标知道该跟随哪个锚点。
css
.profile-menu {
position-anchor: --profile-button;
}
最后一步是给菜单一个绝对定位(absolute)或固定定位(fixed),像这样:
css
.profile-menu {
position-anchor: --profile-button;
position: absolute;
}
很好,连接建立完成!接下来我们要决定菜单出现的位置。
position-area
其中一种方法是使用 position-area
属性。
position-area
允许你在一个九宫格中放置元素,其中锚点在中间。这个九宫格的范围就是锚点的包含块(containing block)。

九宫格,头像在中间,标注了 left、center、right、top、bottom
你可以用这个网格来决定菜单的位置。想让菜单显示在右上角?写 top right
就行。
css
.profile-menu {
position-anchor: --profile-button;
position: absolute;
position-area: top right;
}

九宫格,头像在中间,右上角蓝色标记
但问题是,top right
看似直观,却并不是最佳写法。
在 CSS 中,我们应尽量使用逻辑属性(logical properties),而不是物理属性(physical properties)。这样更具包容性,不会依赖于书写方向或语言。
这是使用逻辑属性的同一个九宫格:

九宫格,头像在中间,标注 start/block-start、center、end/block-end、end/inline-end、start/inline-start
把上面的 top right
改成逻辑写法就是 block-start inline-end
:
css
.profile-menu {
position-anchor: --profile-button;
position: absolute;
position-area: block-start inline-end;
}

九宫格,头像在中间,右上角蓝色标记 block-start inline-end
想让它在下方居中?写成逻辑的 block-end center
:
css
.profile-menu {
position-anchor: --profile-button;
position: absolute;
position-area: block-end center;
}

九宫格,头像在中间,下方居中蓝色标记 block-end center
不过你的菜单比头像宽,所以如果用 block-end center
,它会超出网格,看起来就像这样:

去掉网格线后就是这样:

这可不是你想要的效果。如何实现干净的左对齐呢?
你可以把 position-area
设置为 block-end span-inline-end
。这样菜单会从头像下方开始,沿着内联方向展开,像这样:

代码如下:
arduino
.profile-menu {
position-anchor: --profile-button;
position: absolute;
position-area: block-end span-inline-end;
}
完美,正是你想要的!
主要的逻辑值有:start/block-start
、end/block-end
、start/inline-start
、end/inline-end
。如果必须,也可以用物理值:left
、center
、right
、top
、bottom
。
完整的值列表可以查看 MDN 的文档 这里。接下来让我们继续定位。
桌面端没问题,菜单右边有足够空间。但在移动端,视口更窄,菜单就容易溢出。更好的做法是让菜单右对齐,往左展开。要实现这种切换,我们需要在空间不足时告诉菜单尝试另一个位置。
锚点定位正好支持这一点!这就是它的响应式灵活性。如果定义的位置没空间,它会自动尝试别的位置。这可以通过 position-try
来实现。
示例:
arduino
.profile-menu {
position-anchor: --profile-button;
position: absolute;
position-area: block-end span-inline-end;
position-try: block-end span-inline-start;
}
position-try
顾名思义,就是让元素尝试一个新的位置。效果如下:
窗口变窄时,菜单自动从右对齐切换到左对齐。
Anchor()
anchor()
函数提供了另一种定位思路。position-area
是基于九宫格,而 anchor()
则是基于锚点的 边缘 来放置目标。
它只能用于 inset 属性。inset 属性用于通过偏移量来控制元素位置。包括物理属性:top
、right
、bottom
、left
;逻辑属性:inset-block-start
、inset-block-end
、inset-inline-start
、inset-inline-end
;以及简写:inset-block
、inset-inline
。
比如,要让菜单左侧和头像左侧对齐,可以用 left: anchor(left)
;再让菜单顶端和头像底部对齐,可以用 top: anchor(bottom)
:
css
.profile-menu {
position-anchor: --profile-button;
position: absolute;
left: anchor(left);
top: anchor(bottom);
}
不过我们还是应该用逻辑属性,改写如下:
css
.profile-menu {
position-anchor: --profile-button;
position: absolute;
inset-inline-start: anchor(start);
inset-block-start: anchor(end);
}
这里,left
和 top
被替换成了 inset-inline-start
和 inset-block-start
。
在 anchor()
里,你可以选择 start
、end
(基于包含块),或 self-start
、self-end
(基于锚点内容)。
此外,anchor()
还能显式指定锚点名称。如果写成这样:
css
.profile-menu {
position-anchor: --profile-button;
position: absolute;
left: anchor(--profile-button left);
top: anchor(--profile-button bottom);
}
如果不写,默认会用 position-anchor
设置的那个。
更有趣的是,anchor()
可以和 calc()
结合。假设头像容器有 1.25em 的 padding,你只想让菜单对齐头像图片(不包含 padding),可以写成:
css
.profile-menu {
position-anchor: --profile-button;
position: absolute;
inset-inline-start: calc(anchor(start) + 1.25em);
inset-block-start: anchor(bottom);
}
效果如下(粉色线表示对齐):

position-area
和 anchor()
都能帮助你基于锚点来定位目标。选择哪种方式取决于你更喜欢哪种思维模式。如果你觉得网格更直观,那就用 position-area
;如果更习惯基于边缘的精确控制,那就用 anchor()
。
你可以在 这个 CodePen 上玩一玩,换掉 position-area
的值,看看菜单会怎么移动,或者试试 anchor()
。
关于锚点定位的更多特性,可以查看 MDN 的详细文档 这里。另外还有个小游戏 Anchoreum,能帮你更轻松地理解锚点定位。