锚点定位

放置提示框或下拉菜单时,您通常希望将其定位在网页上的另一个元素旁边。虽然之前也有一些方法可以使用绝对定位来实现这种效果,但对于更复杂的要求,过去通常会使用 JavaScript 来定位项目。

CSS 锚点定位提供了一种以声明方式相对于另一个元素定位元素的方法。

网络共享元素

若要将某个元素设为锚点,请为其指定一个以两个短划线开头的任意字符串作为 anchor-name 值。这是定位元素将用于查找其锚点的标识符,最好为其指定一个描述性名称。如果某个元素将以不同的方式用作锚点,您甚至可以为其指定多个锚点名称。

您需要在定位的元素上设置一些属性,以便将其系留。首先,您需要通过设置 position: absoluteposition: fixed 将元素从文档流中拉出,使其浮动。

接下来,您需要通过将 position-anchor 设置为在锚点上设置的锚点名称,来设置要系绳到的锚点。

最后,您需要设置锚点的位置。在本单元稍后的部分中,您将进一步了解 position-area

#anchor {
   anchor-name: --my-anchor;
}

#positionedElement {
     position: absolute;
     position-anchor: --my-anchor;
     position-area: end;
}

隐式连接

弹出式窗口的关联方式更加简单。当您使用带有 popovertarget 的按钮打开 popover 或通过 showPopover({source}) 设置 source 时,popover 已设置“隐式锚点”。由于默认情况下,弹出式窗口已通过 position: fixed 浮动,因此要定位弹出式窗口,您只需设置位置即可。

#anchor{}

#positionedElement {
  position-area: end;
  margin: unset;
}

确定潜在的锚定广告

您可以将锚点定位实现为组件的一部分,以便在多个位置使用下拉菜单之类的模式。如果您多次使用同一 anchor-name,如何确保每个定位元素都能找到正确的锚点?

JavaScript 解决方案涉及为每个锚点添加唯一 ID,然后从定位元素引用该 ID。这会变得很麻烦,而 CSS 通过 anchor-scope 提供了一个更简单的解决方案。

anchor-scope 属性用于设置仅在元素及其后代中匹配哪些锚点名称。它接受一个或多个锚点名称的列表或关键字 all,以限制所有已定义的锚点名称的范围。

最好将 anchor-scope 添加到定位元素和锚定元素的共同祖先中,该祖先不包含其他同名的锚定元素。通常,此属性位于可重用组件的根目录中。

以下示例展示了 anchor-scope 应用于具有相同 anchor-name 的重复元素时所产生的差异。在此示例中,所有 <img> 元素和图片横幅都引用了 --image 锚点名称。当 anchor-scope 应用于 <li> 元素时,position-anchor: --image 将仅匹配与横幅位于同一 <li> 元素内的 <img> 元素,否则将匹配最后呈现的 <img>

定位

现在,您已将元素与锚点相关联,接下来可以定位元素了。锚点定位提供了两种定位方法 - position-areaanchor() 函数。

position-area

借助 position-area 属性,您可以通过指定一个或两个关键字来将元素定位在锚点周围。这涵盖了许多常见的使用场景,通常是一个不错的起点。

position-area 的工作方式

position-area 的工作原理是,在由锚点边缘和定位元素的原始包含块生成的区域中,为定位元素创建一个新的包含块。

虽然 position-area 有很多可用的关键字,但我们可以将它们分为几个类别,以便更好地理解。Anchor-tool.com 是一个用于探索语法的出色工具。

实体关键字

您可以使用实体关键字 topleftbottomrightcenter。例如,position-area: top right 会将定位元素放置在锚点的上方和右侧。这些关键字还具有对应的实物轴,即 y-startx-starty-endx-end

逻辑关键字

您还可以使用逻辑关键字 block-startblock-endinline-startinline-end。例如,在英语等语言中,position-area: block-end inline-start 会将定位元素放置在锚点的下方和左侧;在文档的书写模式中,position-area: block-end inline-start 会将定位元素放置在块轴上的锚点之后和内联轴上的锚点之前。center 还可以与逻辑关键字搭配使用。

如果您指定的是逻辑关键字,也可以省略轴,但要先指定块轴,再指定内联轴。position-area: start endposition-area: block-start inline-end 相同,甚至与 position-area: inline-end block-start 相同。

跨多个网格区域

到目前为止,您可能已经注意到,这些选项仅允许您将定位元素放置在单个网格空间内。向物理或逻辑属性添加 span 前缀会添加相邻的中心网格空间。position-area: span-top right 将位于锚点的右侧,并从锚点的底部到定位元素的原始包含块的顶部。

下拉菜单的常见位置区域为 position-area: block-end span-inline-end

span-all 关键字跨越 3 行或 3 列。

单个关键字

如果您只设置了一个关键字,系统会自动设置另一个轴。这在很大程度上符合您的预期,但了解其工作原理可能会很有用。

如果提供的关键字明确指定了轴,则另一个轴将计算为 span-all。这意味着 position-area: bottom 等效于 position-area: bottom span-all,定位的元素将位于锚点下方,并占据包含块的整个可用宽度。

另一方面,如果关键字未明确指明轴,则会重复。position-area: start 等效于 start start,并且在从左到右书写的语言中位于锚点的左上角。

anchor() 函数

对于更高级的用例,position-area 可能无法满足您的需求。借助 anchor() 函数,您可以根据另一个元素的位置设置各个边衬区属性。这会解析为 CSS 长度,这意味着您可以在计算中使用它,也可以将其与其他 CSS 函数搭配使用。此外,您还可以将不同侧边系到不同的锚点。

anchor() 函数接受锚点名称和锚点侧。如果您的元素具有默认锚点(通过 position-anchor 设置或隐式设置,例如使用 popover),则可以省略锚点名称。

.positionedElement {
  block-start: anchor(--my-anchor start);
  /*  OR  */
  position-anchor: --my-anchor;
  block-start: anchor(start);
}

后备值

如果找不到 anchor() 函数的锚点,整个声明将无效。如果锚点在定位元素之后呈现,或者没有具有匹配 anchor-name 的元素,就可能会发生这种情况。为处理此问题,您可以设置回退长度或百分比。

.positionedElement {
   block-start: anchor(--my-anchor, 100px)
}

在上述示例中,定位元素的左侧值锚定到 --focused-anchor,但该 anchor-name 仅在悬停或聚焦于第一个按钮时存在。由于 anchor() 函数会解析为长度,因此您可以将另一个锚点用作后备。如果我们不提供回退,定位元素将不会被定位。

锚定边关键字

锚定边值用于选择要与哪个锚定边对齐。与 position-area 类似,锚点侧值支持多种不同的语法。

类型 说明
真机 topleftbottomright

实体关键字与锚点的特定一侧对应,但只能用于与您要设置的位置元素边衬区相同的轴上。

例如,top: anchor(bottom) 会将元素顶部定位在锚点底部,但 left: anchor(top) 不可行。

侧边 insideoutside

inside 关键字对应于与内边距属性相同的边,而 outside 关键字对应于同一轴上的另一侧。

例如,inset-block-start: anchor(inside) 表示锚点的 block-start 侧,inset-inline-end: (outside) 表示锚点的 inline-start 侧。

逻辑 startendself-startself-end

逻辑关键字是指锚点的侧边,具体取决于定位元素的书写模式(使用 self-startself-end)或定位元素的包含块的书写模式(使用 startend)。

百分比 0% - 100%

百分比值用于将定位元素放置在指定轴上从锚点开头到结尾的轴上。0% 位于锚点的 start 侧,100% 位于锚点的末端。center 等效于 50%。如果您在末端边衬区(例如 bottom)上使用百分比,则此百分比不会反转,0% 仍是锚点的 start 侧。

此示例展示了百分比值如何始终在指定轴上从开头到结尾变化:

使用 anchor()

由于 anchor() 是长度,因此非常灵活。您可以使用 max()calc() 等 CSS 函数来操纵值。

一个限制是,您只能对内边距属性使用 anchor() 函数。

上例在打开的详情面板后面添加了一个背景,当打开其他面板时,该背景会平滑地动画显示,并会拉伸以包含悬停的详情面板。为此,它使用 min() 来选择两个锚点之间的较小长度。

#indicator{
/*  Use the smaller of the 2 values:  */
  inset-block-start: min(
/*   1. The start side of the default anchor, which is the open `<details>` element  */
    anchor(start),
/*   2. The start side of the hovered `<details>` element.    */
    anchor(--hovered start,
/*     If no `<details>` element is hovered, this falls back to infinity px, so that the other value is smaller, and therefore used.   */
       var(calc(1px * infinity)))
  );
}

该示例还使用 calc() 在打开的面板周围添加内边距。

使用锚点的大小

您还可以使用 anchor-size() 函数将锚元素的尺寸用于定位元素的尺寸、位置或边距。

anchor-size() 接受锚点名称,或使用默认锚点。默认情况下,它将使用所用轴上锚点的大小,因此 width: anchor-size() 将返回锚点的宽度。您还可以使用其他轴,方法是使用物理关键字 widthheight 或逻辑关键字 blockinlineself-blockself-inline 指定所需的长度。

处理溢出

您创建了一个下拉菜单组件,并使用锚点定位将下拉菜单放置在您希望的位置。但随后,您将菜单移到了屏幕的另一侧,或者将其用于用户菜单,而用户的名字非常长。突然,下拉菜单消失在屏幕之外。接下来该怎么做呢?

CSS 锚点定位具有内置系统,可让您在定位元素最终位于其包含块之外时快速构建一组可靠的后备方案。

后备选项

position-try-fallbacks 规则接受回退选项列表。当默认位置溢出时,系统会按顺序尝试每个选项,直到找到不溢出的位置。

您可以使用任何 position-area 值作为后备选项。在此示例中,在从左到右的书写模式(例如英语)下,定位元素将尝试定位在锚点的底部,跨越中间列和右侧列。如果溢出,则会尝试定位在锚点的底部,跨越左列和中间列。如果该值也溢出,即使溢出,位置也会恢复为默认位置。

.positioned-element {
  position-area: block-end span-inline-end;
  position-try-fallbacks: block-end span-inline-start;
}

还有几个 flip- 关键字可处理常见的回退情况。flip-blockflip-inline 尝试翻转块轴和内联轴上的元素。它们还可以与 flip-block flip-inline 结合使用,以沿两个轴翻转。值 flip-start 会使定位元素沿从锚点起始角到结束角的对角线翻转。

您还可以使用 @position-try 创建自定义回退选项,这样您就可以设置边距、对齐方式,甚至更改锚点。

@position-try --menu-below {
  position-area: bottom span-right;
  margin-top: 1em;
}

#positioned-element {
  position-try: --menu-below;
}

可以将 flip-blockflip-inline 添加到 @position-try 后备选项中以创建变体。

#positioned-element {
  position-try: --menu-below, flip-inline --menu-below;
}

在上述示例中,浏览器会按照以下步骤操作,并在找到不会溢出的解决方案后立即停止。

  1. 元素放置在锚点的右下角,偏移量为 position-area: end
  2. 如果溢出,则使用名为 --bottom-span-right 的自定义回退选项放置元素,该选项使用 position-area: bottom span-right 放置元素,并在下方添加额外的边距。
  3. 如果溢出,则使用 flip-inline --bottom-span-right 放置元素,该属性将自定义回退选项与 flip-inline(本质上是 position-area: bottom span-left)相结合。
  4. 如果溢出,系统会使用 --use-alternate 自定义回退选项放置元素,该选项会将其放置在完全不同的锚点下方。
  5. 如果溢出,即使已知会溢出,元素也会恢复到其原始位置(使用 position-area: end)。

回退顺序

默认情况下,当初始位置溢出时,浏览器会尝试 position-try-fallbacks 中的每个选项,直到找到不溢出的位置。您可以使用 position-try-order 替换此行为,以测试每个回退选项,并使用在指定轴上具有最大空间的选项。

您可以使用逻辑关键字 most-block-sizemost-inline-size,也可以使用物理关键字 most-heightmost-width 来指定轴。

position-try-orderposition-try-fallbacks 可与 position-try 简写形式结合使用,且顺序在前。

滚动

用户在滚动时,希望网页能够流畅地移动。为此,浏览器对滚动时锚点定位的使用方式进行了限制。

虽然您可以将定位元素与不同滚动容器中的锚点相关联,但该元素只会响应其中一个锚点的滚动而移动。这将是默认锚点,即弹出式窗口的隐式锚点或 position-anchor 的值。

您会注意到,即使锚点滚动到视图之外,定位的元素仍保持可见。如需在锚点隐藏时隐藏定位元素,请设置 position-visibility: anchors-visible。这不仅适用于锚点过度滚动的情况,也适用于以其他方式隐藏锚点的情况,例如使用 visibility: hidden

检验您的掌握情况

anchor() 中“side”的有效值有哪些?

inside
正确!
25%
正确!
25px
错误。虽然可以使用 25px 等长度作为回退值,但只能使用百分比作为边。
block-start
错误
start
正确!

哪些是 position-area 的有效值?

top
正确!
block-end inline-end
正确!
block-start block-end
错误。您只能在每个轴上定义单个列或行。

哪些属性支持 anchor() 函数?

top
正确!
margin-left
错误。
inset-block-start
正确!
transform
错误。

如果存在多个具有相同 anchor-name 的锚点,会发生什么情况?

定位的元素会复制并绑定到每个匹配项。
错误。
定位的元素会锚定到文档中的第一个元素。
错误。
定位元素会锚定到文档中的最后一个元素。
正确!
定位的元素会绑定到最近的锚点。
错误。