元素内图像的用例和调整图像的样式。在以下示例中,选择带有 figcaptions
的图像,然后选择该上下文中的图像。使用:has()
不会改变主体,因为我们的目标是图像而不是数字:figure:has(figcaption) img {...}
使用 @supports
及其 selector()
函数使检查支持变得简单,该函数在使用之前测试浏览器是否支持该语法:
@supports (selector(:has(works))) {
/* safe to use :has() */
}
相关资源:
- Spec: https://www.w3.org/TR/selectors-4/#relational
- MDN: https://developer.mozilla.org/docs/Web/CSS/:has
- The CSS :has() selector is way more than a "parent selector": https://www.bram.us/2021/12/21/the-css-has-selector-is-way-more-than-a-parent-selector/
2022年及以后的功能
在所有这些令人惊叹的功能在 2022 年登陆之后,仍有许多事情将难以完成。下面来介绍一些剩余的问题以及正在积极开发的解决方案。这些解决方案是实验性的,即使它们可能在浏览器的标志后面被指定或可用。
1. 松散类型的自定义属性
浏览器支持:

CSS 自定义属性是惊人的。它们允许将各种事物存储在命名变量中,然后可以对其进行扩展、计算、共享等。事实上,它们是如此灵活,如果有一些不太灵活的东西会更好。
考虑一个场景,其中长方体阴影使用自定义属性作为其值:
box-shadow: var(--x) var(--y) var(--blur) var(--spread) var(--color);
这一切都会正常运行,直到任何一个属性更改为 CSS 不接受的值,例如 --x: red
。如果任何一个嵌套变量丢失或设置为无效的值类型,则整个阴影会中断。
这就是@property
的用武之地:--x
可以成为一个类型化的自定义属性,不再松散和灵活,但在某些定义的边界下是安全的:
@property --x {
syntax: '<length>';
initial-value: 0px;
inherits: false;
}
现在,当 box-shadow
中的var(--x)
使用 --x: red
时,red 将被忽略,因为它不是
。这意味着阴影会继续正常工作,即使为其自定义属性之一提供了无效值。它没有失败,而是恢复到其初始值 0px
。
除了类型安全之外,它还为动画打开了许多大门。CSS 语法的灵活性使得某些动画变得不可能,比如渐变。@property
在这里会很有用,因为类型化的 CSS 属性可以告知浏览器开发人员在其他过于复杂的插值中的意图。它本质上限制了可能性的范围,以至于浏览器可以为以前无法实现的样式的各个方面设置动画。
考虑下面的例子,其中使用径向渐变来制作覆盖的一部分,从而创建聚光灯聚焦效果。按下alt/opt
键时,JavaScript设置鼠标x
和y
,然后将焦点大小更改为较小的值,例如25%
,在鼠标位置创建聚光灯焦点圆:

.focus-effect {
--focal-size: 100%;
--mouse-x: center;
--mouse-y: center;
mask-image: radial-gradient(
circle at var(--mouse-x) var(--mouse-y),
transparent 0%,
transparent var(--focal-size),
black 0%
);
}
不过,渐变无法设置动画。它们对于浏览器来说太灵活和太复杂了,无法理解你希望它们如何制作动画。但是,使用@property
,可以单独设置一个属性并为其设置动画,浏览器可以轻松理解其意图。
使用这种聚焦效果的电子游戏始终会为圆设置动画,从一个大圆到一个针孔圆。下面是如何在演示中使用@property
,以便浏览器为渐变遮罩设置动画:
@property --focal-size {
syntax: '<length-percentage>';
initial-value: 100%;
inherits: false;
}
.focus-effect {
--focal-size: 100%;
--mouse-x: center;
--mouse-y: center;
mask-image: radial-gradient(
circle at var(--mouse-x) var(--mouse-y),
transparent 0%,
transparent var(--focal-size),
black 0%
);
transition: --focal-size .3s ease;
}
浏览器现在能够为渐变大小设置动画,因为我们已将修改的表面积减少到只有一个属性并设置值,以便浏览器可以智能地插入长度。
相关资源:
- Spec: https://www.w3.org/TR/css-properties-values-api-1/#at-property-rule
- MDN: https://developer.mozilla.org/docs/Web/CSS/@property
- web.dev: https://web.dev/at-property/
- Zoom demo: https://codepen.io/argyleink/pen/rNwWwor
- CSS Tricks: https://css-tricks.com/exploring-property-and-its-animating-powers/
2. 媒体查询范围
在媒体查询范围之前,CSS 媒体查询使用 min-width
和 max-width
来表达条件。它可能看起来像这样:
@media (min-width: 320px) {
…
}
在媒体查询范围之后,相同的媒体查询可能如下所示:
@media (320px >= width) {
…
}
使用 min-width
和 max-width
的 CSS 媒体查询可能如下所示:
@media (min-width: 320px) and (max-width: 1280px) {
…
}
在媒体查询范围之后,相同的媒体查询可能如下所示:
@media (320px <= width <= 1280px) {
…
}
后者看起来会比前者更清晰。由于规范的增加,开发人员将能够选择他们喜欢的,甚至可以互换使用它们。
相关资源:
- Spec: https://www.w3.org/TR/mediaqueries-5/#mq-range-context
- MDN: https://developer.mozilla.org/docs/Web/CSS/Media_Queries/Using_media_queries#syntax_improvements_in_level_4
- PostCSS plugin: https://github.com/postcss/postcss-media-minmax
3. 自定义媒体查询
在@custom-media
之前,媒体查询必须一次又一次地重复,或者依赖预处理器在构建期间基于静态变量生成正确的输出。
在@custom-media
之后,CSS允许对媒体查询进行定义别名和引用,就像自定义属性一样。
命名非常重要:它可以使目的与语法保持一致,使事物更易于共享,更易于在团队中使用。以下是一些自定义媒体查询:
@custom-media --OSdark (prefers-color-scheme: dark);
@custom-media --OSlight (prefers-color-scheme: light);
@custom-media --pointer (hover) and (pointer: coarse);
@custom-media --mouse (hover) and (pointer: fine);
@custom-media --xxs-and-above (width >= 240px);
@custom-media --xxs-and-below (width <= 240px);
现在它们已定义,我可以像这样使用其中一个:
@media (--OSdark) {
:root {
…
}
}
相关资源
- Spec: https://www.w3.org/TR/mediaqueries-5/#custom-mq
- PostCSS plugin: https://github.com/postcss/postcss-custom-media
4. 嵌套选择器
在 @nest
之前,样式表中有很多重复。当选择器很长且每个选择器都针对微小的差异时,它变得特别笨拙。所以,我们会经常使用预处理器的嵌套功能。
在 @nest
之后,重复就消失了。几乎所有支持预处理器的嵌套功能都将内置在 CSS 中。
article {
color: darkgray;
}
article > a {
color: var(--link-color);
}
/* with @nest becomes */
article {
color: darkgray;
& > a {
color: var(--link-color);
}
}
除了避免重复的代码,嵌套最重要的是样式上下文保留在一个样式块中。读者无需从一个选择器及其样式跳到另一个带有样式的选择器(示例 1),而是可以留在文章的上下文中并查看文章在其中拥有链接。
考虑一个子组件,它希望在不同的父级上下文中调整自己,而不是父组件拥有样式并更改子组件:
/* parent owns this, adjusting children */
section:focus-within > article {
border: 1px solid hotpink;
}
/* with @nest becomes */
/* article owns this, adjusting itself when inside a section:focus-within */
article {
@nest section:focus-within > & {
border: 1px solid hotpink;
}
}
@nest 总体上有助于更健康的风格组织、集中化和所有权。组件可以分组并拥有自己的样式,而不是让它们散布在其他样式块中。在这些示例中,它可能看起来很小,但为了方便和易读性,它可以产生非常大的影响。
相关资源:
- Spec: https://www.w3.org/TR/css-nesting-1/
- PostCSS plugin: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting
- The future of CSS: Nesting Selectors: https://www.bram.us/2019/03/17/the-future-of-css-nesting-selectors/
5. 样式范围
在 @scope
之前,存在许多策略,因为 CSS 中的样式在默认情况下是级联、继承和全局作用域的。CSS 的这些特性在很多方面都非常方便,但对于复杂的站点和应用程序,可能有许多不同样式的组件,级联的全局空间和性质会使样式感觉像是在泄漏。
在 @scope
之后,样式不仅可以限定在一个上下文中,就像一个类一样,它们还可以明确样式的结束位置,并且不会继续级联或继承。
在以下示例中,BEM 命名约定范围可以转换为实际意图。BEM 选择器试图将 header
元素的颜色范围限定为具有命名约定的 .card
容器。这要求header
上有这个类名,从而完成目标。使用 @scope
,无需命名约定即可在不标记header
元素的情况下完成相同的目标:
.card__header {
color: var(--text);
}
/* with @scope becomes */
@scope (.card) {
header {
color: var(--text);
}
}
下面是另一个例子,不特定于组件,更多的是关于 CSS 的全局范围性质。深色和浅色主题必须在样式表中共存,其中顺序在确定获胜风格时很重要。通常这意味着深色主题样式出现在浅色主题之后;这将浅色设置为默认样式,将深色设置为可选样式。避免与 @scope
的排序和范围之争:
@scope (.light-theme) {
a { color: purple; }
}
@scope (.dark-theme) {
a { color: plum; }
}
@scope
还允许建立样式范围的结束位置。这不能通过任何命名约定或预处理器来完成;它很特别,只有浏览器内置的 CSS 才能做到。在以下示例中,当 .media-block
的子项是 .content
的兄弟或父项时,将专门应用 img
和 .content
样式:
@scope (.media-block) to (.content) {
img {
border-radius: 50%;
}
.content {
padding: 1em;
}
}
相关资源:
- Spec: https://www.w3.org/TR/css-scoping-1/
- Explainer: https://css.oddbird.net/scope/explainer/
6. 瀑布流布局
在使用Grid实现CSS瀑布流布局之前,JavaScript是实现瀑布流布局的最佳方式,因为任何带有列或flexbox的CSS方法都会不准确地表示内容顺序。使用grid构建CSS后,将不需要JavaScript库,内容顺序也将正确。

上图使用以下 CSS 实现:
.container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: masonry;
}
相关资源:
- Spec: https://drafts.csswg.org/css-grid-3/#masonry-layout-algorithm
- MDN: https://developer.mozilla.org/docs/Web/CSS/CSS_Grid_Layout/Masonry_Layout
- Smashing Magazine: https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/
7. CSS保存数据
在 prefers-reduced-data
媒体查询之前,JavaScript 和服务器可以根据用户的操作系统或浏览器的“data saver”选项更改其行为,但 CSS 不能。
在 prefers-reduced-data
媒体查询之后,CSS 可以加入用户体验增强,并在保存数据方面发挥作用。
@media (prefers-reduced-data: reduce) {
picture, video {
display: none;
}
}
在这个媒体滚动组件中使用了前面的CSS,节省了很多资源。根据访问视口的大小,可以在页面加载上节省更多资源。当用户与媒体滚动条交互时,继续保存。这些图像上都有load="lazy"
属性,再加上CSS完全隐藏元素,这意味着永远不会对图像发出网络请求。

对于我的测试,在一个中等大小的视口上,最初加载了 40 个请求和 700kb 的资源。当用户滚动媒体选择时,会加载更多请求和资源。使用 CSS prefers-reduced-data
媒体查询,加载了 10 个请求和 172kb 的资源。这节省了半兆字节,用户甚至没有滚动任何媒体,此时没有其他请求。

这种减少数据体验的优势不仅仅是节省资源。可以看到更多标题,并且没有分散注意力的封面图片来吸引注意力。许多用户在数据保护模式下浏览,因为他们按每兆字节的数据付费——很高兴看到 CSS 能够在这里提供帮助。
相关资源:
- Spec: https://www.w3.org/TR/mediaqueries-5/#prefers-reduced-data
- MDN: https://developer.mozilla.org/docs/Web/CSS/@media/prefers-reduced-data
- GUI Challenges: https://gui-challenges.web.app/media-scroller/dist/
- Smashing Magazine: https://www.smashingmagazine.com/2021/12/core-web-vitals-case-study-smashing-magazine/#savedata-and-prefers-reduced-data
8. 滚动快照
在这些滚动快照提案之前,需要编写自己的 JavaScript 来管理轮播、滑块或图库,并且可能会很复杂,需要所有的观察者和状态管理。此外,如果不小心,自然滚动速度可能会被脚本标准化,使用户交互感觉有点不自然并且可能很笨拙。
(1)snapChanging()
浏览器一发布快照子项,就会触发此事件。这允许用户界面反映缺少快照子项和滚动条的不确定快照状态,因为它现在正在使用,并将在新的地方落地。
document.querySelector('.snap-carousel').addEventListener('snapchanging', event => {
console.log('Snap is changing', event.snappedTargetsList);
});
(2)snapChanged()
一旦浏览器捕捉到一个新的子对象,滚动条停止,就会触发此事件。这使得任何依赖于快照子对象的UI都可以更新并反映连接。
document.querySelector('.snap-carousel').addEventListener('snapchanged', event => {
console.log('Snap changed', event.snappedTargetsList);
});
(3)scroll-start
滚动并不总是从一开始就开始。考虑一下可滑动组件,其中向左或向右滑动会触发不同的事件,或者页面加载时的搜索栏最初是隐藏的,直到滚动到顶部。这个CSS属性允许开发者指定一个滚动条应该从一个特定的点开始。
:root { --nav-height: 100px }
.snap-scroll-y {
scroll-start-y: var(--nav-height);
}
(4):snap-target
这个 CSS 选择器将匹配滚动捕捉容器中当前被浏览器捕捉的元素。
.card {
--shadow-distance: 5px;
box-shadow: 0 var(--shadow-distance) 5px hsl(0 0% 0% / 25%);
transition: box-shadow 350ms ease;
}
.card:snapped {
--shadow-distance: 30px;
}
在这些滚动快照提案之后,制作滑块、轮播或图库要容易得多,因为浏览器现在为任务提供了便利,消除了观察者和滚动编排代码,有利于使用内置 API。
这些 CSS 和 JS 功能还处于早期阶段,但请留意可以帮助尽快采用和测试它们的 polyfill。
相关资源:
- Draft spec: https://drafts.csswg.org/css-scroll-snap-2/
- Explainers: https://github.com/argyleink/ScrollSnapExplainers/blob/main/css-snap-target/readme.md
- Snap demos: https://snap-gallery.netlify.app/
9. CSS状态
在 toggle()
之前,只有浏览器内置的状态才能用于样式和交互。例如,复选框输入具有 :checked
,这是一种内部管理的浏览器状态,用于 CSS 能够用于视觉更改元素的输入。
在 toggle()
之后,可以在任何元素上创建自定义状态,以便 CSS 更改和用于样式。它允许循环、定向切换等。
在以下示例中,实现了与完整列表项删除线相同的效果,但没有任何复选框元素:
<ul class='ingredients'>
<li>1 banana
<li>1 cup blueberries
...
ul>
以及相关的 CSS toggle()
样式:
li {
toggle-root: check self;
}
li:toggle(check) {
text-decoration: line-through;
}
如果你熟悉状态机,可能会注意到 toggle()
有多少交叉。这个特性将让开发人员将更多的状态构建到 CSS 中,希望能以更清晰、更语义化的方式来编排交互和状态。
相关资源:
- Draft: https://tabatkins.github.io/css-toggle/
- The Future of CSS: CSS Toggles: https://www.bram.us/2022/04/20/the-future-of-css-css-toggles/
10. 自定义选择元素
在
之前,CSS 无法使用丰富的 HTML 自定义
元素或更改选项列表的显示方式。这导致开发人员加载外部库,这些库重新创建了
图片
表情
视频