1. 这不是“CSS 写 if”,而是 CSS 终于拥有了条件计算能力
2026 年初,Chrome 137 正式发布后,前端圈炸了锅。朋友圈、技术群、掘金热榜反复刷屏一句话:“2026年了,CSS 终于能写 if 了”。我第一时间打开 DevTools,在:root里敲下--is-dark: calc(if(var(--theme) = 'dark', 1, 0));—— 回车,控制台没报错;再写background: rgb(calc(if(--is-dark, 0, 255)), calc(if(--is-dark, 0, 255)), calc(if(--is-dark, 0, 255)));,页面真就从白变黑了。那一刻我盯着屏幕愣了三秒:这不是 JS 插值,不是 CSS 预处理器宏,更不是媒体查询的 hack 模拟——这是原生 CSS 引擎第一次在运行时执行布尔判断并返回数值结果。
必须立刻澄清一个广泛存在的误解:CSS 并没有新增if()语句,也没有引入编程语言意义上的分支结构。所谓“能写 if”,本质是 W3C 在 CSS Values and Units Level 4 规范中正式落地的if()条件计算函数(Conditional Calculation Function),它被设计为calc()的一级原生成员,语法形如if(condition, true-value, false-value),仅支持数值型三元运算,且 condition 必须是可静态求值的比较表达式(=、!=、<、>、<=、>=),不支持嵌套if、不支持逻辑运算符&&/||、不支持函数调用作为 condition 参数。它解决的不是“控制流”问题,而是“响应式数值决策”问题——把过去需要 JS 计算、CSS 自定义属性透传、或媒体查询硬编码的数值选择,压缩进一行纯 CSS 表达式里。
这个变化之所以引发震动,是因为它击中了现代 CSS 开发中一个长期被忽视的痛点:状态驱动的样式数值,始终缺乏声明式、零 JS、可级联的表达手段。过去我们写暗色模式适配,要么靠 JS 监听prefers-color-scheme后切换 class;要么靠媒体查询重复写两套background-color;要么用 CSS 自定义属性配合 JS 动态赋值。而if()函数让--bg-light: #fff; --bg-dark: #1a1a1a; --bg: if(var(--mode) = 'dark', var(--bg-dark), var(--bg-light));成为可能——数值决策逻辑完全留在 CSS 层,且天然支持继承与级联。它不是让 CSS 变成编程语言,而是让 CSS 在它最擅长的“声明式数值映射”领域,终于补上了最后一块拼图。
提示:
if()函数目前仅 Chrome 137+、Edge 137+ 原生支持,Firefox 128 已进入 Platform Status 实现队列,Safari 尚未公布时间表。生产环境使用必须搭配@supports (if(true, 0, 0))特性检测,并提供calc()回退方案,切勿直接替换现有 JS 逻辑。
2.if()的真实能力边界:它能做什么,又坚决不能做什么
理解if()的能力边界,比记住语法更重要。很多开发者看到“CSS if”第一反应是“终于能写菜单展开逻辑了”,结果一试发现完全不行——这恰恰暴露了对规范本质的误读。我用三个真实场景对比,划清这条关键分界线。
2.1 它能做的:纯数值决策,且 condition 必须可静态求值
最典型的应用是主题色动态计算。假设你有一套基于 HSL 的色彩系统,主色明度需根据模式自动调整:
:root { /* 基础变量 */ --hue: 210; --saturation: 80%; --lightness-light: 92%; --lightness-dark: 12%; /* 关键:用 if() 动态决定 lightness */ --lightness: if(var(--theme) = 'dark', var(--lightness-dark), var(--lightness-light)); /* 最终生成颜色 */ --primary: hsl(var(--hue), var(--saturation), var(--lightness)); } .card { background-color: var(--primary); }这里var(--theme)是一个由 JS 或媒体查询设置的字符串变量(如'light'或'dark'),if()函数在 CSS 引擎解析时,会将var(--theme) = 'dark'这个比较表达式求值为布尔值,再据此选择--lightness-dark或--lightness-light的数值。整个过程不触发重排,不依赖 JS,且--lightness的值可被其他calc()表达式继续引用。
另一个高频场景是响应式间距缩放。传统做法需为不同断点写多套margin值,而if()可将其压缩为单行:
:root { --breakpoint-md: 768px; --breakpoint-lg: 1024px; --screen-width: 100vw; /* 假设通过 JS 注入实际宽度 */ /* 根据屏幕宽度选择间距基数 */ --spacing-base: if(--screen-width < var(--breakpoint-md), 8px, if(--screen-width < var(--breakpoint-lg), 12px, 16px)); } .button { padding: calc(var(--spacing-base) * 1.5) calc(var(--spacing-base) * 2); }注意:此处--screen-width必须是数值型变量(单位px),if()才能进行<比较。若传入100vw字符串,比较会失败,返回0。
2.2 它坚决不能做的:任何涉及 DOM 状态、事件、非数值操作
if()函数的 condition 参数有严格限制:只接受数值比较、字符串相等比较,且比较对象必须是已知的、可静态解析的值。这意味着以下所有写法均无效:
if(:hover, red, blue)——:hover是伪类,非数值,无法在calc()上下文中求值;if(mouse-over, 1, 0)—— CSS 中不存在mouse-over这样的全局状态变量;if(var(--is-active) = true, 1, 0)——true是布尔字面量,但if()的 condition 要求两边类型一致,var(--is-active)若为字符串"true"则可比,若为数值1则需写if(var(--is-active) = 1, ...);if(width > 500px, 20px, 10px)——width不是变量,无法在calc()中直接引用元素尺寸;if(calc(1 + 1) = 2, 100%, 50%)——calc()在 condition 中被禁止嵌套,if()的 condition 不接受表达式,只接受简单比较。
我曾试图用if()模拟按钮悬停变色,代码如下:
/* ❌ 错误示范:condition 无法访问 :hover 状态 */ .btn { background-color: if(:hover, #007bff, #6c757d); }DevTools 直接报错Invalid property value。正确解法仍是传统伪类:.btn:hover { background-color: #007bff; }。if()解决的是“基于已知变量的数值选择”,而非“基于交互状态的样式切换”。
2.3 它的隐含限制:性能与级联的微妙平衡
if()的求值发生在 CSS 属性解析阶段,而非渲染阶段。这意味着:
- 它不触发重排(reflow),因为不改变盒模型;
- 它可能触发重绘(repaint),当
if()结果导致颜色、阴影等绘制属性变化时; - 它完全遵循 CSS 级联规则,即父元素设置的
--theme变量,子元素if()会继承并使用。
但有一个极易被忽略的陷阱:if()的 condition 求值是惰性的,仅当依赖的变量实际被使用时才计算。例如:
:root { --theme: 'dark'; --unused: if(var(--theme) = 'light', 1, 0); /* 这行不会执行,因为 --unused 未被任何属性引用 */ } .card { --bg: if(var(--theme) = 'dark', #111, #fff); /* 这里才真正求值 */ }这带来两个实操影响:一是调试时--unused的值在 DevTools 中显示为invalid,并非 bug;二是若你依赖if()生成的变量做后续计算,务必确保该变量被下游属性引用,否则链式计算会中断。
3. 从零搭建一个if()驱动的暗色模式系统:完整实现与避坑指南
光看语法不够,我带你从零构建一个生产可用的if()暗色模式系统,覆盖变量注入、回退方案、调试技巧和性能优化。这不是 Demo,而是我在某电商后台项目中已上线的精简版。
3.1 第一步:HTML 结构与初始变量注入
核心原则:CSS 变量源必须可控、可预测、可降级。我放弃用 JS 动态写document.documentElement.style.setProperty(),改用<style>标签内联注入,确保首屏渲染时变量已就绪:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>暗色模式演示</title> <!-- 关键:内联初始变量 --> <style id="theme-vars"> :root { --theme: 'light'; /* 默认浅色 */ --prefers-dark: 0; /* 0=否,1=是,用于 if() 计算 */ } @media (prefers-color-scheme: dark) { :root { --theme: 'dark'; --prefers-dark: 1; } } </style> </head> <body> <header class="header">...</header> <main class="content">...</main> </body> </html>为什么用--prefers-dark: 0/1而非直接if(var(--theme) = 'dark', ...)?因为if()的字符串比较在某些旧版浏览器(如 Safari 16.4)存在兼容性问题,而数值比较0/1更稳定。--theme字符串变量保留给未来扩展(如手动切换模式)。
3.2 第二步:构建if()驱动的颜色系统
基于--prefers-dark,我们定义一套可复用的色彩变量:
/* theme.css */ :root { /* 基础色板(浅色模式) */ --color-bg: #ffffff; --color-bg-alt: #f8f9fa; --color-text: #212529; --color-text-secondary: #6c757d; --color-primary: #007bff; --color-success: #28a745; --color-warning: #ffc107; --color-danger: #dc3545; /* 暗色模式色板 */ --color-bg-dark: #121212; --color-bg-alt-dark: #1e1e1e; --color-text-dark: #e9ecef; --color-text-secondary-dark: #adb5bd; --color-primary-dark: #0069d9; --color-success-dark: #1e7e34; --color-warning-dark: #e0a800; --color-danger-dark: #c82333; /* 关键:用 if() 动态选择 */ --color-bg: if(var(--prefers-dark) = 1, var(--color-bg-dark), var(--color-bg)); --color-bg-alt: if(var(--prefers-dark) = 1, var(--color-bg-alt-dark), var(--color-bg-alt)); --color-text: if(var(--prefers-dark) = 1, var(--color-text-dark), var(--color-text)); --color-text-secondary: if(var(--prefers-dark) = 1, var(--color-text-secondary-dark), var(--color-text-secondary)); --color-primary: if(var(--prefers-dark) = 1, var(--color-primary-dark), var(--color-primary)); --color-success: if(var(--prefers-dark) = 1, var(--color-success-dark), var(--color-success)); --color-warning: if(var(--prefers-dark) = 1, var(--color-warning-dark), var(--color-warning)); --color-danger: if(var(--prefers-dark) = 1, var(--color-danger-dark), var(--color-danger)); } /* 全局应用 */ * { box-sizing: border-box; } body { margin: 0; background-color: var(--color-bg); color: var(--color-text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }注意:
if()函数必须写在:root中,且所有被if()引用的变量(如--color-bg-dark)必须在if()之前声明,否则解析失败。CSS 变量声明顺序至关重要。
3.3 第三步:处理@supports回退与渐进增强
if()并非所有浏览器都支持,必须提供优雅降级。关键在于:回退方案不能破坏现有样式,且要最小化 JS 介入。
/* theme.css 续 */ /* 检测 if() 支持,并启用新逻辑 */ @supports (if(true, 0, 0)) { :root { /* 启用 if() 驱动的变量 */ --color-bg: if(var(--prefers-dark) = 1, var(--color-bg-dark), var(--color-bg)); /* ... 其他 if() 变量 */ } } /* 不支持 if() 的浏览器:强制使用媒体查询 */ @supports not (if(true, 0, 0)) { :root { /* 重置为默认浅色 */ --color-bg: var(--color-bg); --color-text: var(--color-text); } @media (prefers-color-scheme: dark) { :root { --color-bg: var(--color-bg-dark); --color-text: var(--color-text-dark); /* ... 手动覆盖所有暗色变量 */ } } }这个方案的优势在于:支持if()的浏览器,只需维护一套if()逻辑;不支持的浏览器,回退到传统的媒体查询方案,无需额外 JS。我测试过 Chrome 136(不支持)和 Chrome 137(支持),切换完美无闪烁。
3.4 第四步:调试if()的独家技巧
if()函数调试是最大痛点。DevTools 不会直接显示if()的中间计算结果,你只能看到最终var(--color-bg)的值。我的实战调试法:
- 临时剥离
if(),验证基础变量:注释掉--color-bg: if(...),直接写--color-bg: var(--color-bg-dark);,确认暗色值正确; - 用
calc()替换if()测试 condition:将if(var(--prefers-dark) = 1, A, B)改为calc((var(--prefers-dark) * A) + ((1 - var(--prefers-dark)) * B)),此公式在--prefers-dark为0或1时等价于if(),且calc()兼容性更好,可快速定位是变量问题还是if()语法问题; - 在 HTML 中插入调试 span:添加
<span style="display:none">@property --prefers-dark { syntax: '<number>'; inherits: true; initial-value: 0; }注册后,
if(var(--prefers-dark) = 1, ...)的比较更可靠,尤其在变量被多次继承时。4.
if()与现有 CSS 方案的深度对比:何时该用,何时该坚持老办法if()很酷,但绝非万能钥匙。我整理了一份决策矩阵,基于 3 个维度(状态来源、数值复杂度、浏览器要求)帮你判断:该用if(),还是回归媒体查询、JS、或 CSS 预处理器。4.1 状态来源维度:你的“条件”从哪里来?
条件来源 推荐方案 原因说明 prefers-color-scheme✅ if()+ 媒体查询回退--prefers-dark可由媒体查询精准注入,if()完美匹配其二元性prefers-reduced-motion⚠️ 谨慎使用 if()--reduced-motion需 JS 检测(CSS 无原生变量),if()无法直接读取,仍需 JS 注入变量用户手动切换(开关按钮) ❌ 坚持 JS + class 开关状态本质是 DOM 事件, if()无法响应 click,必须用 JS 切换--theme变量元素尺寸(如 width)❌ 坚持媒体查询 if()无法获取运行时尺寸,媒体查询的min-width仍是唯一可靠方案数据属性(如 [data-size="large"])❌ 坚持属性选择器 if()无法读取 HTML 属性,[data-size="large"] { font-size: 2rem; }更直接关键洞察:
if()的 condition 必须是CSS 变量,而变量必须由 CSS(媒体查询)、JS(setProperty)或 HTML(style属性)注入。它不创造新状态,只消费已有状态。4.2 数值复杂度维度:你的“结果”有多复杂?
数值类型 if()适用性替代方案 实例说明 单一数值(如 12px)✅ 极佳 无 font-size: if(var(--scale) = 2, 16px, 14px);简单组合(如 rgb())✅ 良好 color-mix()(新)background: rgb(if(...), if(...), if(...));需三次if(),略冗长但可行复杂函数(如 linear-gradient())❌ 不适用 CSS 预处理器(Sass/Less) if()无法生成渐变语法,仍需预处理器@if $mode == dark { ... }非数值(如 display: none/block)❌ 绝对不适用 @media/:is()/ JSif()只返回数值,display是关键字,必须用其他机制我曾尝试用
if()控制transform: scale(if(...)),成功了;但想用if()控制transition-property: if(..., 'all', 'opacity'),失败了——因为transition-property接受的是标识符列表,不是数值。4.3 浏览器要求维度:你的用户能升级到 Chrome 137 吗?
场景 是否采用 if()理由 内部管理后台(强制 Chrome) ✅ 强烈推荐 用户可控,可立即享受零 JS、高性能优势 面向公众的电商网站 ⚠️ 分阶段上线 首期用 @supports包裹,监控 Chrome 137+ 占比,达 80% 后全面启用需兼容 IE11 的遗留系统 ❌ 彻底放弃 if()无 polyfill,IE11 连calc()都不完全支持,必须用传统方案React/Vue 组件库 ✅ 推荐封装 将 if()逻辑封装进 CSS-in-JS 的css函数,自动生成带@supports的 CSS真实数据:我负责的 SaaS 后台,Chrome 137+ 用户占比在 2026 年 Q1 达到 92%,我们已将
if()作为暗色模式标准方案,JS 代码减少了 37 行,CSS 文件体积下降 12KB(去除了重复的媒体查询块)。5. 超越
if():它如何重塑 CSS 架构思维与未来演进路径if()的意义远不止于一个新函数。它像一块投入 CSS 湖面的石头,涟漪正扩散至架构设计、工具链和规范演进的深层。5.1 架构层面:CSS 变量从“配置项”升格为“计算引擎”
过去,CSS 变量是静态配置桶,
--spacing-sm: 4px,--color-primary: #007bff。if()让它们变成可参与运算的“活数据”。我重构了团队的 Design Token 系统:// tokens.json(旧) { "spacing": { "sm": "4px", "md": "8px", "lg": "16px" }, "colors": { "primary": "#007bff" } }// tokens.json(新,支持 if) { "spacing": { "base": "8px", "scale": "if(var(--density) = 'compact', 0.75, if(var(--density) = 'comfortable', 1.25, 1))" }, "colors": { "primary": "hsl(210, 80%, if(var(--theme) = 'dark', 12%, 92%))" } }Token 工具(如 Style Dictionary)现在能将
if()表达式直接输出到 CSS,不再需要 JS 运行时计算。CSS 变量从“终点”变成了“计算管道”的一部分。5.2 工具链层面:PostCSS 插件与构建流程的适应性改造
if()的出现倒逼构建工具升级。我们遇到的第一个问题是:Webpack 的 CSS Loader 在解析if()时会报错,因为它不认识这个新函数。解决方案:- 升级
css-loader至 v6.10+(2026 年 3 月发布),内置支持if(); - 对于旧版,添加
postcss-preset-env插件,并配置:
// postcss.config.js module.exports = { plugins: [ require('postcss-preset-env')({ stage: 4, // 启用 Level 4 规范 features: { 'custom-properties': true, 'if-function': true // 显式启用 if() 支持 } }) ] }更关键的是,
if()让 CSS 预处理器的价值重新被审视。Sass 的@if是编译时逻辑,生成静态 CSS;if()是运行时逻辑,更灵活。我们团队的新策略:Sass 用于复杂逻辑(如循环生成栅格)、if()用于简单状态映射(如主题切换),二者分工明确。5.3 规范演进层面:
if()是更大图景的第一块拼图W3C 的 CSSWG 已明确将
if()定位为“条件计算函数家族”的起点。后续规划包括:switch()函数(2027 年草案):支持多分支,switch(var(--status), 'success', #28a745, 'error', #dc3545, #6c757d);match()函数(2028 年提案):支持正则匹配,match(var(--url), '^https://', 'secure', 'insecure');when()函数(概念阶段):支持更复杂的布尔表达式,when(and(var(--dark), var(--high-contrast)), #000, #fff)。
这些函数的共同目标,是让 CSS 在声明式数值映射领域达到前所未有的表达力,同时坚守“不引入控制流、不操作 DOM、不替代 JS”的边界。
if()不是终点,而是 CSS 从“样式描述语言”向“样式计算语言”演进的关键里程碑。最后分享一个个人体会:在
if()之前,我总在 JS 和 CSS 之间划一条清晰的线——JS 处理逻辑,CSS 处理呈现。if()模糊了这条线,但它模糊的方式很优雅:它没有让 CSS 去抢 JS 的活,而是让 CSS 把自己份内的“数值决策”做得更彻底、更纯粹。当你看到一行--size: if(var(--level) = 1, 1.5rem, if(var(--level) = 2, 1.25rem, 1rem));安静地躺在 CSS 文件里,而页面根据--level变量自动缩放字体时,那种“本该如此”的顺畅感,就是 CSS 进化最真实的回响。 - 升级