CSS 锚点定位入门指南

本篇依然来自于我们的 《前端周刊》 项目!

由团队成员 嘿嘿 翻译,他的文章风格稳健而清晰,注重结构与逻辑的严谨性,善于用简洁的语言将复杂技术拆解成易于理解的知识点~

欢迎大家 进群 与他探讨 React 最新趋势,并持续追踪全球前端领域的最新动态!

原文地址:A gentle introduction to anchor positioning

锚点定位(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-startend/block-endstart/inline-startend/inline-end。如果必须,也可以用物理值:leftcenterrighttopbottom

完整的值列表可以查看 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 属性用于通过偏移量来控制元素位置。包括物理属性:toprightbottomleft;逻辑属性:inset-block-startinset-block-endinset-inline-startinset-inline-end;以及简写:inset-blockinset-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);
}

这里,lefttop 被替换成了 inset-inline-startinset-block-start

anchor() 里,你可以选择 startend(基于包含块),或 self-startself-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-areaanchor() 都能帮助你基于锚点来定位目标。选择哪种方式取决于你更喜欢哪种思维模式。如果你觉得网格更直观,那就用 position-area;如果更习惯基于边缘的精确控制,那就用 anchor()

你可以在 这个 CodePen 上玩一玩,换掉 position-area 的值,看看菜单会怎么移动,或者试试 anchor()

关于锚点定位的更多特性,可以查看 MDN 的详细文档 这里。另外还有个小游戏 Anchoreum,能帮你更轻松地理解锚点定位。

相关推荐
pany14 分钟前
体验一款编程友好的显示器
前端·后端·程序员
Zuckjet19 分钟前
从零到百万:Notion如何用CRDT征服离线协作的终极挑战?
前端
ikonan24 分钟前
译:Chrome DevTools 实用技巧和窍门清单
前端·javascript
顾林海24 分钟前
网络江湖的两大护法:TCP与UDP的爱恨情仇
网络协议·面试·性能优化
Juchecar24 分钟前
Vue3 v-if、v-show、v-for 详解及示例
前端·vue.js
ccc101828 分钟前
通过学长的分享,我学到了
前端
编辑胜编程28 分钟前
记录MCP开发表单
前端
可爱生存报告29 分钟前
vue3 vite quill-image-resize-module打包报错 Cannot set properties of undefined
前端·vite
__lll_29 分钟前
前端性能优化:Vue + Vite 全链路性能提升与打包体积压缩指南
前端·性能优化
weJee29 分钟前
pnpm原理
前端·前端工程化