我非常激动地宣布,从本周开始,CSS-Tricks年鉴为每个与新的锚点定位API相关的属性、函数和@规则都添加了条目!在过去的一个月里,我一直在努力全面理解这个新模块,并尽我所能地解释它。然而,锚点定位是一个新特性,它带来了定位绝对元素的新动态,因此它肯定会有一些奇怪的怪癖,甚至可能还有一些潜伏的错误。
为了庆祝这一报道,我想讨论一下我在深入研究这些内容时遇到的那些令人头疼的问题,并将它们分解开来,希望你能避免像我一开始那样一头撞到墙上。
内缩修改后的包含块
静态元素包含块是一个相当直接的概念:它是该元素的父元素的内容区域。但是当谈到绝对定位元素时,事情就变得棘手了。默认情况下,绝对定位元素的包含块是视口或元素最近的非static
位置的祖先元素,或者是属性如contain
或filter
中的某些值。
总的来说,关于绝对元素包含块的规则并不难记。虽然锚点定位和包含块有它们的怪癖(例如,锚点元素必须在定位元素之前被绘制),但我想重点讨论内缩修改后的包含块(从这里开始我将其缩写为IMCB)。
关于内缩修改后的包含块的信息并不多,现有的信息直接来自锚点定位规范模块。这告诉我,虽然它不是CSS中的新东西,但它绝对是因为锚点定位而变得重要的东西。
我能找到的最好的解释直接来自规范:
对于绝对定位的盒子,内缩属性有效地将其包含块缩小到指定的量。生成的矩形称为内缩修改后的包含块。
所以如果我们内缩一个绝对定位元素的(带有top
、left
、bottom
、right
等),其包含块就会按照每个属性的值缩小。
.absolute {
position: absolute;
top: 80px;
right: 120px;
bottom: 180px;
left: 90px;
}
在这个例子中,元素的包含块是整个视口,而它的内缩修改后的包含块距离顶部80px
,距离右侧120px
,距离底部180px
,距离左侧90px
。
了解IMCB的工作原理并不是学习CSS的首要任务,但如果你想充分理解锚点定位,这是一个必须知道的概念。例如,position-area
和position-try-order
严重依赖这个概念。
在position-area
属性的情况下,目标包含块可以被细分为一个由四条想象中的线划分的网格:
- 目标包含块的起点。
- 锚点元素的起点或
anchor(start)
。 - 锚点元素的终点或
anchor(end)
。 - 目标包含块的终点。
position-area
属性使用这个3×3的想象中的网格包围目标来定位自己。所以,如果我们有两个元素…
<div class="anchor">Anchor</div>
<div class="target">Target</div>
…附加了锚点定位:
.anchor {
anchor-name: --my-anchor;
height: 50px;
width: 50px;
}
.target {
position: absolute;
position-anchor: --my-anchor;
height: 50px;
width: 50px;
}
…我们可以使用position-area
属性定位.target
元素:
.target {
position: absolute;
position-anchor: --my-anchor;
position-area: top left;
height: 50px;
width: 50px;
}
IMCB缩小以适应我们选择的网格区域内,在这个例子中,是左上角区域。
position-try-order
也使用IMCB的尺寸来决定如何排序在position-try-fallbacks
属性中声明的回退。它检查哪一个回退提供了最大的可用高度或宽度,这取决于你是用most-height
还是most-width
值设置属性。
我花了很长时间才理解这个概念,但我认为Una Kravets在https://chrome.dev/anchor-tool/上的视觉工具完美地展示了这一点。
规范与实现
在我研究锚点定位时,规范是我最好的朋友。然而,理论只能带你走这么远,玩一个新特性是理解它如何工作的乐趣部分。在锚点定位的情况下,有些东西写在规范里但在浏览器中实际上不起作用(当时是基于Chromium的浏览器)。在茫然地盯着我的屏幕之后,我发现问题是如此简单,以至于我甚至没有考虑过:浏览器和规范不匹配。
锚点定位与其他许多特性不同,它很快就被引入到浏览器中。第一稿于2023年6月发布,仅一年后,它就在Chrome 125上发布。为了把它放在透视中,自定义属性的第一稿在2012年发布,我们等了四年才看到它被实现(尽管Firefox在其他浏览器之前很多年就发布了)。
我对浏览器快速推出新的CSS特性感到兴奋。虽然能更快地获得新东西是很棒的,但它使浏览器和CSSWG之间的空间变小了,以重塑特性和完善现有的草稿。请记住,一旦某样东西在浏览器中可用,就很难改变或移除它。在锚点定位的情况下,浏览器很早就推出了某些属性和函数,最终在规范完全确定为候选建议之前就被改变了。
这有点令人困惑,但截至Chrome 129+,以下是Chrome推出需要改变的东西:
position-area
inset-area
属性被重命名为position-area
(#10209),但它将得到支持,直到Chrome 131。
.target {
/* from */
inset-area: top right;
/* to */
position-area: top right;
}
position-try-fallbacks
position-try-options
被重命名为position-try-fallbacks
(#10395)。
.target {
/* from */
position-try-options: flip-block, --smaller-target;
/* to */
position-try-fallbacks: flip-block, --smaller-target;
}
inset-area()
inset-area()
包装函数不再存在于position-try-fallbacks
(#10320)中,你可以直接写值,而不需要包装。
.target {
/* from */
position-try-options: inset-area(top left);
/* to */
position-try-fallbacks: top left;
}
anchor(center)
最初,如果我们想要从中心定位一个目标,我们将不得不写这个复杂的语法:
.target {
--center: anchor(--x 50%);
--half-distance: min(abs(0% - var(--center)), abs(100% - var(--center)));
left: calc(var(--center) - var(--half-distance));
right: calc(var(--center) - var(--half-distance));
}
CWSSG工作组解决了(#8979),增加了anchor(center)
参数,大大简化了这个过程。
.target {
left: anchor(center);
}
错误!
一些错误潜入了锚点定位的浏览器实现中。例如,规范说如果一个元素没有默认的锚点元素,那么position-area
属性就不起作用。这是一个已知问题(#10500),但仍然可以复制,所以请不要这样做。
以下代码…
.container {
position: relative;
}
.element {
position: absolute;
position-area: center;
margin: auto;
}
另一个例子来自position-visibility
属性。如果你的锚点元素在屏幕外,你通常希望其目标也被隐藏。规范说默认值是anchors-visible
,但浏览器却使用always
。
Chrome目前没有反映规范。它确实使用了
always
作为初始值。但规范的文本是有意的——如果你的锚点在屏幕外或以其他方式滚动,你通常希望它隐藏(#10425)。
锚点定位的可访问性
虽然锚点定位最直接用例是用于工具提示、信息框和弹出框等,但它也可以用于许多其他东西。检查这个由Silvestar Bistrović提供的例子,例如,他用线条连接元素。他将元素连接在一起是为了装饰目的,所以锚点定位并不意味着元素之间有语义关系。因此,非视觉代理,如屏幕阅读器,不知道如何解释两个看似无关的元素。
如果我们的目标是将一个工具提示链接到另一个元素,我们需要在DOM中建立一个关系,让锚点定位处理视觉效果。令人高兴的是,有一些API(如Popover API)为我们做到了这一点,甚至建立了我们可以利用的锚点关系,以创造更有吸引力的视觉效果。
一般来说,规范描述了一种使用ARIA属性(如aria-details
或aria-describedby
)以及目标元素上的role
属性来创建这种关系的方法。
所以,虽然我们可以连接以下两个元素…
<div class="anchor">anchor</div>
<div class="toolip">toolip</div>
…使用锚点定位:
.anchor {
anchor-name: --my-anchor;
}
.toolip {
position: absolute;
position-anchor: --my-anchor;
position-area: top;
}
…但屏幕阅读器只看到两个元素并排在一起,没有任何明显的关联。这对可访问性来说是个问题,但我们可以使用相应的ARIA属性轻松修复它:
<div class="anchor" aria-describedby="tooltipInfo">anchor</div>
<div class="toolip" role="tooltip" id="tooltipInfo">toolip</div>
现在它们在视觉和语义上都连接在一起了!如果我们能在没有ARIA的情况下做到这一点就更好了。
结论
对一个新特性感到困惑,最终理解它,是任何程序员都能体会到的最令人满意的经历之一。虽然锚点定位仍然有一些(而且确实有一些)令人困惑的地方,但我很高兴地说,CSS-Tricks年鉴现在有了大量的信息来帮助澄清事情。
最令人兴奋的是,锚点定位还处于早期阶段。这意味着有更多的令人困惑的事情等着我们去发现和学习!