这篇讲什么
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 直接跳过了一代写法,日常写代码值得用起来。
—— 别看了 · 2026