CSS 正在经历一场复兴。过去几年,浏览器厂商联手推进了大量强大的新特性。从”父选择器” :has() 到层叠控制的 @layer,这些特性正在改变我们编写样式的方式。
:has() — 终于有了”父选择器”
:has() 被称为 CSS 最强大的新选择器,它让你可以根据元素的后代来选择元素。
基础用法
/* 选择包含图片的卡片 */
.card:has(img) {
display: grid;
grid-template-rows: 200px 1fr;
}
/* 选择包含必填输入框的表单组 */
.form-group:has(input:required) {
font-weight: bold;
}
/* 选择包含被选中复选框的列表项 */
li:has(input:checked) {
background-color: #e8f5e9;
}
实用场景
1. 表单验证样式
/* 输入框无效时,整个表单组变红 */
.form-group:has(input:invalid) {
border-left: 3px solid #e53935;
}
/* 输入框有效时,显示成功状态 */
.form-group:has(input:valid) {
border-left: 3px solid #43a047;
}
/* 聚焦时高亮整个区域 */
.form-group:has(input:focus) {
background-color: #fff8e1;
}
2. 动态布局
/* 根据子元素数量调整布局 */
.grid:has(> :nth-child(4)) {
grid-template-columns: repeat(2, 1fr);
}
.grid:has(> :nth-child(7)) {
grid-template-columns: repeat(3, 1fr);
}
/* 没有图片时的卡片布局 */
.card:not(:has(img)) {
padding: 2rem;
}
3. 状态驱动的全局样式
/* 暗色模式切换 */
body:has(#dark-mode:checked) {
--bg-color: #1a1a1a;
--text-color: #ffffff;
}
/* 侧边栏展开时调整主内容 */
body:has(.sidebar.expanded) .main-content {
margin-left: 280px;
}
/* 模态框打开时禁止滚动 */
body:has(.modal.open) {
overflow: hidden;
}
:has() 与其他选择器组合
/* 选择紧跟在包含图片的卡片后面的卡片 */
.card:has(img) + .card {
margin-top: 2rem;
}
/* 选择空状态 */
.list:not(:has(li)) {
display: grid;
place-content: center;
}
.list:not(:has(li))::after {
content: '暂无数据';
color: #999;
}
@layer — 层叠层控制
Cascade Layers(层叠层)让你可以精确控制样式的优先级,不再需要与选择器权重作斗争。
基础语法
/* 定义层的顺序(后面的优先级更高)*/
@layer reset, base, components, utilities;
/* 在特定层中添加样式 */
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
@layer base {
body {
font-family: system-ui, sans-serif;
line-height: 1.6;
}
}
@layer components {
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
}
}
@layer utilities {
.mt-4 { margin-top: 1rem; }
.hidden { display: none; }
}
为什么需要 @layer?
/* 传统问题:第三方库样式难以覆盖 */
/* 即使你的选择器权重更低,也能覆盖 */
@layer third-party, custom;
@layer third-party {
/* 引入的 UI 库样式 */
.btn {
background: blue; /* 选择器权重:0,0,1,0 */
}
}
@layer custom {
/* 你的自定义样式,优先级更高! */
.btn {
background: green; /* 虽然权重相同,但层优先级更高 */
}
}
嵌套层
@layer components {
@layer buttons {
.btn { /* ... */ }
}
@layer cards {
.card { /* ... */ }
}
}
/* 或者这样引用 */
@layer components.buttons {
.btn-primary { /* ... */ }
}
匿名层和导入
/* 匿名层(最低优先级)*/
@layer {
/* 这些样式在所有命名层之前 */
}
/* 将外部样式放入层中 */
@import url("bootstrap.css") layer(framework);
Subgrid — 网格的网格
Subgrid 让子网格可以继承父网格的轨道定义,解决了嵌套对齐问题。
问题场景
<div class="card-grid">
<article class="card">
<h2>短标题</h2>
<p>内容文本...</p>
<footer>操作按钮</footer>
</article>
<article class="card">
<h2>这是一个非常非常长的标题需要换行</h2>
<p>内容文本...</p>
<footer>操作按钮</footer>
</article>
</div>
没有 Subgrid,每张卡片的内部元素无法对齐:
┌─────────────────┐ ┌─────────────────┐
│ 短标题 │ │ 这是一个非常 │
│ │ │ 长的标题需要换行│
├─────────────────┤ ├─────────────────┤
│ 内容文本... │ │ 内容文本... │ ← 不对齐!
├─────────────────┤ │ │
│ 操作按钮 │ ├─────────────────┤
└─────────────────┘ │ 操作按钮 │
└─────────────────┘
Subgrid 解决方案
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
/* 定义行轨道供子网格使用 */
grid-template-rows: subgrid;
}
.card {
display: grid;
/* 继承父网格的行轨道 */
grid-template-rows: subgrid;
/* 占据 3 行 */
grid-row: span 3;
}
现在所有卡片的标题、内容、操作区都完美对齐!
实战:表单布局
.form {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem 2rem;
}
.form-row {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
}
/* 所有标签和输入框自动对齐 */
.form-row label {
grid-column: 1;
text-align: right;
}
.form-row input {
grid-column: 2;
}
嵌套 CSS — 原生支持
不再需要 Sass/Less,浏览器原生支持 CSS 嵌套了!
.card {
background: white;
border-radius: 8px;
/* 嵌套子元素 */
.title {
font-size: 1.5rem;
margin-bottom: 1rem;
}
.content {
color: #666;
}
/* 伪类和伪元素 */
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
&::before {
content: '';
/* ... */
}
/* 媒体查询嵌套 */
@media (min-width: 768px) {
display: flex;
gap: 2rem;
}
}
嵌套选择器 &
.btn {
/* & 代表父选择器 */
/* .btn:hover */
&:hover { }
/* .btn.primary */
&.primary { }
/* .container .btn */
.container & { }
/* .btn + .btn */
& + & {
margin-left: 0.5rem;
}
}
color-mix() — 颜色混合
原生 CSS 现在可以混合颜色了:
:root {
--primary: #3b82f6;
--white: #ffffff;
--black: #000000;
}
.btn {
background: var(--primary);
}
.btn:hover {
/* 将主色与白色混合 20% */
background: color-mix(in srgb, var(--primary), var(--white) 20%);
}
.btn:active {
/* 将主色与黑色混合 20% */
background: color-mix(in srgb, var(--primary), var(--black) 20%);
}
创建调色板
:root {
--primary: #3b82f6;
/* 自动生成明暗变体 */
--primary-50: color-mix(in srgb, var(--primary), white 90%);
--primary-100: color-mix(in srgb, var(--primary), white 80%);
--primary-200: color-mix(in srgb, var(--primary), white 60%);
--primary-300: color-mix(in srgb, var(--primary), white 40%);
--primary-400: color-mix(in srgb, var(--primary), white 20%);
--primary-500: var(--primary);
--primary-600: color-mix(in srgb, var(--primary), black 20%);
--primary-700: color-mix(in srgb, var(--primary), black 40%);
--primary-800: color-mix(in srgb, var(--primary), black 60%);
--primary-900: color-mix(in srgb, var(--primary), black 80%);
}
其他实用新特性
逻辑属性
/* 传统方式 */
.element {
margin-left: 1rem;
margin-right: 1rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
/* 逻辑属性(支持 RTL)*/
.element {
margin-inline: 1rem; /* 左右 */
padding-block: 0.5rem; /* 上下 */
}
accent-color
/* 一行代码改变表单控件颜色 */
:root {
accent-color: #3b82f6;
}
/* 所有复选框、单选框、进度条等都会使用这个颜色 */
text-wrap: balance
/* 平衡标题的换行,避免孤字 */
h1, h2, h3 {
text-wrap: balance;
}
/* 对于更长的文本,使用 pretty */
p {
text-wrap: pretty;
}
@scope
/* 限制样式作用范围 */
@scope (.card) to (.card-content) {
/* 这些样式只应用于 .card 内部,但不包括 .card-content 及其后代 */
p {
margin-bottom: 1rem;
}
}
浏览器支持
| 特性 | Chrome | Firefox | Safari |
|---|---|---|---|
| :has() | 105+ | 121+ | 15.4+ |
| @layer | 99+ | 97+ | 15.4+ |
| Subgrid | 117+ | 71+ | 16+ |
| Nesting | 120+ | 117+ | 17.2+ |
| color-mix() | 111+ | 113+ | 16.2+ |
渐进增强策略
/* 回退方案 */
.card {
display: flex;
flex-direction: column;
}
/* 现代浏览器增强 */
@supports (grid-template-rows: subgrid) {
.card {
display: grid;
grid-template-rows: subgrid;
}
}
@supports selector(:has(*)) {
.form-group:has(:invalid) {
border-color: red;
}
}
总结
现代 CSS 正在变得越来越强大:
| 特性 | 解决的问题 |
|---|---|
:has() | 父元素选择、状态驱动样式 |
@layer | 样式优先级管理 |
| Subgrid | 嵌套网格对齐 |
| Nesting | 减少选择器重复 |
color-mix() | 动态颜色计算 |
关键收获:
:has()是最强大的新选择器,解锁了无数可能@layer让样式优先级变得可控可预测- Subgrid 解决了多年来的嵌套对齐难题
- 原生嵌套让 CSS 预处理器的核心功能变得可选
- 新特性正在快速获得浏览器支持
CSS 的未来已经到来。这些特性不仅让代码更简洁,更让曾经需要 JavaScript 的交互效果变成了纯 CSS 解决方案。
CSS 不再只是”层叠样式表”,它正在成为一门强大的声明式编程语言。