现代 CSS 里那些被低估的特性
我们已经不需要那么多媒体查询了
响应式设计在过去十年里几乎等同于媒体查询。但媒体查询有一个根本问题:它只能感知视口宽度,不知道组件自己被放在了多宽的空间里。
一个卡片组件放在侧边栏(300px)和放在主区域(800px)时,理想布局完全不同,而媒体查询对此无能为力——除非你为每个使用场景写一套专用的类名。
容器查询
容器查询让组件感知自己的容器:
/* 1) 声明这是一个可查询的容器 */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* 2) 组件根据自己的容器宽度调整 */
.card {
display: grid;
gap: 12px;
}
@container card (min-width: 520px) {
.card {
grid-template-columns: 200px 1fr;
align-items: start;
}
}
@container card (min-width: 800px) {
.card {
grid-template-columns: 260px 1fr auto;
}
}这一段 CSS 的价值在于:卡片组件现在是真正自包含的。同一个 .card 类,放进窄容器就竖排,放进宽容器就横排,不需要任何外部干预。
:has() —— 终于能选父元素了
:has() 被称为"父选择器",但实际上它的能力远不止于此:
/* 表单:有输入时才高亮整个字段组 */
.field:has(input:focus) {
outline: 2px solid var(--brand);
}
/* 有图片的文章卡片,标题收紧一点 */
.post-card:has(img) .card-title {
margin-bottom: 6px;
}
/* 数量为 0 时隐藏整个区块 */
.list:not(:has(li))::after {
content: "暂无内容";
color: var(--text-mute);
}
/* 前面紧跟标题的段落,去掉上边距 */
h2:has(+ p) {
margin-bottom: 0.4em;
}最后那个例子在过去必须用 JavaScript 才能做——遍历所有标题,检查下一个兄弟节点是不是段落,再加类名。现在一行 CSS 就完成了,而且窗口 resize、内容动态插入时都会自动更新。
color-mix() 与相对颜色
以前要做一个"主色的 10% 透明版本",你得手动算出 rgba 值,主色一改就全部失效:
/* 旧写法:主色变了这里就不同步了 */
--brand-soft: rgba(246, 130, 31, 0.1);
/* 新写法:从主色推导,永远同步 */
--brand-soft: color-mix(in srgb, var(--brand) 12%, transparent);
/* 半透明背景,用于毛玻璃顶栏 */
.site-header {
background: color-mix(in srgb, var(--bg) 88%, transparent);
backdrop-filter: saturate(180%) blur(12px);
}这让主题系统变得极其好维护:只需要定义几个基础色,其余几十个派生色全部自动计算。改一次 --brand,整站配色跟着变。
逻辑属性:一份 CSS 服务所有书写方向
物理属性(margin-left)在从右往左的语言里会失效。逻辑属性用书写方向代替物理方向:
/* 旧:阿拉伯语环境下会错位 */
.entry {
margin-left: 24px;
border-left: 3px solid var(--brand);
padding-right: 16px;
}
/* 新:自动适配 LTR / RTL / 竖排 */
.entry {
margin-inline-start: 24px;
border-inline-start: 3px solid var(--brand);
padding-inline-end: 16px;
}对只做中文/英文站点的项目,这看起来没必要。但一旦你要支持阿拉伯语或希伯来语,改写成本从"重写整个样式表"降到"几乎为零"。
一些语法糖
嵌套
.post-card {
padding: 24px;
&:hover {
transform: translateY(-3px);
}
.card-title {
margin: 0;
}
}原生嵌套已经是标准,不再需要 Sass 或 PostCSS 来编译这一层。
clamp() 做流体排版
/* 视口 375px 时约 28px,1440px 时约 40px,中间线性插值 */
.post-title {
font-size: clamp(1.75rem, 4vw, 2.5rem);
}一行替代了过去三四个断点的手写媒体查询,而且任意视口宽度下都是连续变化的,没有断点处的跳变。
一个实用的组合:主题切换
把这些特性组合起来,实现一个跟随系统 + 手动覆盖的主题系统:
:root {
color-scheme: light;
--bg: #ffffff;
--text: #1a1d23;
--brand: #f6821f;
}
/* 跟随系统 */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
color-scheme: dark;
--bg: #0f1115;
--text: #e8eaee;
--brand: #ff9d4d;
}
}
/* 手动强制 */
:root[data-theme="dark"] {
color-scheme: dark;
--bg: #0f1115;
--text: #e8eaee;
--brand: #ff9d4d;
}关键点是 :root:not([data-theme="light"]) 这个选择器:它让"跟随系统"成为默认行为,同时允许手动指定 light 来覆盖系统偏好。color-scheme 属性则告诉浏览器该用哪种原生控件配色(滚动条、表单控件、下拉框),这一步经常被漏掉,导致深色模式下出现刺眼的白底滚动条。
小结
| 特性 | 解决的问题 | 替代掉的方案 |
|---|---|---|
| 容器查询 | 组件感知自身空间 | 为每个场景写专用类名 |
:has() | 根据子/兄弟元素选父元素 | JavaScript 遍历加类 |
color-mix() | 从基础色推导派生色 | 手写 rgba 常量 |
| 逻辑属性 | 书写方向自适应 | 为 RTL 重写样式表 |
| 原生嵌套 | 样式层级组织 | Sass / PostCSS |
clamp() | 流体排版 | 多个断点媒体查询 |
这些特性的共同点是:它们把过去必须靠构建工具或 JavaScript 完成的事,变成了浏览器原生能力。前端工具链越来越复杂的同时,CSS 本身其实一直在悄悄变强。