位置感知提示

使用 CSS 锚点定位构建提示或弹出式窗口时,如果元素会溢出视口,浏览器可以自动将元素“翻转”到后备位置。发生这种情况时,您可能需要调整定位内容的样式,例如重新定位从定位内容指向锚点的箭头。

锚定容器查询解决了这个问题,它允许您查询元素的有效定位状态,并相应地应用样式。如需了解具有视口碰撞翻转(不重新设置方向箭头样式)的常规锚定下拉菜单和弹出式菜单定位,请参阅视口感知下拉菜单。

问题

假设有一个提示,默认显示在锚点上方。底部有一个“向下”箭头。如果用户滚动屏幕,并且提示翻转到锚点下方,则箭头现在指向错误的方向,并且位于提示的错误一侧。

解决方案:锚定容器查询

通过在定位元素上设置 container-type: anchored,您可以将其转换为查询容器,该容器会了解其自身的锚点定位状态。然后,您可以使用 @container anchored() 查询来更新其后代或伪元素。

1. 创建提示和触发器

使用 Popover API 创建提示。通过 popovertarget 调用弹出式窗口会自动与触发器按钮建立隐式锚点连接,因此无需显式 anchor-name 或 position-anchor。

<button popovertarget="tooltip" id="anchor" aria-describedby="tooltip">anchor</button>
<div id="tooltip" popover role="tooltip"></div>

重置弹出式窗口边衬区和外边距样式,以便与锚点定位搭配使用,但仅在支持锚点定位时适用。

@supports (anchor-name: --my-anchor) {
  [popover] {
    inset: auto;
    margin: unset;
  }
}

2. 设置容器

将 container-type: anchored 应用于要定位的元素。此元素还必须定义 position-try-fallbacks 才能启用翻转行为。

#tooltip {
  position: fixed;
  position-area: block-start;
  position-try-fallbacks: flip-block;

  /* Enable anchored container queries */
  container-type: anchored;
}

3. 根据后备样式设置样式

使用 @container anchored(fallback: <value>) 可在特定后备处于活动状态时应用样式。

与所有容器查询一样,@container 只能为容器的后代设置样式。创建箭头的常见策略是使用 ::before 和 ::after 伪元素,这些元素会被视为后代,可以直接设置样式。不过,为了设置提示本身的样式(如第 4 步所示),我们将向提示添加一个子元素,并在其 ::before 伪元素中创建箭头。

<div id="tooltip" popover role="tooltip">
  <div class="tooltip-content">Tooltip</div>
</div>
.tooltip-content::before {
  /* Default "down" arrow for the 'top' position */
  content: "▼";
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: 1rem;
}

/* Update to an "up" arrow when the 'flip-block' fallback (bottom) is active */
@container anchored(fallback: flip-block) {
  .tooltip-content::before {
    content: "▲";
    inset-block-start: 0;
    inset-block-end: auto;
  }
}

4. 设置容器本身的样式

如果您需要在容器翻转时更改容器本身的属性(例如 margin 或 background-color),则应使用内部封装容器元素。

  1. 将 container-type: anchored 应用于外部定位元素。
  2. 在 @container 块内定位内部元素。
@container anchored(fallback: flip-block) {
  .tooltip-content {
    border-radius: 0 0 .5rem .5rem;
    margin-block-start: 0.25rem;
  }
}

最佳做法

  • 首选逻辑后备:在 position-try-fallbacks 中使用 flip-block 和 flip-inline 等关键字,以便自动处理 RTL 和不同书写模式的更简单查询。
  • 使用伪元素创建箭头:提示箭头纯粹是装饰性的,非常适合使用 ::before 或 ::after,它们可以通过锚定容器查询进行样式设置,而无需额外的 DOM。

后备策略

根据应用的后备位置来定位箭头是一种渐进增强,没有其他方法可以对后备位置做出反应。如需在不支持锚点位置容器查询的浏览器中隐藏箭头,请使用 @supports (container-type: anchored) 测试 CSS 支持情况。

@supports (container-type: anchored) {
  .tooltip-content::before {
    content: "▼";
  }
}

Popover 的回退和浏览器支持

Popover API 主要是一种渐进增强技术,但其核心行为(顶层提升、轻触关闭和 popovertarget 调用)没有仅限 CSS 的等效项。旧版浏览器需要 polyfill,如果您不想提供 polyfill,则需要手动后备方案。

Polyfill:如需在旧版浏览器中支持 popover 属性,请有条件地加载 @oddbird/popover-polyfill。强制性要求:通过检查 HTMLElement.prototype 上的 popover 属性来检测功能,并且仅在缺少原生支持时加载 polyfill,请勿无条件加载。

使用打包工具或导入映射:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

如果没有打包器,请从 <script type="module"> 内的 CDN 导入:

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

样式注意事项:polyfill 无法定义真正的 :popover-open 伪类,因此它会改为应用 .\:popover-open 类。强制性:使用 :is() 或 :where() 将两者结合,否则缺少 :popover-open 的浏览器会舍弃整个规则:

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

或者,对于没有 polyfill 的旧版后备方案,请使用 position: fixed 并通过 getBoundingClientRect() 手动计算坐标,或依赖 inset: auto 的默认定位(如果该用例可以接受)。

不支持 Popover API 的浏览器也不支持锚点定位,因此提示会显示在屏幕中央。