12k
All articles

何时使用 CSS `if()` 与容器样式查询

对比 CSS if() 和 container style queries:何时使用、语法陷阱、range syntax 以及 2026 年浏览器支持。

OpenReplay Team
OpenReplay Team
何时使用 CSS `if()` 与容器样式查询

当单个元素需要内联决定自身的值时,使用 CSS if() 函数——例如主题令牌、组件状态、响应式属性微调。当某个父元素的样式需要驱动多个后代元素的一组规则时,则使用容器样式查询(@container style(...))。一句话概括:if() 产生元素局部的条件值;容器样式查询建立上下文,将条件规则块从父元素向下应用到其子元素。

两者都是读取自定义属性的条件 CSS 机制,都使用相同的 style() 查询原语,且功能重叠程度足以让人选错工具,写出别扭的代码。本文将厘清这种重叠:各自的功能、决定使用哪个的机制差异、两个常见语法陷阱(分号问题以及冒号与等号的写法差异)、两者现已支持的范围语法、场景与工具对照表,以及在截至 2026 年中期浏览器支持三分天下的情况下如何落地使用。

核心要点

  • if() 读取同一规则同一元素上声明的自定义属性并返回一个值;@container style() 只会沿级联向查找,并将完整规则块应用于后代元素。
  • if() 内部,条件与值对之间用分号(而非逗号)分隔,else 提供回退值;若无匹配条件且未提供 else,该函数将返回一个保证无效的值,最终解析为属性的初始值。
  • style() 内部,冒号写法(style(--n: 3))进行令牌/字符串匹配,不做任何运算;而比较运算符写法(style(--n = 3))则将两侧解析为数值类型,并先对 calc() 求值后再进行数值比较。
  • 截至 2026 年中期,两者的支持情况存在分歧:基础容器样式查询已在四大主流引擎中落地(Chrome 111+、Edge 111+、Safari 18+、Firefox 151+),而 if() 及默认启用的范围语法仍仅限于 Chromium。
  • 由于 if() 仅限 Chromium,每条 if() 声明都需要在其前面提供同属性的回退值,或使用 @supports 守卫包裹。

两种机制的最简定义

if() 函数是一种值级别的条件表达式,可放置在任意属性中。其参数是以分号分隔的条件–值对列表,每个条件与其值之间用冒号分隔,可选的 else 提供默认值:

.badge {
  color: if(
    style(--variant: danger): white;
    style(--variant: muted): #555;
    else: black;
  );
}

每个条件属于以下三种查询类型之一:

  • style() 测试当前元素上的自定义属性;
  • media() 执行内联媒体查询;
  • supports() 执行特性查询。

MDN 参考文档以相同的分号语法记录了这三种类型——例如,flex-direction: if(media(orientation: landscape): row; else: column;) 可根据媒体查询结果切换单个属性。

容器样式查询是一个 @container at 规则,其条件由一个或多个 style() 写法组成,读取的是容器元素的自定义属性。它包裹的是一个规则块,而非产生单个值:

.card { --status: active; }

@container style(--status: active) {
  .card-title { font-weight: 700; }
  .card-icon { opacity: 1; }
  .card-meta { color: green; }
}

根据 MDN 容器查询指南,每个元素默认都是样式容器——与尺寸查询不同,样式查询无需设置 container-type。这消除了人们在使用容器查询时预期会遇到的主要配置障碍。

决定使用哪个的核心区别

两者的机制差异在于查找范围。if() 读取同一规则同一元素上声明的自定义属性,是自包含的;而 @container style() 只会沿级联向查找祖先容器。MDN 对此有明确说明:在 if() 中使用样式查询,可以根据元素自身是否设置了某个自定义属性来对其进行样式设置,而不是检查父元素上的样式。两者被明确定位为互补关系,而非冗余。

这种查找范围的差异带来三个实际影响:

  1. 输出粒度。 if() 只设置一个值。@container 在条件为真时应用完整的规则块——跨多个选择器的多条声明。正如 MDN 所述,if() 样式查询只能设置单个属性值,而 @container 查询可以有条件地应用整组规则。

  2. 方向性。 if() 可以读取其正在样式化的元素本身声明的属性。@container 则无法读取自身元素的值来样式化该元素;它读取的是祖先元素的值,并对后代元素应用样式。

  3. 作用域。 只有容器样式查询才能通过 container-name 限定作用域。为容器命名后,查询仅针对该容器求值;若命名容器未定义被查询的属性,则该规则块直接不执行。if() 没有等效的作用域控制——它始终针对所在元素进行解析。

因此,在重叠区域的取舍标准是:如果一个元素上的一个属性需要切换,if() 的间接层次更少。如果多个后代元素需要响应一个父元素的值,@container 只需一个条件即可表达,而无需在每个子元素上重复 if()

常见的语法陷阱

if() 中的条件–值对用分号分隔,而非逗号。 这个问题对从 JavaScript 或 rgb() 等逗号分隔的 CSS 函数迁移过来的开发者来说几乎无一例外地会踩坑。语法格式为 if(condition-1: value-1; condition-2: value-2; else: fallback),最后一对后面的分号可以省略。此外,if 与左括号之间不能有空格,否则整条声明无效。当没有条件匹配且省略了 else 时,if() 返回一个保证无效的值——在普通属性上下文中,这会解析为属性的初始值,而这几乎不是你想要的结果。请务必包含 else

style() 内部冒号与等号的区别,是几乎没人讲清楚的微妙而重要的细节。 if() 和容器样式查询都支持两种写法,且行为确实不同,MDN 现已对此有文档记录。

普通(冒号)写法对计算值进行令牌/字符串匹配,不做任何运算。范围(运算符)写法——使用 =<<=>>=——会对两侧求值,将两者解析为数值类型,对 calc() 求值后进行数值比较。其结果直接引用 MDN 自己的示例:

.box { --n: calc(6 / 2); }

/* FALSE:--n 的计算值是字符串 "calc(6/2)",
   它不等于令牌 "3" */
@container style(--n: 3) { /* … */ }

/* TRUE:两侧均解析为 <number>,calc(6/2) 计算结果为 3,
   且 3 = 3 */
@container style(--n = 3) { /* … */ }

因此,style(--n: 3)--n: calc(6/2) 的匹配失败,而 style(--n = 3) 则匹配成功。还有两条规则值得注意。使用普通写法时,自定义属性名必须在左侧且不能使用 var()——style(var(--n): 3) 是无效的——而范围写法则允许在两侧以任意顺序使用属性名、var()、字面量或 calc()。此外,等价值(如 blue#0000ff)只有在属性通过 @property 注册并指定了 syntax 描述符的情况下,才能在冒号写法中匹配。结论:关键字/字符串匹配使用冒号写法(style(--theme: dark)),数值比较使用运算符写法(style(--columns >= 3))。

范围语法现已适用于两者

范围语法将两种机制的能力从精确匹配扩展到数值比较,操作数可来自自定义属性、attr() 或字面量值。根据 Chrome 142 发布说明,比较仅在两侧解析为相同数据类型时才能求值,限于七种数值类型:<length><number><percentage><angle><time><frequency><resolution>。三值区间形式同样有效——@container style(0 < --n < 10)——两个比较符方向须一致。

范围语法使通知徽章等模式变得简洁。从属性读取计数值并按阈值分支:

@container style(--count > 99) {
  .badge::after { content: "99+"; }
}

由于范围写法进行数值解析,style(--s = new) 对关键字 new 的匹配结果为假,而 style(--s: new) 则为真——这再次说明写法的选择至关重要。

按场景选择工具

场景选用原因
主题令牌 → 单个属性(如从 --theme 设置 colorif()单个元素内联决定自身值
组件状态切换多个子元素样式(激活/错误)@container style()一个父元素值驱动跨后代的规则块
单个属性的响应式微调if() 配合 media()局部的单属性逻辑,无需 @media
通知计数徽章阈值两者均可,通过范围语法若兄弟元素也需响应则用 @container;若只有徽章变化则用 if()
主题父元素上的对比度感知文字@container style()子元素读取父元素的主题并一起重新样式化
特性门控值(如带回退的 lch()if() 配合 supports()基于支持情况的单值替换
断点处的多属性布局变更@media(两者均不适用)两者都不适合大规模布局切换

经验法则:单个条件if() 的职责;对子树进行重新样式化的条件上下文是容器样式查询的职责。

2026 年的落地方案

浏览器支持呈三分格局,“条件 CSS 仅限 Chromium”的旧说法已不再准确。截至 2026 年 6 月的浏览器支持情况:

特性Chrome / EdgeSafariFirefox
容器样式查询(自定义属性,普通语法)111+18+151+
if() 函数137+未支持未支持
style() / if() 中的范围语法142+(默认启用)未支持151,需开启 layout.css.attr.enabled

基础容器样式查询现已真正实现跨引擎支持。它在 Chrome 和 Edge 111 中落地,在 Safari 18.0 中进入 WebKit,并于 2026 年 5 月 19 日发布的 Firefox 151 中正式支持,该版本新增了基于容器自定义属性的 @container style() 查询支持。相比之下,if() 函数仍仅限于 Chromium——MDN 将其标记为实验性特性,可用性有限——范围语法也仅在 Chromium 中默认启用。Firefox 151 虽然支持 @container style() 范围语法,但默认未启用,需通过 layout.css.attr.enabled 偏好项开启。

实际建议:if() 不能优雅降级,因此应先写静态默认声明,让支持的浏览器覆盖它,或将规则包裹在 @supports 守卫中。以下模式同时覆盖两种机制:

/* 回退优先——所有浏览器均应用此样式 */
.card-title { font-weight: 400; }

/* 渐进增强:仅支持样式查询的引擎应用此样式 */
@supports (container-type: normal) {
  @container style(--status: active) {
    .card-title { font-weight: 700; }
  }
}

/* if():先写静态值,再用条件覆盖 */
.badge { padding: 0.25em; }
.badge { padding: if(style(--size: lg): 0.5em; else: 0.25em); }

由于 if() 和默认启用的范围语法在 Firefox 和 Safari 中仍走回退路径——而这些浏览器是以 Chrome 为主的开发者在开发时很少打开的——跨浏览器会话回放是一种实用技术,可以真实观察回退分支在用户浏览器中的渲染效果,而不是凭假设认为它看起来没问题。关于 if() 内部更深入的 calc() 和嵌套模式,LogRocket 的决策导向详解比本文篇幅所能涵盖的更为深入。

按意图选择,而非追求新颖:单个元素上的条件if();从父元素对子树进行重新样式化的条件上下文用容器样式查询。目前两者都应在回退方案后落地,在生产环境依赖 if() 前重新检查支持矩阵,随着两者的发展,它们将越来越多地覆盖过去需要自定义属性技巧和 JavaScript 才能实现的条件 CSS 场景。

常见问题

容器样式查询能像 if() 那样读取同一元素自身的自定义属性吗?

不能。容器样式查询始终沿级联向上查找祖先容器,并将规则应用于后代元素,因此无法读取其将要样式化的那个元素本身的自定义属性。if() 函数则恰恰相反:它读取同一规则在同一元素上声明的自定义属性,并内联解析出一个值。如果需要元素根据自身属性进行分支,使用 if();如果父元素的值需要对其子元素重新样式化,则使用容器样式查询。

容器样式查询是否像尺寸查询那样需要设置 container-type?

不需要。每个元素默认都是样式容器,因此基于自定义属性的样式查询无需声明 container-type 即可生效。这与容器尺寸查询不同,后者确实需要在被查询的祖先元素上设置 container-type(如 inline-size),尺寸条件才能解析。MDN 的容器查询指南对此有记录,这消除了开发者初次使用 @container at 规则时预期会遇到的主要配置步骤。

为什么我的带 calc() 的样式查询从不匹配?

因为普通冒号写法进行的是令牌和字符串匹配,不做任何运算。设置为 calc(6 / 2) 的属性,其计算值是字符串 'calc(6/2)',永远不会匹配令牌 3,所以 style(--n: 3) 返回假。要进行数值比较,请使用比较运算符写法 style(--n = 3),它会对两侧求值、对 calc() 求值、将两者解析为数值类型后进行数值比较,因此返回真。关键字匹配使用冒号形式,数值比较使用运算符形式。

if() 在 Safari 和 Firefox 中能优雅降级吗?

不能。截至 2026 年中期,if() 仅在 Chromium 浏览器(Chrome 和 Edge 137+)中支持,因此 Safari 和 Firefox 会忽略任何包含它的声明,并回退到之前生效的值。应先写静态默认声明,让支持的浏览器覆盖它,或将条件规则包裹在 @supports 守卫中。基础容器样式查询的跨引擎兼容性更好,已在 Chrome 111+、Edge 111+、Safari 18+ 和 Firefox 151+ 中落地,但默认启用的范围语法仍仅限于 Chromium。

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.