锚点定位的怪癖

原文信息: 查看原文查看原文

Anchor Positioning Quirks

- Juan Diego Rodríguez

我非常激动地宣布,从本周开始,CSS-Tricks年鉴为每个与新的锚点定位API相关的属性、函数和@规则都添加了条目!在过去的一个月里,我一直在努力全面理解这个新模块,并尽我所能地解释它。然而,锚点定位是一个新特性,它带来了定位绝对元素的新动态,因此它肯定会有一些奇怪的怪癖,甚至可能还有一些潜伏的错误。

为了庆祝这一报道,我想讨论一下我在深入研究这些内容时遇到的那些令人头疼的问题,并将它们分解开来,希望你能避免像我一开始那样一头撞到墙上。

内缩修改后的包含块

静态元素包含块是一个相当直接的概念:它是该元素的父元素的内容区域。但是当谈到绝对定位元素时,事情就变得棘手了。默认情况下,绝对定位元素的包含块是视口或元素最近的非static位置的祖先元素,或者是属性如containfilter中的某些值。

总的来说,关于绝对元素包含块的规则并不难记。虽然锚点定位和包含块有它们的怪癖(例如,锚点元素必须在定位元素之前被绘制),但我想重点讨论内缩修改后的包含块(从这里开始我将其缩写为IMCB)。

关于内缩修改后的包含块的信息并不多,现有的信息直接来自锚点定位规范模块。这告诉我,虽然它不是CSS中的新东西,但它绝对是因为锚点定位而变得重要的东西。

我能找到的最好的解释直接来自规范

对于绝对定位的盒子,内缩属性有效地将其包含块缩小到指定的量。生成的矩形称为内缩修改后的包含块。

所以如果我们内缩一个绝对定位元素的(带有topleftbottomright等),其包含块就会按照每个属性的值缩小。

.absolute {
  position: absolute;
  top: 80px;
  right: 120px;
  bottom: 180px;
  left: 90px;
}

在这个例子中,元素的包含块是整个视口,而它的内缩修改后的包含块距离顶部80px,距离右侧120px,距离底部180px,距离左侧90px

内缩修改后的包含块示例。它从顶部缩小了80px,从右侧缩小了120px,从底部缩小了180px,从左侧缩小了90px

了解IMCB的工作原理并不是学习CSS的首要任务,但如果你想充分理解锚点定位,这是一个必须知道的概念。例如,position-areaposition-try-order严重依赖这个概念。

position-area属性的情况下,目标包含块可以被细分为一个由四条想象中的线划分的网格:

  1. 目标包含块的起点。
  2. 锚点元素的起点或anchor(start)
  3. 锚点元素的终点或anchor(end)
  4. 目标包含块的终点。

示例说明了我们如何将锚点元素的包含块想象成一个3x3的非对称网格

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-detailsaria-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年鉴现在有了大量的信息来帮助澄清事情。

最令人兴奋的是,锚点定位还处于早期阶段。这意味着有更多的令人困惑的事情等着我们去发现和学习!

分享于 2024-09-18

访问量 88

预览图片