溢出时的回退选项和条件性隐藏

在使用 CSS 锚点定位时,一个重要的考虑因素是确保锚点定位的元素始终出现在方便用户交互的位置(如果可能的话),而不管锚点位于何处。例如,当你滚动页面时,锚点及其关联的定位元素会向视口边缘移动。当定位元素开始溢出视口时,你会希望改变它的位置,使其重新回到屏幕上,例如移动到锚点的另一侧。

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

本指南介绍了如何使用 CSS 锚点定位机制来处理这些问题——位置尝试回退选项position-try fallback options)和条件性隐藏conditional hiding)。位置尝试回退选项为浏览器提供了备选位置,以便在定位元素开始溢出时尝试放置,从而将它们保持在屏幕上。条件性隐藏允许指定在何种条件下隐藏锚点或定位元素。

备注: 关于 CSS 锚点定位的基础知识,请参阅使用 CSS 锚点定位

功能摘要

如果一个工具提示框固定在某个 UI 元素的右上角,当用户滚动内容,使得该 UI 功能移动到视口的右上角时,该 UI 功能的工具提示框就会滚动到屏幕外。CSS 锚点定位解决了这类问题。该模块的 position-try-fallbacks 属性指定了一个或多个备选的位置尝试回退选项,供浏览器尝试,以防止定位元素溢出。

位置尝试回退选项可以通过以下方式指定:

此外,position-try-order 属性允许你指定各种选项,使得浏览器会优先选择一个可用的位置尝试选项,而不是元素的初始定位。例如,你可能希望最初将元素显示在具有更多可用高度或宽度的空间中。

简写属性 position-try 可用于在单个声明中指定 position-try-orderposition-try-fallbacks 的值。

在某些情况下,如果锚点在屏幕外,锚点定位的内容就没有意义了,反之亦然。例如,你可能有一个包含测验问题的锚点,而答案包含在相关的定位元素中,你希望它们要么一起显示,要么都不显示。这可以通过条件性隐藏来实现,它由 position-visibility 属性管理。该属性接受多个值,用于定义在何种条件下隐藏溢出元素。

预定义的回退选项

position-try-fallbacks 属性的预定义回退选项值(在规范中定义为 <try-tactic>)会在锚点定位的元素可能溢出时,沿一个或两个轴线“翻转”其位置。

元素可以设置为沿块轴(flip-block)、内联轴(flip-inline)翻转,或者沿从锚点一角穿过其中心到对角的假想线对角翻转(flip-start)。前两个值会翻转元素,将其位置镜像到相对的一侧,而 flip-start 则会镜像到相邻的一侧。例如,如果一个定位在锚点上方 10px 的元素开始在锚点顶部溢出,flip-block 值会将其翻转到锚点下方 10px 处。

在此示例中,我们包含两个 <div> 元素。第一个将是我们的锚点元素,第二个将相对于锚点进行定位。

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

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

我们将 <body> 元素的样式设置为比视口更大,以便我们可以在视口中水平和垂直地滚动锚点和定位元素。

css
body {
  width: 1500px;
  height: 500px;
}

为了便于说明,我们对锚点进行绝对定位,使其出现在初始 <body> 渲染的中心附近。

锚点定位的元素被赋予了固定定位,并使用 position-area 绑定到锚点的左上角。它被设置为 position-try-fallbacks: flip-block, flip-inline;,以便在锚点靠近视口边缘时,为移动定位元素以防止其溢出提供一些回退选项。

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  position-area: top left;
  position-try-fallbacks: flip-block, flip-inline;
}

备注: 当指定多个位置尝试回退选项时,它们用逗号分隔,并按指定的顺序进行尝试。

尝试滚动演示,让锚点开始靠近边缘。

  • 将锚点移动到视口顶部。定位元素会翻转到锚点的左下方以避免溢出。
  • 将锚点移动到视口左侧。定位元素会翻转到锚点的右上方以避免溢出。

如果将锚点移向视口的左上角,你会发现一个问题——当定位元素开始在块和内联方向上溢出时,它会翻转回其默认的左上角位置,并在两个方向上都溢出,这不是我们想要的。

发生这种情况是因为我们只给了浏览器 flip-block flip-inline 的位置选项。我们没有给它同时尝试两者的选项。浏览器会尝试这些回退选项,寻找一个能使定位元素完全呈现在视口或包含块内部的选项。如果找不到,它会以其最初定义的渲染位置渲染定位元素,不应用任何位置回退选项。

下一节将演示如何解决这个问题。

将多个值组合成一个选项

可以将多个预定义的尝试回退选项自定义尝试选项名称放入一个由空格分隔的尝试回退选项值中,该值是逗号分隔的 position-try-fallbacks 列表的一部分。当尝试应用这些回退选项时,浏览器会将各个效果组合成一个单一的组合回退选项。

让我们使用一个组合的尝试回退选项来解决我们在上一个演示中发现的问题。这个演示的 HTML 和 CSS 与之前相同,除了信息框的定位样式。在这种情况下,它被赋予了第三个位置尝试回退选项:flip-block flip-inline

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  position-area: top left;
  position-try-fallbacks:
    flip-block,
    flip-inline,
    flip-block flip-inline;
}

这意味着浏览器会首先尝试 flip-block,然后尝试 flip-inline 来避免溢出。如果这两个回退选项都失败了,它会尝试将两者结合起来,同时在块内联方向上翻转元素的位置。现在,当你将锚点向视口的顶部左侧边缘滚动时,定位元素会翻转到右下角。

使用 position-area 尝试回退选项

预定义的 <try-tactic> 尝试回退选项很有用,但也有局限性,因为它们只允许定位元素的位置沿轴线翻转。如果你有一个锚点定位的元素位于其锚点的左上方,并希望在它开始溢出时将其位置更改为锚点的正下方,该怎么办呢?

为了实现这一点,你可以使用 position-area 值作为位置尝试回退选项,将其包含在 position-try-fallbacks 列表中。这会自动创建一个基于该位置区域的尝试回退选项。实际上,这是创建只包含该 position-area 属性值的自定义位置选项的快捷方式。

以下示例展示了 position-area 位置尝试回退选项的使用。我们使用相同的 HTML 和 CSS,除了信息框的定位。在这种情况下,我们的位置尝试回退选项是 position-area 值——toptop-rightrightbottom-rightbottombottom-leftleft。无论锚点接近哪个视口边缘,定位元素都会被合理地定位。这种详细的方法比预定义值的方法更精细、更灵活。

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  position-area: top left;
  position-try-fallbacks:
    top, top right, right,
    bottom right, bottom,
    bottom left, left;
}

备注: 你不能将 position-area 尝试回退选项添加到位置尝试回退列表中的由空格分隔的组合位置选项中。

滚动页面,看看当锚点靠近视口边缘时这些位置尝试回退选项的效果。

自定义回退选项

要使用上述机制无法实现的自定义位置回退选项,你可以使用 @position-try @规则创建自己的选项。语法如下:

@position-try --try-fallback-name {
  descriptor-list
}

--try-fallback-name 是开发者为位置尝试回退选项定义的名称。然后,这个名称可以在 position-try-fallbacks 属性值的逗号分隔的尝试回退选项列表中指定。如果多个 @position-try 规则具有相同的名称,则文档顺序中最后一个会覆盖其他的。避免为你的尝试回退选项你的锚点或自定义属性名称使用相同的名称;这不会使 @规则无效,但会让你的 CSS 非常难以理解。

descriptor-list 定义了该单个自定义尝试回退选项的属性值,包括定位元素应如何放置和调整大小,以及任何外边距。允许的属性描述符的有限列表包括:

如果你命名的自定义尝试回退选项被应用,@规则中包含的值会应用到定位元素上。如果这些属性之前已经在定位元素上设置过,那么这些属性值会被描述符的值覆盖。如果用户滚动,导致应用了不同的尝试回退选项或没有应用尝试回退选项,则先前应用的尝试回退选项的值将被取消设置。

在这个例子中,我们设置并使用了几个自定义的尝试回退选项。我们使用了与之前例子相同的基本 HTML 和 CSS 代码。

我们首先使用 @position-try 定义了四个自定义尝试回退选项:

css
@position-try --custom-left {
  position-area: left;
  width: 100px;
  margin-right: 10px;
}

@position-try --custom-bottom {
  position-area: bottom;
  margin-top: 10px;
}

@position-try --custom-right {
  position-area: right;
  width: 100px;
  margin-left: 10px;
}

@position-try --custom-bottom-right {
  position-area: bottom right;
  margin: 10px 0 0 10px;
}

一旦我们的自定义尝试回退选项被创建,我们就可以通过引用它们的名称将它们包含在位置列表中。

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  position-area: top;
  width: 200px;
  margin-bottom: 10px;
  position-try-fallbacks:
    --custom-left, --custom-bottom, --custom-right, --custom-bottom-right;
}

请注意,我们的默认位置由 position-area: top 定义。当信息框没有在任何方向上溢出页面时,信息框位于锚点上方,并且在 position-try-fallbacks 属性中设置的位置尝试回退选项会被忽略。另外,请注意信息框设置了固定的宽度和底部外边距。这些值会随着应用不同的位置尝试回退选项而改变。

如果信息框开始溢出,浏览器会尝试 position-try-fallbacks 属性中列出的位置选项。

  • 浏览器首先尝试 --custom-left 回退位置。这会将信息框移动到锚点的左侧,调整外边距以适应,并为信息框设置不同的宽度。
  • 接下来,浏览器尝试 --custom-bottom 位置。这会将信息框移动到锚点的底部,并设置适当的外边距。它不包含 width 描述符,所以信息框会恢复到由 width 属性设置的默认宽度 200px
  • 浏览器接着尝试 --custom-right 位置。这与 --custom-left 位置的工作方式非常相似,应用了相同的 width 描述符值,但 position-areamargin 的值是镜像的,以将信息框适当地放置在右侧。
  • 如果其他回退选项都未能阻止定位元素溢出,浏览器会尝试 --custom-bottom-right 位置作为最后的手段。这与其他回退选项的工作方式非常相似,但它将定位元素放置在锚点的右下方。

如果所有回退选项都未能阻止定位元素溢出,位置将恢复为初始的 position-area: top; 值。

备注: 当应用一个位置尝试回退选项时,它的值将覆盖在定位元素上设置的默认值。例如,定位元素上设置的默认 width200px,但是当应用 --custom-right 位置尝试回退选项时,它的宽度被设置为 100px

滚动页面,看看当锚点靠近视口边缘时这些位置尝试回退选项的效果。

使用 position-try-order

position-try-order 属性的关注点与位置尝试功能的其他部分略有不同,因为它在定位元素首次显示时使用位置尝试回退选项,而不是在它正在溢出时。

此属性允许你指定希望定位元素最初使用能为其包含块提供最大宽度或最大高度的位置尝试回退选项来显示。这可以通过设置 most-heightmost-widthmost-block-sizemost-inline-size 值来实现。你还可以使用 normal 值来移除任何先前设置的 position-try-order 值的效果。

如果没有可用的位置尝试回退选项能提供比分配给元素的初始定位更大的宽度/高度,position-try-order 将没有效果。

让我们来看一个演示,展示这个属性的效果。HTML 与之前的例子相同,只是我们添加了一个包含单选按钮的 <form>,允许你选择不同的 position-try-order 值来查看它们的效果。

我们包含一个自定义的尝试回退选项——--custom-bottom——它将元素定位在锚点下方并添加一个外边距。

css
@position-try --custom-bottom {
  top: anchor(bottom);
  bottom: unset;
  margin-top: 10px;
}

我们最初将信息框定位在锚点的顶部,然后给它我们的自定义尝试回退选项。

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  bottom: anchor(top);
  margin-bottom: 10px;
  justify-self: anchor-center;
  position-try-fallbacks: --custom-bottom;
}

最后,我们包含一些 JavaScript,它在单选按钮上设置一个 change 事件处理程序。当一个单选按钮被选中时,它的值会应用到信息框的 position-try-order 属性上。

js
const infobox = document.querySelector(".infobox");
const radios = document.querySelectorAll('[name="position-try-order"]');

for (const radio of radios) {
  radio.addEventListener("change", setTryOrder);
}

function setTryOrder(e) {
  const tryOrder = e.target.value;
  infobox.style.positionTryOrder = tryOrder;
}

尝试选择 most-height 排序选项。这会应用 --custom-bottom 位置尝试回退选项,将元素定位在锚点下方。这是因为锚点下方的空间比上方的空间要多。

条件性隐藏锚点定位元素

在某些情况下,你可能想要隐藏一个锚点定位的元素。例如,如果锚点元素因为离视口边缘太近而被裁剪,你可能只想完全隐藏其关联的元素。position-visibility 属性允许你指定在何种条件下隐藏定位元素。

默认情况下,定位元素是 always(总是)显示的。no-overflow 值会在定位元素开始溢出其包含元素或视口时强制隐藏该元素。

另一方面,anchors-visible 值会在其关联的锚点完全隐藏时强制隐藏定位元素,无论是通过溢出其包含元素(或视口)还是被其他元素覆盖。只要锚点的任何部分仍然可见,定位元素就会是可见的。

一个被强制隐藏的元素表现得就像它及其后代元素都被设置了 visibility 值为 hidden,无论它们实际的 visibility 值是什么。

让我们看看这个属性的实际效果。

这个例子使用了与之前例子相同的 HTML 和 CSS,信息框被绑定到锚点的下边缘。信息框被赋予 position-visibility: no-overflow;,以便在它向上滚动到开始溢出视口时完全隐藏它。

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  margin-bottom: 5px;
  position-area: top span-all;
  position-visibility: no-overflow;
}

向下滚动页面,注意一旦定位元素到达视口顶部,它就会被隐藏。

另见