现代 CSS 特性让你不再需要 JavaScript
多年来,前端开发者习惯性地使用 JavaScript 来处理交互式 UI 模式。手风琴需要点击事件处理器。工具提示需要定位库。响应式组件需要 resize observers。这个时代正在结束。
现代 CSS 现在可以处理状态样式、组件级断点、基于滚动的效果和原生弹出层——完全不需要一行 JavaScript。这些不是实验性特性。它们稳定、广泛支持,并已为 2025 年的生产环境做好准备。
核心要点
- CSS
:has()选择器可以基于子元素的状态来设置父元素样式,无需 JavaScript 即可实现表单验证样式和交互组件。 - 容器查询让组件响应其容器的大小而非视口大小,取代了 JavaScript resize observers。
- 滚动驱动动画在合成器线程上运行,提供比 Intersection Observer 替代方案更流畅的性能。
- Popover API 和
<details name="">属性提供原生、可访问的工具提示、菜单和手风琴,无需自定义脚本。
CSS :has() 选择器:父元素选择终于来了
CSS :has() 选择器解决了开发者抱怨了几十年的问题:基于子元素的状态来设置父元素样式。
以前,当复选框被选中时切换卡片的外观需要 JavaScript 事件监听器。现在 CSS 可以直接处理:
.card:has(input:checked) {
border-color: blue;
}
这种模式消除了整个类别的 JavaScript:
- 表单验证样式:基于
:valid或:invalid输入来设置容器样式 - 状态驱动布局:当子元素为空或存在时更改父网格
- 交互组件:使用隐藏输入和
:has()构建选项卡、手风琴和开关
该选择器在所有主流浏览器中都可用。对于旧版浏览器支持,可以将样式包装在 @supports selector(:has(*)) 中并提供基准体验。
CSS 容器查询:组件级响应式设计
媒体查询响应视口大小。CSS 容器查询响应组件容器的大小——这是我们构建响应式布局方式的根本性转变。
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
}
这很重要,因为组件存在于不同的上下文中。侧边栏中的卡片与主内容区域中的相同卡片表现不同。容器查询让组件适应其实际可用空间,而不是浏览器窗口。
在容器查询出现之前,实现这一点需要 JavaScript resize observers 和手动类切换。现在它是声明式的 CSS。
滚动驱动动画:不再需要 Intersection Observer
传统的滚动触发动画意味着导入库或编写 Intersection Observer 代码。CSS 滚动驱动动画取代了两者。
两种时间轴类型处理大多数用例:
滚动时间轴将动画进度与滚动位置绑定:
@keyframes grow {
from { width: 0; }
to { width: 100%; }
}
.progress-bar {
animation: grow linear;
animation-timeline: scroll();
}
视图时间轴在元素进入视口时触发:
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.reveal {
animation: fade-in linear;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
这些动画在合成器线程上运行,提供比 JavaScript 替代方案更流畅的性能。始终使用 @media (prefers-reduced-motion: reduce) 尊重用户偏好,以禁用或简化动画。
Discover how at OpenReplay.com.
Popover API:原生工具提示和菜单
构建可访问的弹出层历来需要管理焦点陷阱、外部点击检测、Escape 键处理和 z-index 堆叠。Popover API 原生处理所有这些。
<button popovertarget="menu">打开菜单</button>
<div id="menu" popover>
<p>菜单内容</p>
</div>
浏览器自动:
- 将弹出层定位在顶层(所有其他内容之上)
- 在外部点击或按 Escape 时关闭它
- 适当管理焦点
- 处理可访问性公告
使用 CSS 设置弹出层样式,包括通过 @starting-style 实现的入场动画。popover="auto" 默认在用户与其他地方交互时关闭,而 popover="manual" 需要显式关闭。
使用 <details name=""> 的原生手风琴
<details> 元素多年来一直支持手风琴。name 属性添加了独占行为——一次只打开一个面板:
<details name="faq">
<summary>第一个问题</summary>
<p>答案内容</p>
</details>
<details name="faq">
<summary>第二个问题</summary>
<p>答案内容</p>
</details>
无需 JavaScript。完整的键盘可访问性。内置屏幕阅读器支持。设置 [open] 状态和 ::marker 伪元素样式以匹配你的设计系统。
渐进增强仍然重要
这些特性享有广泛支持,但渐进增强仍然是良好实践。使用 @supports 提供降级方案:
@supports not (container-type: inline-size) {
/* 使用媒体查询的降级样式 */
}
这种方法确保在所有地方都有基准功能,同时在支持的地方提供改进的体验。
结论
没有 JavaScript 的现代 CSS 并不是要完全避免 JavaScript——而是要选择正确的工具。声明式 CSS 解决方案实现起来更快,更易于维护,并且通常比脚本等效方案性能更好。
首先审查你当前的 JavaScript。如果它基于状态切换类、定位工具提示或监视滚动位置,CSS 现在可能可以处理它。浏览器完成繁重的工作。让它来做吧。
常见问题
本文涵盖的特性——:has()、容器查询、滚动驱动动画和 Popover API——截至 2025 年在所有主流浏览器中都受支持,包括 Chrome、Firefox、Safari 和 Edge。滚动驱动动画的支持范围最窄,因此请始终查看 caniuse.com 获取当前兼容性数据,并使用 @supports 提供降级方案。
:has() 选择器可以处理许多基于状态的样式场景,但有局限性。它适用于基于表单状态、子元素存在或兄弟条件的样式设置。对于复杂的多步骤逻辑、条件渲染或数据获取,仍然需要 JavaScript。使用 :has() 进行视觉状态更改,而不是应用程序逻辑。
CSS 滚动驱动动画通常优于 JavaScript 替代方案,因为它们在合成器线程上运行,与主线程分离。这可以防止布局抖动和卡顿。但是,动画触发布局重新计算的属性(如 width 或 height)仍然可能导致性能问题。为获得最佳效果,请坚持使用 transform 和 opacity。
使用 @supports 规则检测特性可用性并提供替代样式。例如,@supports not (container-type: inline-size) 允许你定义媒体查询降级方案。对于依赖 JavaScript 的降级方案,在初始化 polyfills 或替代实现之前,在脚本中检查特性支持。
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.