css 锚点定位与 popover

在锚点定位推出前, 定位通常包含相对定位,绝对定位,和粘贴定位,以及基于视口的定位.

锚点定位基于以上定位基础, 锚点定位的不同点在于他是基于页面的锚定元素的, 并利用锚定元素的物理位置(或逻辑位置). 所以锚点定位的基础就是: 将需要定位的元素的定位基本点与某个元素相互绑定, 并基于绑定点进行位置(定位)设置.

根据此基本关系, 可以分为锚点元素和与其锚定的定位元素. 简称锚元素定位元素(针对锚点定位语境). 在需要定位的元素与其锚点元素绑定关系后, 定位元素 可以获得锚点元素的大小和位置, 从而根据此信息设置自身的位置和大小. 锚点定位还提供了定位元素的备选定位, 主要用于定位元素超出视口时的回退(备选)声明, 用于指导浏览器渲染到建议的位置上, 或将定位元素完全隐藏.

本文解释锚定定位基本概念, 以及如何使用锚点定位模块的关联,定位和尺寸设置功能. 文中提供了参考链接, 链接包含更多示例和语法细节, 这些内容对下文介绍的每个概念进行了详细说明. 有关回退(备选)位置和定位元素的隐藏的更多信息, 其属于更加深入的内容, 可以参考回退选项和溢出隐藏指南

基本概念

将一个元素与另一个元素连接或绑定是非常常见的。例如:

  • 表单控件旁边显示的错误信息。
  • 在用户界面元素旁边弹出的工具提示或信息框,用于提供有关该元素的更多信息。
  • 可以访问的设置或选项对话框,用于快速配置用户界面元素。
  • 出现在相关导航栏或按钮旁边的下拉菜单或弹出菜单。

现代界面经常需要将某些内容(通常是可重用且动态生成的)定位到锚点元素之后。基本界面元素(即锚点元素)在用户界面中的位置始终不变,并且依赖基本界面元素的信息元素(即定位元素)始终可以紧邻其之前或之后,那么创建此类用例就相当简单。然而,实际情况很少如此简单。

当锚元素移动或发生其他配置变化(例如滚动、更改视口大小、拖放等)时,定位元素相对于其锚元素的位置需要保持并进行调整。例如,如果表单字段等元素靠近视口边缘,其工具提示可能会超出屏幕范围。通常,您需要将提示工具绑定到其表单控件,并确保只要表单字段可见,提示工具就始终完全显示在屏幕上,并在需要时自动移动提示工具。您可能已经注意到,在台式机或笔记本电脑上右键单击(+单击)上下文菜单时,操作系统默认就是这种行为。

过去,将一个元素与另一个元素关联,并根据锚点的位置动态改变已定位元素的位置和大小,都需要使用 JavaScript,这增加了复杂性并导致性能问题。而且,这种方法也无法保证在所有情况下都有效。CSS锚点定位模块中定义的功能使得我们可以用 CSS(和 HTML)而非 JavaScript 来高效且声明式地实现此类用例。

锚元素和定位元素的关联

要将一个元素与一个锚点关联起来,首先需要声明哪个元素是锚点,然后指定要与该锚点关联的已定位元素。这样就在两者之间创建了一个锚点引用。这种关联可以通过 CSS 显式创建,也可以隐式创建。

css显式关联

要声明一个锚点元素, 通过css属性anchor-name设置锚点的名称, 锚点必须是双短横线开头的值.

以下实例将 class=anchor 锚点元素的锚点名称设置称为 --my-anchor, 同时将该元素的宽度设置为了 fit-content, 以便于更好展示锚定效果

.anchor {
  anchor-name: --my-anchor;
  width: fit-content;
}

由于使用锚点定位的能力需要涉及到两个元素, 因此还需要设置一个定位元素.

定位元素首先需要将其 position 属性设置为 绝对定位或视口定位.

然后通过设置 position-anchor, 将该属性值与与上文设置的锚点名称建立关联.

.infobox {
  position: fixed;
  position-anchor: --my-anchor;
}

html元素关系如下:

<div class="anchor">⚓︎</div>

<div class="infobox">
  <p>This is an information box.</p>
</div>

渲染效果如下

锚点和信息框现在已经关联,但目前您只能相信, 它们之间还没有真正绑定——如果您定位锚点并通过绝对定位到页面上的其他位置, 锚点元素能正常工作,而信息框则保持在原位。当我们讲解如何根据锚点位置设置定位元素时,您就会看到它们之间实际的绑定效果。

隐性的关联

在某些情况下,由于两个元素之间关系的语义性质,会在它们之间建立隐式锚点引用:

  • 使用Popover API将弹出框与控件关联时,两者之间会建立隐式锚点引用。可能有以下两种情形:

    • 使用popovertarget和id属性或commandfor和id属性,以声明方式将popover与控件相关联。
    • 使用以编程方式将popover操作与控件相关联 ,(如showPopover() 函数的 source 选项) 。
  • 一个<select>元素及其下拉选择器通过appearance属性的 base-select 值, 被选入可自定义的select元素功能。在这种情况下,两者之间会创建一个隐式的popover-invoker关系,这也意味着它们会有一个隐式的锚点引用。

NOTE

上述方法将锚点与元素关联起来,但它们尚未绑定在一起。要将它们绑定在一起,需要将定位元素相对于其锚点进行定位,这可以通过 CSS 实现。

移除关联

如果要移除之前在锚元素和定位元素之间建立的显式锚点关联,可以执行以下操作之一:

  1. 将锚点的anchor-name属性值设置为none,或者设置为不同的,如果要将不同的元素锚定到它上面。
  2. position-anchor将定位元素的属性设置为none,或者设置为当前文档中不存在的锚点名称,例如—not-an-anchor-name。

对于隐式锚点关联,您需要使用第二种方法——第一种方法无效。这是因为这种关联是由内部控制的,您无法anchor-name通过 CSS 移除它。

例如,要阻止可自定义元素本身,可以使用以下规则:

::picker(select) {
  position-anchor: none;
}

锚点作用范围

当多个锚元素被赋予相同的 anchor-name 值,且某个定位元素将该名称作为其 position-anchor 属性的值时,该定位元素将与源顺序中具有该锚名称值的最后一个锚元素相关联。

比如,如果文档包含多个重复组件,每个组件都有一个定位元素并通过锚点进行绑定,那么除非每个组件使用不同的锚点名称,否则所有定位元素都会锚定到页面上的最后一个锚点。这很可能不是我们想要的结果。

anchor-scope 属性可以通过限制值的可见性(或“作用域”)来解决这个问题,使被其值指定的 anchor-name 仅限于自身子元素用于锚定。这样声明过 anchor-scope 的锚点子元素, 就无法被其外部的定位元素作为锚点使用.

  • anchor-scope: none是默认值;它指定不设置锚点范围。
  • anchor-scope: all设置作用域,使得子树中设置的任何设置过 anchor-name 值的锚点元素, 只能被相同子树的定位元素绑定。也就是所有的锚点不能被当前容器以外的定位元素用于锚定定位.
  • anchor-scope: —my-anchor, —my-anchor2; 该容器内名为 —my-anchor, —my-anchor2 只能在该容器的子树的定位元素锚定, 外部无法锚定 —my-anchor, —my-anchor2的锚元素.

例如,假设容器<div>内有多个锚点和锚定位元素 <section>

<section class="scoped">
  <div class="anchor">⚓︎</div>
  <div class="positioned">Positioned 1</div>
</section>

<section class="scoped">
  <div class="anchor">⚓︎</div>
  <div class="positioned">Positioned 2</div>
</section>

<section class="scoped">
  <div class="anchor">⚓︎</div>
  <div class="positioned">Positioned 3</div>
</section>

通过为每个锚点<div>赋予一个锚点名称“—my-anchor”,将其转换为锚点元素。然后,通过为每个已定位的<div>赋予绝对定位、位置锚点值为“—my-anchor”以及 position-area 值为“right”,使其相对于具有“—my-anchor”锚点名称的元素进行定位。最后,我们使用anchor-scope: —my-anchor来设置每个<section>容器的锚点范围:

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

.positioned {
  position: absolute;
  position-anchor: --my-anchor;
  position-area: right;
}

.scoped {
  anchor-scope: --my-anchor;
}

如下渲染结果为定位效果

<section>内每个定位元素都是相对于其自身内部的锚点进行定位的 。这是因为每个<section>元素都设置了 anchor-scope = --my-anchor; 因此每一个容器内的定位元素都无法使用其他容器内的定位元素, 只能使用其作用域内的 名为 —my-anchor 锚点.

如果我们不对anchor-scope: —my-anchor容器进行设置,所有定位元素都将相对于页面上的最后一个锚点进行定位。

将元素定位到锚点

如前所述,将定位元素与锚点相关联本身并没有多大用处。我们的目标是将定位元素相对于其关联的锚点元素进行放置。这可以通过在inset属性上设置CSS anchor()函数值、指定位置区域或使用anchor-center放置值将定位元素居中来实现。

NOTE

CSS 锚点定位还提供了指定备用位置的机制,以防定位元素的默认位置导致其超出视口范围。有关详细信息,请参阅“备用选项和条件隐藏”指南。

NOTE

锚元素必须是可见的 DOM 节点,关联和定位才能正常工作。如果它被隐藏(例如通过设置 display: none),则定位元素将相对于其最近的已定位祖先元素进行定位。将在 使用条件隐藏 部分讨论如何在锚点消失时隐藏锚定位元素。

通过 anchor() 函数定位元素

传统的绝对定位元素和固定定位元素是通过设置inset值的lengthpercentage 值来明确定位的。使用position: absolute时,此inset位置值是相对于最近已定位祖先元素边缘的绝对距离。使用position: fixed时,此inset位置值是相对于视口的绝对距离。

CSS定位锚改变了这一范式,使定位锚元素能够相对于其关联锚的边缘定位。该css 模块定义了 anchor()函数,该函数是每个内嵌属性的有效值。使用时,该函数通过定义锚元素、定位元素相对于锚元素的哪一侧以及与该侧的距离,将内嵌位置值设置为相对于锚元素的绝对距离。

功能组件看起来是这样的:

anchor(<anchor-name> <anchor-side>, <fallback>)

<anchor-name>

anchor-name您希望元素侧边相对于哪个锚元素定位。这是一个dashed-ident值。如果省略,则使用元素的position-anchor属性值。这是其属性中引用的锚点,或通过非标准 anchor HTML 属性与元素关联的锚点。

<anchor-side>

指定相对于锚点一侧或多侧的位置。有效值包括锚点的中心、锚点的物理侧边(top、right等)或逻辑侧边(start、self-end等),或者在inset属性(该属性上设置了anchor()函数)的轴的起始(0%)和结束(100%)之间的<percentage> 值。如果使用的值与设置了anchor()函数的inset属性不兼容,则使用回退值。

<fallback>

一个<length-percentage>值,用于在以下情况下作为回退值:元素不是绝对定位或固定定位;所使用的<anchor-side>值与设置 anchor() 函数的inset属性不兼容;或者锚元素不存在。

anchor() 函数的返回值是基于锚点位置计算得出的长度值。如果直接在定位到锚点的元素的 inset 属性上设置长度或百分比,则其定位方式就像未绑定到锚点元素一样。这与当 <anchor-side> 值与其设置的 inset 属性不兼容并使用回退方案时所观察到的行为相同。以下两种声明是等效的:

bottom: anchor(right, 50px); /* 不推荐:该写法一定会回退到 fallback 值 */
bottom: 50px;

两者都会将定位元素放置50px在该元素最近的定位祖先元素(如果有)或初始包含块的底部上方。

最常用的anchor()参数指的是默认锚点的边长。您通常还需要添加一个间距margin来创建锚点边缘和定位元素之间的间距,或者anchor()在calc()函数中使用间距来添加间距。

例如,这条规则将定位元素的左边缘与锚定元素的右边缘对齐,然后添加一些空间margin-left,使边缘之间留出一些空隙:

.positionedElement {
  left: anchor(right);
  margin-left: 10px;
}

函数的返回值anchor()是一个长度值。这意味着你可以在函数内部使用它calc()。这条规则将定位元素的逻辑块结束边缘10px与锚元素的逻辑块开始边缘对齐,并使用该函数添加间距,calc()因此我们不需要添加边距:

.positionedElement {
  inset-block-end: calc(anchor(start) + 10px);
}

achor用例

让我们来看一个anchor()实际例子。我们使用了与之前示例相同的 HTML 代码,但在代码的上下添加了一些填充文本,使内容溢出容器并滚动显示。我们还将锚元素设置anchor-name为与之前示例相同的样式:

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

信息框通过锚点名称与锚点相关联,并被赋予了固定定位。通过包含inset-block-start和inset-inline-start属性(在水平从左到右的书写模式下,这两个属性等同于top和left),我们已将其与锚点绑定。我们为信息框添加了边距,以便在已定位的元素与其锚点之间增加空间:

.infobox {
  position-anchor: --my-anchor;
  position: fixed;
  inset-block-start: anchor(end);
  inset-inline-start: anchor(self-end);
  margin: 5px 0 0 5px;
}

让我们更详细地看一下 inset 属性的定位声明:

  • inset-block-start: anchor(end):这将定位元素的块起始边缘设置为锚点的块结束边缘,该边缘是使用该anchor(end)函数计算的。
  • inset-inline-start: anchor(self-end):这将定位元素的行内起始边缘设置为锚点的行内结束边缘,该边缘是使用该anchor(self-end)函数计算出来的

定位元素位于锚元素的5px下方和5px右侧。如果上下滚动文档,定位元素相对于锚元素的位置保持不变——它固定在锚元素上,而不是固定在视口上。

通过 position-areas属性定位元素

position-area属性为相对于锚点定位元素提供了anchor()函数的替代方案。position-area属性基于3x3网格平铺的概念,其中锚点元素是中心平铺。position-area属性可用于将锚点定位的元素定位在九个平铺中的任何一个,或者使其跨越两个或三个平铺。

img

网格图块被分解成行和列:

  • 这三行分别用物理值top(顶部)、center(中心)和bottom(底部)来表示。它们也有逻辑等效值,如start(开始)、center(中心)和end(结束),以及坐标等效值,如y-start(y轴起点)、center(中心)和y-end(y轴终点)。
  • 这三列分别用物理值left、center、right 来表示。它们也有逻辑等效值,如start、center和end,以及坐标等效值,如x-start、center和x-end。

中心图块的尺寸由锚元素的容器块定义,而中心图块与网格外边缘之间的距离由定位元素的容器块定义

position-area属性值由一个或两个值组成,这些值基于上面描述的行值和列值,并提供跨度选项来定义元素应位于网格中的区域。

将定位元素与锚点中心对齐

虽然可以使用position-area的中心值来使定位元素居中,但inset属性与anchor()函数结合使用可以更好地控制精确位置。当使用inset属性(而非position-area)进行绑定时,CSS定位提供了一种相对于锚点居中定位元素的方法。

justify-self、align-self、justify-items和align-items属性(以及它们的place-items和place-self简写形式)的存在,使开发人员能够轻松地在各种布局系统中,沿行内或块方向对齐元素,例如在flex子元素的情况下,沿主轴或交叉轴对齐。CSS锚点定位为这些属性提供了额外的值,即anchor-center,它将定位元素与其默认锚点的中心对齐。

本例使用了与上一例相同的HTML和基础CSS。信息框被赋予了固定定位,并固定在锚点的底部边缘。然后使用justify-self: anchor-center来确保它在锚点的中心水平居中

.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  top: calc(anchor(bottom) + 5px);
  justify-self: anchor-center;
}

This centers the anchor-positioned element at the bottom of its anchor:

定位元素获得锚元素的尺寸

除了可以根据锚点的位置来定位元素之外,还可以使用anchor-size()尺寸属性值中的函数,根据锚点的大小来调整元素的大小。

可接受anchor-size()值的属性包括:

  • width
  • height
  • min-width
  • min-height
  • max-width
  • max-height
  • block-size
  • inline-size
  • min-block-size
  • min-inline-size
  • max-block-size
  • max-inline-size

anchor-size()函数会解析为<length>值。它们的语法如下所示:

anchor-size(<anchor-name> <anchor-size>, <length-percentage>)

<anchor-name>

<dashed-ident> 名称被设置为要相对于其调整大小的锚元素的 anchor-name 属性的值。如果省略,则使用元素的默认锚点,即 position-anchor 属性中引用的锚点。

<length-percentage>

指定定位元素将相对于其调整大小的锚点元素的尺寸。这可以使用物理值(width 或 height)或逻辑值(inline, block, self-inline, 和 self-block)来表示。

最常用的anchor-size()函数会直接引用默认锚点的尺寸。你也可以在calc()函数内部使用这些函数,来修改定位元素的尺寸。

例如,这条规则将定位元素的宽度设置为与默认锚元素的宽度相同:

.elem {
  width: anchor-size(width);
}

这条规则将定位元素的行内尺寸设置为锚元素行内尺寸的 4 倍,乘法运算在一个calc()函数内部完成:

.elem {
  inline-size: calc(anchor-size(self-inline) * 4);
}

处理定位元素溢出

在使用 CSS 锚点定位 时,一个重要的考虑因素是确保锚点定位的元素始终出现在用户方便交互的位置,无论锚点位于何处。例如,当滚动页面时,锚点及其关联的定位元素会向视口边缘移动。当定位元素开始超出视口时,您需要调整其位置,使其重新出现在屏幕上,例如将其放置在锚点的另一侧。

或者,在某些情况下,最好直接隐藏溢出的定位元素——例如,如果它们的锚点在屏幕外,它们的内容可能就没有意义了。

本指南解释了如何使用 CSS 锚点定位机制来解决这些问题——position-try 回退选项和条件隐藏。position-try 回退选项为浏览器提供了备用位置,以便在定位元素开始溢出屏幕时尝试将其放置在这些位置,从而确保它们始终显示在屏幕上。条件隐藏允许指定隐藏锚点或定位元素的条件。

主要 position-anchor 的附属属性

  1. position-try-fallbacks
  2. at规则 @position-try
  3. position-visibility
  4. position-try-order

参考链接

  1. 锚点定位指南 - MDN
  2. 溢出回退处理 - MDN