2022年将成为 CSS 最伟大的一年。无论是在功能还是合作浏览器的功能发布方面,合作目标是实现 14 个功能。
译者:CUGGZ
如有翻译不准确之处,欢迎指正!
概述
本文是在 Google IO 2022上发表的演讲的文字形式。这里不会对每个功能进行深入的讲解,而是对每个功能进行简要概述,提供广度而不是深度。
下面是这些 CSS 功能的概览:
浏览器兼容性
将这么多 CSS 功能设置为合作发布的主要原因是来自 Interop 2022 的努力,下面就来看看 Interop 2022 和 Compat 2021 分别做出了哪些努力。
1. Compat 2021
2021 年的目标是由开发人员通过调查获得的反馈来推动的,旨在稳定当前功能、改进测试套件并提高浏览器在五个功能方面的通过分数:
- sticky 定位
- aspect-ratio 尺寸
- flex 布局
- grid 布局
- transform 定位和动画
测试分数全面提高,显示出更高的稳定性和可靠性。
2. Interop 2022
今年,浏览器们齐心协力,讨论了他们打算开发的功能和优先事项。他们计划为开发者提供以下 web 功能:
- 级联层
@layer
- 颜色空间和方法
- 容器查询
<dialog>
- 表单兼容性
- 滚动
- 子网格
subgrid
- 排版
- 视口单位
- Web 兼容
2022年的新功能
毫不疑问,CSS 2022 的状态受到 Interop 2022 工作的巨大影响。
1. 级联层(@layer)
浏览器支持:
在 @layer
之前,加载样式表的顺序非常重要,因为最后加载的样式会覆盖之前加载的样式。这样开发人员就需要先加载不太重要的样式,然后再加载更重要的样式。
在@layer
之后,入口文件可以预先定义图层及其顺序。 然后,当样式加载、加载完成或已经定义时,它们可以放置在一个层中,允许保留样式覆盖的重要性,但无需精心管理加载顺序。
上图展示了级联层如何允许更自由、更开放地编写和加载过程。同时仍然根据需要维护层叠。
Chrome DevTools 有助于可视化哪些样式来自哪些图层:
相关资源:
- CSS Cascade 5 specification: www.w3.org/TR/css-casc…
- Cascade layers explainer: css.oddbird.net/layers/expl…
- Cascade layers on MDN: developer.mozilla.org/docs/Web/CS…
- Cascade Layers: developer.chrome.com/blog/cascad…
- Hello, CSS Cascade Layers: www.bram.us/2022/02/13/…
2. 子网格(subgrid)
浏览器支持:
在subgrid
之前,另一个网格中的网格无法与其父单元格或网格线对齐。每个网格布局都是独一无二的。许多设计师在他们的整个设计上放置一个网格,并不断地在其中对齐项目,这在CSS中是做不到的。
在subgrid
之后,网格的子网格可以将其父网格的列或行作为自己的列或行,并将其自身或子网格与它们对齐!
在下面的demo中,body
元素创建了一个经典的三列网格,中间列为main
,左边和右边的列称为fullbleed
。然后,body
中的每个元素, <nav>
和 <main>
通过设置 grid-template-columns: subgrid
来采用 body
中的命名行。
body {
display: grid;
grid-template-columns:
[fullbleed-start]
auto [main-start] min(90%, 60ch) [main-end] auto
[fullbleed-end]
;
}
body > * {
display: grid;
grid-template-columns: subgrid;
}
最后,<nav>
或 <main>
的子级可以使用 fullbleed
和main
列和行对齐或调整自己的大小。
.main-content {
grid-column: main;
}
.fullbleed {
grid-column: fullbleed;
}
目前,Firefox Devtools 可以帮助我们查看子网格。 在下图中,父网格和子网格已重叠。 它现在类似于设计师对布局的思考方式。
在 Devtools 的“Elements”面板中,可以看到哪些元素是grid
和subgrid
,这对调试或验证布局是非常有用的。
相关资源:
- Spec: www.w3.org/TR/css-grid…
- MDN: developer.mozilla.org/docs/Web/CS…
- Practical CSS Subgrid Video Tutorials: www.bram.us/2021/11/04/…
3. 容器查询
在 @container
之前,网页的元素只能响应整个视口的大小。 这对于大型布局非常有用,但对于外部容器不是整个视口的小型布局,布局不可能进行相应调整。
在@container
之后,元素可以响应父容器的大小或样式!唯一需要注意的是,容器必须将自己声明为可能的查询目标,这是一个很小的要求,可以带来很大的好处。
/* 新建一个容器 */
.day {
container-type: inline-size;
container-name: calendar-day;
}
这些样式使下图中的 Mon、Tues、Wed、Thurs 和 Fri 列能够被事件元素查询。
以下CSS用于查询 calendar-day
容器大小,然后调整布局和字体大小:
@container calendar-day (max-width: 200px) {
.date {
display: block;
}
.date-num {
font-size: 2.5rem;
display: block;
}
}
下面是另一个示例:一个book组件根据其拖动到的列中的可用空间进行调整:
相关资源:
- Spec: https:/www.w3.org/TR/css-cont…
- Explainer: css.oddbird.net/rwd/query/e…
- MDN: developer.mozilla.org/docs/Web/CS…
- The new responsive: web.dev/new-respons…
- Calendar demo by Una: codepen.io/una/pen/Rwo…
- **Awesome container queries: ** github.com/sturobson/A…
- Designcember: web.dev/how-we-buil…
- Container Queries are Actually Coming / Say Hello To CSS Container Queries: www.bram.us/2021/04/14/…
4. accent-color
浏览器支持:
在accent-color
之前,当我们想要一个与品牌颜色匹配的表单时,最终可能需要复杂的库或CSS解决方案,随着时间的推移,这些解决方案会变得难以管理。虽然它们提供了所有选项,并希望包括可访问性,但选择使用内置组件或采用自己的组件会变得单调乏味,无法继续选择。
在accent-color
之后,一行CSS为内置组件带来了主题颜色,除了色调之外,浏览器还会智能地为组件的辅助部分选择适当的对比色,并适应系统配色方案(亮暗)。
/* 为所有颜色着色 */
:root {
accent-color: hotpink;
}
/* 为一个元素着色 */
progress {
accent-color: indigo;
}
相关资源
- Spec: www.w3.org/TR/css-ui-4…
- MDN: developer.mozilla.org/docs/Web/CS…
- web.dev: web.dev/accent-colo…
- Tint User-Interface Controls with CSS accent-color:www.bram.us/2021/08/23/…
6. Color level 4 and 5
在过去的几十年里,web 一直由 sRGB 主导,但在高清显示器和预先配备 OLED 或 QLED 屏幕的移动设备不断扩大的数字世界中,sRGB 是不够的。 此外,需要适应用户偏好的动态页面,并且颜色管理已成为设计师、设计系统和代码维护人员日益关注的问题。
CSS有许多新的颜色功能和空间(不过不是在2022年):
- 达到显示器高清色彩功能的色彩。
- 与意图相匹配的色彩空间,例如感知一致性。
- 渐变的颜色空间会显著改变插值结果。
- 颜色功能可帮助你混合和对比,并选择在哪个空间进行工作。
在所有这些颜色特性出现之前,设计系统需要预先计算出适当的对比色,并确保调色板具有适当的活力,而预处理器或JavaScript则起到了重要作用。
在完成了所有这些颜色功能之后,浏览器和CSS可以动态、及时地完成所有工作。CSS可以进行编排和计算,而不是向用户发送很大的CSS和JavaScript来启用主题和数据可视化颜色。CSS还可以更好地在使用前检查支持情况,或者优雅地处理回退。
@media (dynamic-range: high) {
.neon-pink {
--neon-glow: color(display-p3 1 0 1);
}
}
@supports (color: lab(0% 0 0)) {
.neon-pink {
--neon-glow: lab(150% 160 0);
}
}
(1)hwb()
浏览器支持:
HWB代表色调、白度和黑度。它表现为一种对人类友好的表达颜色的方式,因为它只是一种色调,加上一定量的白色或黑色以使其变亮或变暗。
:root {
--hwb-swatch-1: hwb(200 75% 0%);
--hwb-swatch-2: hwb(200 50% 25%);
--hwb-swatch-3: hwb(200 25% 50%);
--hwb-swatch-4: hwb(200 0% 75%);
--hwb-swatch-5: hwb(200 0% 90%);
}
.swatch:nth-of-type(1) {
background: var(--hwb-swatch-1);
}
.swatch:nth-of-type(2) {
background: var(--hwb-swatch-2);
}
.swatch:nth-of-type(3) {
background: var(--hwb-swatch-3);
}
.swatch:nth-of-type(4) {
background: var(--hwb-swatch-4);
}
.swatch:nth-of-type(5) {
background: var(--hwb-swatch-5);
}
* {
box-sizing: border-box;
margin: 0;
}
html {
block-size: 100%;
}
body {
min-block-size: 100%;
font-family: system-ui, sans-serif;
display: grid;
}
效果如下:
使用此颜色函数会产生来自 sRGB 颜色空间的颜色,与 HSL 和 RGB 相同。 就 2022 年的新意而言,这并没有给你带来新的色彩,但它可能会让语法和心智模型的粉丝更容易完成一些任务。
相关资源:
- Spec: www.w3.org/TR/css-colo…
- MDN: developer.mozilla.org/docs/Web/CS…
- hwb() – a color notation for humans?: www.stefanjudis.com/blog/hwb-a-…
(2)颜色空间
颜色的表示方式是通过颜色空间完成的。 每个颜色空间都为使用颜色提供了各种功能和权衡。 有些人可能会将所有鲜艳的颜色打包在一起; 有些人可能会先根据它们的亮度排列它们。
2022年, SS 将提供 10 个新的颜色空间,每个都有独特的功能来帮助设计师和开发人员显示、挑选和混合颜色。 以前,sRGB 是处理颜色的唯一选项,但现在 CSS 释放了新的潜力和新的默认颜色空间 LCH。
(3)color-mix()
浏览器支持:
在 color-mix()
之前,开发人员和设计人员需要像 Sass 这样的预处理器在浏览器看到颜色之前混合颜色。大多数颜色混合功能也没有提供指定在哪个颜色空间中进行混合的选项,有时会导致结果混乱。
在 color-mix()
之后,开发人员和设计人员可以在浏览器中混合颜色以及所有其他样式,而无需运行构建过程或包括 JavaScript。 此外,他们可以指定在哪个颜色空间中进行混合,或者使用 LCH 的默认混合颜色空间。
通常,主题颜色被用作基础颜色,并从中创建变体,例如悬停样式的浅色或深色。下面是color-mix()
例子:
.color-mix-example {
--brand: #0af;
--darker: color-mix(var(--brand) 25%, black);
--lighter: color-mix(var(--brand) 25%, white);
}
如果你想在不同的颜色空间中混合这些颜色,例如 srgb,请更改它:
.color-mix-example {
--brand: #0af;
--darker: color-mix(in srgb, var(--brand) 25%, black);
--lighter: color-mix(in srgb, var(--brand) 25%, white);
}
在 2022 年享受在你的样式表中混合各种颜色空间的颜色!
相关资源:
- Spec: www.w3.org/TR/css-colo…
- MDN: developer.mozilla.org/docs/Web/CS…
- Theming demo: codepen.io/argyleink/p…
- Another theming demo: codepen.io/argyleink/p…
- Create a color theme with CSS Relative Color Syntax, CSS color-mix(), and CSS color-contrast(): www.bram.us/2021/04/28/…
(4)color-contrast()
浏览器支持:
在 color-contrast()
之前,样式表作者需要提前了解可访问的颜色。 通常,调色板会在颜色样本上显示黑色或白色文本,以向颜色系统的用户指示需要哪种文本颜色才能与该样本进行适当对比。
在 color-contrast()
之后,样式表作者可以将任务完全转移到浏览器。 你不仅可以使用浏览器自动选择黑色或白色,还可以为其提供设计系统适用的颜色列表,并让其选择第一个通过所需对比度的颜色。
这是 HWB 调色板集 demo 的截图,其中文本颜色由浏览器根据样本颜色自动选择:
语法的基本内容如下所示,其中灰色被传递给函数,浏览器确定黑色或白色是否具有最大对比度:
color: color-contrast(gray);
该函数还可以使用颜色列表进行自定义,它将从中选择对比度最高的颜色:
color: color-contrast(gray vs indigo, rebeccapurple, hotpink);
最后,如果最好不要从列表中选择对比度最高的颜色,可以提供目标对比度,并选择第一种通过该对比度的颜色:
color: color-contrast(
var(--bg-blue-1)
vs
var(--text-lightest), var(--text-light), var(--text-subdued)
to AA /* 4.5 could also be passed */
);
这个函数不仅可以用于文本颜色,但这将是它的主要用途。 想一想,一旦选择适当的对比色内置到 CSS 语言本身中,那么交付可访问且易读的界面将变得多么容易。
相关资源:
(5)相对颜色语法
浏览器支持:
在使用相对颜色语法之前,为了计算颜色并进行调整,需要将颜色通道单独放置到自定义属性中。这一限制还使HSL成为处理颜色的主要颜色函数,因为色调、饱和度或亮度都可以通过 calc()
直接调整。
在相对颜色语法之后,任何空间中的任何颜色都可以解构、修改并作为颜色返回,所有这些都可以在CSS的一行中完成。在任何所需的颜色空间中,都无法对HSL操作进行更多限制,并且需要创建更少的自定义属性来简化操作。
在以下语法示例中,提供了一个基本十六进制,并相对于它创建了两种新颜色。 第一种颜色 --absolute-change
在 LCH 中从基色创建新颜色,然后继续将基色的亮度替换为 75%,保持色度 (c) 和色相 (h)。 第二种颜色 --relative-change
在 LCH 中从基色创建新颜色,但这一次将色度 (c) 降低了 20%。
.relative-color-syntax {
--color: #0af;
--absolute-change: lch(from var(--color) 75% c h);
--relative-change: lch(from var(--color) l calc(c-20%) h);
}
它类似于混合颜色,但它更像是改变而不是混合。 你可以从另一种颜色创建一种颜色,访问所用颜色函数命名的三个通道值,并有机会调整这些通道。 总而言之,这是一种非常酷且强大的颜色语法。
在下面的例子中,使用了相对颜色语法来创建基色的更亮和更暗的变体,并使用 color-contrast()
来确保标签具有适当的对比度:
此函数也可用于调色板生成。 这是一个例子,其中整个调色板是根据提供的基色生成的。 这套 CSS 支持所有不同的调色板,每个调色板只是提供不同的基色。
:root {
--_color-base: #339af0;
--color-0: lch(from var(--_color-base) 98% 10 h);
--color-1: lch(from var(--_color-base) 93% 20 h);
--color-2: lch(from var(--_color-base) 85% 40 h);
--color-3: lch(from var(--_color-base) 75% 46 h);
--color-4: lch(from var(--_color-base) 66% 51 h);
--color-5: lch(from var(--_color-base) 61% 52 h);
--color-6: lch(from var(--_color-base) 55% 57 h);
--color-7: lch(from var(--_color-base) 49% 58 h);
--color-8: lch(from var(--_color-base) 43% 55 h);
--color-9: lch(from var(--_color-base) 39% 52 h);
--color-10: lch(from var(--_color-base) 32% 48 h);
--color-11: lch(from var(--_color-base) 25% 45 h);
--color-12: lch(from var(--_color-base) 17% 40 h);
--color-13: lch(from var(--_color-base) 10% 30 h);
--color-14: lch(from var(--_color-base) 5% 20 h);
--color-15: lch(from var(--_color-base) 1% 5 h);
}
到现在为止,希望你可以看到色彩空间和不同的色彩函数如何根据它们的优点和缺点用于不同的目的。
相关资源:
- Spec: www.w3.org/TR/css-colo…
- Palettes: codepen.io/web-dot-dev…
- Variants: codepen.io/web-dot-dev…
(6)渐变颜色空间
在渐变颜色空间之前,sRGB 是使用的默认颜色空间。 sRGB 通常是可靠的,但确实有一些弱点,例如灰色死区。
在渐变颜色空间之后,告诉浏览器使用哪个颜色空间进行颜色插值。 这使开发人员和设计人员能够选择他们喜欢的渐变。 默认色彩空间也更改为 LCH 而不是 sRGB。
语法添加在渐变方向之后,使用新的in
语法,并且是可选的:
background-image: linear-gradient(
to right in hsl,
black, white
);
background-image: linear-gradient(
to right in lch,
black, white
);
这是从黑色到白色的基本且必不可少的渐变。 查看每个颜色空间中的结果范围。 有些更早达到深黑色,有些更晚达到白色。
在下一个示例中,黑色转换为蓝色,因为它是渐变的已知问题空间。 大多数颜色空间在颜色插值期间会逐渐变为紫色。或者,当颜色在其颜色空间内从 A 点移动到 B 点时。由于渐变将从 A 点到 B 点采用直线,因此色彩空间的形状变化极大地改变了路径沿途的停止点。
okLCH 和 okLAB 是专门的色彩空间,可以解释各种变化,比如这个变成紫色的,这使得它们对于渐变特别准确。
相关资源:
- Spec: drafts.csswg.org/css-images-…
- Codepen comparing gradients: codepen.io/argyleink/p…
- Observable notebook: observablehq.com/@argyleink/…
7. inert
浏览器支持:
在 inert
之前,将用户的注意力引导到需要立即关注的页面或应用程序区域是一种很好的做法。 这种引导式焦点策略被称为焦点捕获,因为开发人员会将焦点置于交互空间中,监听焦点更改事件,如果焦点离开交互空间,则强制返回。使用键盘或屏幕阅读器的用户会被引导回到互动空间,以确保在继续完成之前的任务。
在inert
之后,不需要设置陷阱,因为你可以冻结或保护页面或应用程序的整个部分。当文档的这些部分处于惰性状态时,单击和焦点更改尝试根本不可用。你也可以把它想象成守卫而不是陷阱,惰性分子不想让你待在某个地方,而是让其他地方不可用。
JavaScript alert()
函数就是一个很好的例子:
请注意,在调用 alert()
之前,页面是如何通过鼠标和键盘访问的。显示警报对话框弹出窗口后,页面的其余部分将被冻结或不活动。用户的注意力放在警报对话框中,无处可去。一旦用户交互并完成警报功能请求,页面将再次交互。inert
使开发人员能够轻松实现同样的引导焦点体验。
下面示例来展示它是如何工作的:
<body>
<div class="modal">
<h2>Modal Title</h2>
<p>...<p>
<button>Save</button>
<button>Discard</button>
</div>
<main inert>
<!-- cannot be keyboard focused or clicked -->
</main>
</body>
对话框是一个很好的例子,但inert
也有助于诸如滑出式侧边菜单用户体验之类的事情。 当用户滑出侧边菜单时,让鼠标或键盘与后面的页面交互是不合适的;相反,当显示侧边菜单时,使页面处于inert
状态,现在用户必须关闭或在该侧边菜单中导航,并且永远不会发现自己在打开菜单的页面中迷失在其他地方。
相关资源:
- Spec: html.spec.whatwg.org/multipage/i…
- MDN: developer.mozilla.org/docs/Web/AP…
- Introducing inert: developer.chrome.com/blog/inert/
8. COLRv1 字体
在 COLRv1 字体之前,Web 有 OT-SVG 字体,这也是一种开放格式,用于渐变字体、内置颜色和效果。不过,它们可能会变得非常大,虽然它们允许编辑文本,但定制的空间不大。
在 COLRv1 字体之后,Web 具有更小的占用空间、矢量可缩放、可重新定位、渐变功能和混合模式驱动的字体,它们接受参数来自定义每个用例的字体或匹配主题。
下面是 Chrome Developer 博客文章中有关表情符号的示例。 也许你已经注意到,如果你放大表情符号的字体大小,它就不会保持清晰。 这是一个图像,而不是矢量艺术。 使用 COLRv1 字体,表情符号既矢量又漂亮:
图标字体可以用这种格式做一些惊人的事情,提供自定义的双色调调色板等等。 加载 COLRv1 字体就像任何其他字体文件一样:
@import url(https://fonts.googleapis.com/css2?family=Bungee+Spice);
自定义 COLRv1 字体使用 @font-palette-values
完成的,这是一个特殊的 CSS 规则,用于将一组自定义选项分组和命名为一个包以供以后参考。 指定自定义名称就像自定义属性一样,以 --
开头:
@import url(https://fonts.googleapis.com/css2?family=Bungee+Spice);
@font-palette-values --colorized {
font-family: "Bungee Spice";
base-palette: 0;
override-colors: 0 hotpink, 1 cyan, 2 white;
}
使用 --colorized
作为自定义的别名,最后一步是将调色板应用于使用颜色字体系列的元素:
@import url(https://fonts.googleapis.com/css2?family=Bungee+Spice);
@font-palette-values --colorized {
font-family: "Bungee Spice";
base-palette: 0;
override-colors: 0 hotpink, 1 cyan, 2 white;
}
.spicy {
font-family: "Bungee Spice";
font-palette: --colorized;
}
随着越来越多的可变字体和彩色字体的出现,网页排版正朝着丰富的定制和创造性表达的方向发展。
相关资源:
- Github: github.com/googlefonts…
- Chrome Developers: developer.chrome.com/blog/colrv1…
- BlinkOn developer explainer video: www.youtube.com/watch?v=Bmq…
9. 视口单位
在新的视口变体之前,web提供了物理单位来帮助适应视口。 有高度、宽度、最小尺寸 (vmin) 和最大边 (vmax)。 这些对很多事情都有效,但移动浏览器带来了复杂性。
在移动设备上,加载页面时,会显示带有 url 的状态栏,此栏会占用部分视口空间。 在几秒钟和一些交互之后,状态栏可能会滑开,以便为用户提供更大的视口体验。 但是当该条滑出时,视口高度发生了变化,任何 vh 单位都会随着目标大小的变化而移动和调整大小。 在后来的几年里,vh 单位特别需要决定要使用两种视口尺寸中的哪一种,因为这会在移动设备上造成不和谐的视觉布局问题。 已确定 vh 将始终代表最大的视口。
.original-viewport-units {
height: 100vh;
width: 100vw;
--size: 100vmin;
--size: 100vmax;
}
在新的视口变体之后,可以使用小型、大型和动态视口单位,并在物理视口单元的基础上添加逻辑等效单位。 这个想法是让开发人员和设计人员能够选择他们想要在给定场景中使用的单位。当状态栏消失时,也许可以稍微改变一下不协调的布局,这样就可以不用担心使用dvh(动态视口高度)。
以下是新视口变体提供的所有新视口单位选项的完整列表:
/* 高度视口单位 */
.new-height-viewport-units {
height: 100vh;
height: 100dvh;
height: 100svh;
height: 100lvh;
block-size: 100vb;
block-size: 100dvb;
block-size: 100svb;
block-size: 100lvb;
}
/* 宽度视口单位 */
.new-width-viewport-units {
width: 100vw;
width: 100dvw;
width: 100svw;
width: 100lvw;
inline-size: 100vi;
inline-size: 100dvi;
inline-size: 100svi;
inline-size: 100lvi;
}
/* 最小视口单位 */
.new-min-viewport-units {
--size: 100vmin;
--size: 100dvmin;
--size: 100svmin;
--size: 100lvmin;
}
/* 最大视口单位 */
.new-max-viewport-units {
--size: 100vmax;
--size: 100dvmax;
--size: 100svmax;
--size: 100lvmax;
}
希望这些将为开发人员和设计人员提供实现其视口响应式设计所需的灵活性。
相关资源:
- Spec: drafts.csswg.org/css-values-…
- The Large, Small, and Dynamic Viewports: www.bram.us/2021/07/08/…
10. :has()
浏览器支持:
在 :has()
之前,选择器的主体总是在最后。 例如,这个选择器的主体是一个列表项:ul > li。 伪选择器可以改变选择器,但它们不会改变主体:ul > li:hover
或 ul > li:not(.selected)
。
在 :has() 之后,元素树中较高的主体可以保留为主体,同时提供有关子项的查询:ul:has(> li)
。 很容易理解 :has(
) 是如何获得“父选择器”的通用名称的,因为在这种情况下,选择器的主体现在是父级。
这是一个基本语法示例,其中 .parent
类仍然是主体,但仅在子元素具有 .child
类时才被选中:
.parent:has(.child) {...}
这是一个示例,其中 <section>
元素是主体,但选择器仅在其中一个子元素具有 :focus-visible
时才匹配:
section:has(*:focus-visible) {...}
:has()
选择器开始成为一个神奇的实用工具,因为实际用例变得更加明显。例如,当前无法在包装图像时选择<a>
标签,因此很难确定锚定标记在该用例中如何更改其样式。可以使用 :has()
实现:
a:has(> img) {...}
这些都是 :has()
看起来像父选择器的例子。 如果图片有 <figcaption>
,请考虑 <figure>
元素内图像的用例和调整图像的样式。 在以下示例中,选择带有 figcaptions
的图像,然后选择该上下文中的图像。 使用:has()
不会改变主体,因为我们的目标是图像而不是数字:
figure:has(figcaption) img {...}
使用 @supports
及其 selector()
函数使检查支持变得简单,该函数在使用之前测试浏览器是否支持该语法:
@supports (selector(:has(works))) {
/* safe to use :has() */
}
相关资源:
- Spec: www.w3.org/TR/selector…
- MDN: developer.mozilla.org/docs/Web/CS…
- The CSS :has() selector is way more than a "parent selector": www.bram.us/2021/12/21/…
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 将被忽略,因为它不是 <length>
。 这意味着阴影会继续正常工作,即使为其自定义属性之一提供了无效值。 它没有失败,而是恢复到其初始值 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: www.w3.org/TR/css-prop…
- MDN: developer.mozilla.org/docs/Web/CS…
- web.dev: web.dev/at-property…
- Zoom demo: codepen.io/argyleink/p…
- CSS Tricks: css-tricks.com/exploring-p…
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: www.w3.org/TR/mediaque…
- MDN: developer.mozilla.org/docs/Web/CS…
- PostCSS plugin: github.com/postcss/pos…
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: www.w3.org/TR/mediaque…
- PostCSS plugin: github.com/postcss/pos…
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: www.w3.org/TR/css-nest…
- PostCSS plugin: github.com/csstools/po…
- The future of CSS: Nesting Selectors: www.bram.us/2019/03/17/…
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: www.w3.org/TR/css-scop…
- Explainer: css.oddbird.net/scope/expla…
6. 瀑布流布局
在使用Grid实现CSS瀑布流布局之前,JavaScript是实现瀑布流布局的最佳方式,因为任何带有列或flexbox的CSS方法都会不准确地表示内容顺序。使用grid构建CSS后,将不需要JavaScript库,内容顺序也将正确。
上图使用以下 CSS 实现:
.container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: masonry;
}
相关资源:
- Spec: drafts.csswg.org/css-grid-3/…
- MDN: developer.mozilla.org/docs/Web/CS…
- Smashing Magazine: www.smashingmagazine.com/native-css-…
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: www.w3.org/TR/mediaque…
- MDN: developer.mozilla.org/docs/Web/CS…
- GUI Challenges: gui-challenges.web.app/media-scrol…
- Smashing Magazine: www.smashingmagazine.com/2021/12/cor…
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: drafts.csswg.org/css-scroll-…
- Explainers: github.com/argyleink/S…
- Snap demos: 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: tabatkins.github.io/css-toggle/
- The Future of CSS: CSS Toggles: www.bram.us/2022/04/20/…
10. 自定义选择元素
在 <selectmenu>
之前,CSS 无法使用丰富的 HTML 自定义 <option>
元素或更改选项列表的显示方式。 这导致开发人员加载外部库,这些库重新创建了 <select>
的大部分功能,这最终导致了大量工作。
在 <selectmenu>
之后,开发人员可以为选项元素提供丰富的 HTML,并根据需要对其进行样式设置,同时仍然满足可访问性要求并提供语义 HTML。
在以下示例中,取自 <selectmenu>
解释器页面,创建了一个带有一些基本选项的新选择菜单:
<selectmenu>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectmenu>
CSS可以针对元素的各个部分并设置其样式:
.my-select-menu::part(button) {
color: white;
background-color: red;
padding: 5px;
border-radius: 5px;
}
.my-select-menu::part(listbox) {
padding: 10px;
margin-top: 5px;
border: 1px solid red;
border-radius: 5px;
}
相关资源:
11. 定位
在 anchor()
之前,绝对位置和相对位置是为开发人员提供的位置策略,可以让子元素在父元素中移动。
在 anchor(
) 之后,开发人员可以将元素定位到其他元素,无论它们是否是子元素。 它还允许开发人员指定要定位的边缘,以及创建元素之间位置关系的其他细节。
相关资源:
- Explainer: github.com/MicrosoftEd…