基于内容的样式

Web 功能: :has() :not()

过去,若要根据组件的内容为其应用不同的布局,您需要在 HTML 模板语言中使用 JavaScript 或条件逻辑来注入修饰符类(例如 .card--has-image 或 .card--text-only)。

:has() 伪类可充当父级选择器,从而消除这种需求。它允许您根据特定后代元素的存在与否,有条件地设置容器元素的样式。

使用 :has(),您可以完全在 CSS 中根据组件的实际 DOM 内容轻松定义不同的布局变体。您还可以选择将其与 :not() 结合使用,以明确针对缺少内容的情况来定义默认布局。

实现基于内容的容器样式

强制性:您必须在容器元素上使用 :has() 选择器,以检测是否存在特定的子内容。

如需构建一个根据其内容更改布局的组件,请执行以下操作:

  1. 定义默认样式:将基本布局样式应用于容器元素(例如,简单的单列堆叠)。
  2. 应用基于内容的替换:使用 :has([child-selector]) 定位容器,并在该内容存在时应用新的布局样式(例如多列网格)。

示例:如果存在图片,卡片组件会切换到并排布局。

/* 1. Define the default state on the component container */
/* This applies when there is NO image */
.article-card {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1.5rem;
  border: 1px solid #ccc;
  border-radius: 8px;
}

/* 2. Apply styles when a specific child element is present */
/* MANDATORY: Target the container and use :has() to check for the descendant */
.article-card:has(img) {
  /* Change the layout to a row if an image exists */
  flex-direction: row;
  align-items: center;
}

/* Optional: Style the image itself (no :has() needed here) */
.article-card img {
  width: 150px;
  height: auto;
  border-radius: 4px;
}

/* You can also combine :has() and :not() to explicitly target the ABSENCE of content */
/* This selects a card that DOES NOT have an image */
.article-card:not(:has(img)) {
  /* E.g., apply a different background color for text-only cards */
  background-color: #f9f9f9;
}
<!-- Assume an <h1> precedes these components in the document layout -->
<!-- A card WITH an image (will use row layout) -->
<article class="article-card">
  <img src="thumbnail.jpg" alt="Article thumbnail" />
  <div class="content">
    <h2>Card With Image</h2>
    <p>This card lays out its content horizontally.</p>
  </div>
</article>

<!-- A card WITHOUT an image (will use default column layout) -->
<article class="article-card">
  <div class="content">
    <h2>Text-Only Card</h2>
    <p>This card lays out its content vertically, and gets its background color from the :not(:has()) rule.</p>
  </div>
</article>

性能提示:使用 :has() 时,请将选择器的范围限定在尽可能具体的组件容器(例如 .article-card)中。如果样式更改高度局部化,请避免将其锚定到非常高级别的元素(例如 body:has(img)),因为宽泛的 :has() 查询可能会触发更多布局重新计算。

后备策略

如果基于内容的布局样式对用户体验或页面设计至关重要,您必须为不支持 :has() 选择器的浏览器提供后备方案。对于纯装饰效果,:has() 可用作渐进增强,无需后备。

强制性要求:在为关键布局实现后备方案时,您必须在 CSS 中使用 @supports not selector(:has(*)) 来定义传统的基于类的后备方案(例如 .has-image)。

与基于交互状态的样式设置不同,内容是否存在通常在呈现时就已知晓。最可靠的后备方案是,如果数据中存在子元素(例如图片),则让服务器端模板引擎(或静态网站生成器)将 .has-image 等类注入容器。

如果无法使用服务器端渲染,则必须使用包含 CSS.supports() 的小型脚本来检测内容,并在加载时或动态内容注入后附加类。

/* Fallback CSS for older browsers */
/* We check if the browser DOES NOT support the :has() selector */
@supports not selector(:has(*)) {
  /* Define a traditional modifier class that applies the exact same layout overrides */
  .article-card.has-image {
    flex-direction: row;
    align-items: center;
  }
  
  .article-card:not(.has-image) {
    background-color: #f9f9f9;
  }
}
/* Fallback JavaScript for older browsers (if not using SSR to add the class) */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
  // Find all components that need checking
  const cards = document.querySelectorAll('.article-card');
  
  cards.forEach(card => {
    // If the critical content exists, manually add the fallback class
    if (card.querySelector('img')) {
      card.classList.add('has-image');
    }
  });
}