这篇讲什么
CSS 这几年新增了一批函数式和选择器,大幅简化了以前需要写一堆代码才能完成的需求。本文挑 6 个最常用的:min() / max() / clamp() 三个函数,:where() / :is() / :has() 三个选择器。
min() - Chrome 79+ / 全浏览器支持
min() 函数取多个值中最小的,常用于"响应式宽度":想要某个最大宽度,但小屏幕时跟着屏幕缩。
/* 这个例子没什么意义,因为最小值永远都是200px */ div { width: min(400px, 200px, 300px); }
这条 width: min(100%, 800px) 的含义:
- 大屏幕(超过 800px):宽度 800px(
800px小) - 小屏幕(不到 800px):宽度 100%(
100%小)
替代了以前的写法 max-width: 800px; width: 100%;,一行搞定。
max() - 全浏览器支持
跟 min() 反过来,取最大。用于"最小宽度":
/* 如果100%比较小,那就是100%,如果800px比较小,则为800px */ /* 800px或者更小(比如100%代表700px,那就是700px),无最小宽度 */ div { width: min(100%, 800px); } /* 大概就是下面这个意思 */ div { width: 100%; max-width: 800px; }
这条 width: max(50%, 300px):
- 父容器宽度 > 600px 时(50% 也就是 300px+):用 50%
- 父容器宽度 < 600px 时:固定 300px,不能再小
clamp() - 全浏览器支持
三参数,clamp(min, val, max)。"值在 min 和 max 之间,优先 val"。这是响应式字体的杀手锏。
/* 表示如果50vw小于300px,则width匹配300px,如果50vw大于 300px,则匹配50vw */ /* 300px或者更大,无最大宽度 */ div { width: max(300px, 50vw); } /* 大概就是下面这个意思 */ div { min-width: 300px; width: 50vw; }
这条 font-size: clamp(16px, 4vw, 24px):
- 正常情况:字体随 viewport 缩放,等于 4vw
- 屏幕太小,4vw < 16px 时:固定 16px
- 屏幕太大,4vw > 24px 时:固定 24px
这一招把"字体随屏幕缩放但有上下限"做成了一行,以前要写 media query 三段。
实际例子:
/* 宽度为90%,最小不会低于300px,最大不会高于700px */ div { width: clamp(300px, 90%, 700px); } /* 大概就是下面这个意思 */ div { width: 90%; min-width: 300px; max-width: 700px; } /* 当然你也可以写成这样让你的代码更加难以阅读 */ div { width: max(300px, min(90%, 700px)); }
:where() - Chrome 88+
选择器组合,但权重为 0。这是它的杀手特性 —— 等同于"语法糖,不增加 specificity"。
div { border: max(20px, calc(1vw + 10px)) solid; } /* 上面这样写,等价于下面这样 */ div { border: max(20px, 1vw + 10px) solid; } /* 或者使用变量 */ .var { --extra: 10px; border-width: max(20px, 1vw + var(--extra)); } /* 纯算也是可以的 */ div { width: clamp(50px * 4 * 1.5, (100% / 2) * 2, 400px * 2); }
对比传统写法:
/* 之前 */ input[type="text"], input[type="email"], input[type="url"], input[type="tel"], input[type="password"], input[type="search"] { border: 2px solid; } /* 使用where */ input:where( [type="text"], [type="email"], [type="url"], [type="tel"], [type="password"], [type="search"] ) { border: 2px solid; }
当你写 CSS reset 或者基础样式库时,用 :where() 包裹选择器,后续用户在自己代码里覆盖你的样式不需要额外提权重。
:is() - Chrome 88+
跟 :where() 写法一样,但权重正常(取列表里最高那个的权重)。用作选择器简写:
/* 使用:is() */ a:is(:link, :visited) { color: green; } a:is(:hover, :focus) { text-decoration: none; }
替代了:
/* 权重: 0 0 1 (0 id选择器, 0 类选择器, 1 标签选择器) */ button:where(.button1) { background-color: rebeccapurple; } /* 权重: 0 1 1 (0 id选择器, 1 类选择器,也就是is括号里的, 1 标签选择器,也就是最前面的button) */ button:is(.button2) { background-color: rebeccapurple; }
需要权重时用 :is,不需要权重时用 :where。
:has() - Chrome 105+ / 父选择器
这是 CSS 等了二十年的"父选择器" —— 根据子元素的存在 / 状态来匹配父元素。
/* 全部失效:因为并不存在touch,因为这个错误的touch,导致hover和focus即使写对了也无效 */ .button:hover, .button:focus, .button:touch { background-color: #09f; } /* 会忽略掉错误的,hover和focus可以正常生效,touch会被忽略 */ button:where(:hover, :focus, :touch) { background-color: #09f; }
含义:"包含 img 子元素的 article",给它加上下 padding 24px。以前完全做不到,只能 JS。
更强大的组合:
<div class="box"> <p class="children"> <span>测试</span> </p> </div> <div class="foo"> </div>
这种"表单里有 checkbox 被勾选时,改整个表单的样式"在 :has 出现前需要 JavaScript 介入。现在纯 CSS 搞定。
实际应用例子
结合多个新特性的实际场景:
/* 无关紧要的默认样式 */ .box, .foo { width: 200px; height: 200px; } .foo { background-color: #000000; } /* 如果.box里面有.children的元素,则.box的颜色为#b5b7ff */ .box:has(.children) { background-color: #b5b7ff; } /* 但是has()不能嵌套使用 */ /* 下面的写法是无效的 */ .box:has(p:has(span)) { font-size: 14px; } /* 但是可以使用正常的选择器 */ /* 下面的写法是有效的 */ .box:has(p span) { font-size: 14px; } /* 甚至有更多的用法 */ /* .box的下边距为16px */ .box { margin-bottom: 16px; } /* 如果.box的兄弟是.foo,则下边距为200px,文字大小变为32px */ .box:has(+ .foo) { margin-bottom: 200px; font-size: 32px; } /* 无效:只适用于实际元素,不适用于伪元素 */ p:has(::before) { background-color: #f00; }
浏览器兼容性总结
| 特性 | Chrome | Firefox | Safari |
|---|---|---|---|
| min / max / clamp | 79+ | 75+ | 13+ |
| :where / :is | 88+ | 78+ | 14+ |
| :has | 105+ | 121+ | 15.4+ |
2024+ 几乎所有浏览器都支持,放心写。
一句话总结
min/max/clamp 解决响应式数值;:where/:is 简化选择器语法;:has 给了 CSS 父选择器能力 —— 这一波更新让 CSS 直接跳过了一代写法,日常写代码值得用起来。

正在读取登录状态…
暂无讨论,说说你的看法吧