我之前写了一些关于 容器样式查询的早期想法。现在还处于早期阶段。它们已经在 CSS Containment Module Level 3 规范(目前处于编辑草案状态)中定义,但仍有一些讨论正在进行。
基本思想是我们可以定义一个容器,然后根据其计算的样式有条件地将其后代应用样式。
@container <name>? <conditions> {
/* conditional styles */
}
到目前为止,我见过的最好的例子是在内容已用斜体的情况下,从类似于 <em>
、<i>
和 <q>
的内容中删除斜体。
em, i, q {
font-style: italic; /* default UA behavior */
}
/* When the container's font-style is italic, remove italics from these elements. */
@container style(font-style: italic) {
em, i, q {
font-style: normal;
}
}
这就是总体思路。但如果你不知道,规范的编辑 Miriam Suzanne 持续维护着一组完整的 容器样式查询的个人笔记,这些笔记公开可用。它前几天更新了,我在里面花了一些时间试图理解样式查询更细微的方面。这些都是非官方的东西,但我认为我应该记下一些让我印象深刻的东西。谁知道呢?也许这是我们将来可以期待的东西!
每个元素都是一个样式容器
我们甚至不需要显式分配 container-name
或 container-type
来定义一个样式容器,因为默认情况下,所有元素都是样式容器。
因此,您在上面看到的删除斜体的示例?请注意它没有识别容器。它直接跳到使用 style()
函数的查询。那么,正在查询哪个容器呢?它将是**接收应用样式的元素的直接父级**。如果不是这样,那么它就是**优先级最高的下一个最接近的相对容器**。
我喜欢这一点。查询向上搜索匹配项,然后继续冒泡直到找到匹配条件,这非常符合 CSS 的风格。
我难以理解为什么我们可以使用基于样式的隐式容器,但在处理尺寸查询(如 size
和 inline-size
)时却不行。Miriam 对此做了很好的解释
尺寸查询需要在容器的大小、布局和样式上进行 css 包含,以防止布局循环。包含是广泛应用的侵入性操作,因此作者必须小心地控制哪些元素是(或不是)大小容器。
基于样式的查询没有相同的限制。在 CSS 中,后代样式无法影响祖先的计算样式。因此,不需要包含,并且将元素设置为 样式查询容器 不会产生任何侵入性或意外的副作用。
(强调我的)
这一切都归结于后果——就默认情况下所有元素都是样式查询容器而言,没有后果。
- 如果找到了容器:条件将针对该容器进行解析。
- 如果多个容器匹配:优先使用最接近的相对容器。
- 如果未找到匹配项:返回
unknown
。
这与 CSS 剩余部分的 “宽容”精神相同。
容器可以同时支持尺寸查询和样式查询
假设我们想要在没有显式 container-name
的情况下定义样式查询
@container style(font-style: italic) {
em {
font-style: normal;
}
}
之所以有效,是因为 所有元素都是样式容器,无论 container-type
是什么。这就是我们可以隐式查询样式并依赖最接近匹配项的原因。这完全没问题,因为同样地,在创建样式容器时,不会产生任何负面影响。
对于尺寸查询,我们必须使用显式 container-type
,但对于样式查询则不必,因为每个元素都是一个样式查询。这也意味着该容器既是样式容器,也是尺寸查询容器
.card-container {
container: card / inline-size; /* implictly a style query container as well */
}
排除容器被查询
也许我们不希望容器参与匹配过程。在这种情况下,可以在元素上设置 container-type: none
。
.some-element {
container-type: none;
}
显式样式查询容器提供对查询内容的更多控制
假设我们要编写针对 padding
的样式查询,无论我们使用的是显式命名的容器还是最接近的直接父级,都没有可靠的方法来确定最佳匹配容器。这是因为 padding
不是继承的属性。
因此,在这些情况下,我们应该使用 container-name
来显式告知浏览器可以从哪些容器中获取数据。我们甚至可以为容器提供多个显式名称,以使其匹配更多条件。
.card {
container-name: card layout theme;
}
哦,container-name
接受容器的任意数量的可选且 可重用 名称!在帮助浏览器在搜索匹配项时做出选择方面,这提供了更大的灵活性。
.theme {
container-name: theme;
}
.grid {
container-name: layout;
}
.card {
container-name: card layout theme;
}
我有点想知道,如果一个容器被忽略,这是否也可以被视为一种“回退”。
样式查询可以组合
or
和 and
运算符允许我们组合查询,以保持代码简洁
@container bubble style(--arrow-position: start start) or style(--arrow-position: end start) {
.bubble::after {
border-block-end-color: inherit;
inset-block-end: 100%;
}
}
/* is the same as... */
@container bubble style(--arrow-position: start start) {
/* etc. */
}
@container bubble style(--arrow-position: end start) {
/* etc. */
}
切换样式
容器样式查询和 正在进行的定义 toggle()
函数的工作之间有一些重叠。例如,我们可以循环遍历两个 font-style
值,例如 italic
和 normal
em, i, q {
font-style: italic;
}
@container style(font-style: italic) {
em, i, q {
font-style: normal;
}
}
很酷。但是 CSS 切换的提案建议 toggle()
函数将是一种更简单的方法
em, i, q {
font-style: toggle(italic, normal);
}
但是,除了这种二进制用例之外,toggle()
不太适用。然而,样式查询可以正常工作。Miriam 确定了三种情况下,样式查询比 toggle()
更合适
/* When font-style is italic, apply background color. */
/* Toggles can only handle one property at a time. */
@container style(font-style: italic) {
em, i, q {
background: lightpink;
}
}
/* When font-style is italic and --color-mode equals light */
/* Toggles can only evaluate one condition at a time */
@container style((font-style: italic) and (--color-mode: light)) {
em, i, q {
background: lightpink;
}
}
/* Apply the same query condition to multiple properties */
/* Toggles have to set each one individually as separate toggles */
@container style(font-style: italic) {
em, i, q {
/* clipped gradient text */
background: var(--feature-gradient);
background-clip: text;
box-decoration-break: clone;
color: transparent;
text-shadow: none;
}
}
样式查询解决了“自定义属性切换技巧”
请注意,样式查询是针对 “CSS 自定义属性切换技巧”的正式解决方案。在其中,我们设置了一个空的自定义属性 (--foo: ;
),并使用逗号分隔的回退方法,当自定义属性设置为实际值时,打开和关闭属性。
button {
--is-raised: ; /* off by default */
border: 1px solid var(--is-raised, rgb(0 0 0 / 0.1));
box-shadow: var(
--is-raised,
0 1px hsl(0 0% 100% / 0.8) inset,
0 0.1em 0.1em -0.1em rgb(0 0 0 / 0.2)
);
text-shadow: var(--is-raised, 0 -1px 1px rgb(0 0 0 / 0.3));
}
button:active {
box-shadow: var(--is-raised, 0 1px 0.2em black inset);
}
#foo {
--is-raised: initial; /* turned on, all fallbacks take effect. */
}
这超级酷,也是样式容器查询简化的许多工作。
样式查询和 CSS 生成的内容
对于由 ::before
和 ::after
伪元素的 content
属性生成的生成内容,匹配容器是生成该内容的元素。
.bubble {
--arrow-position: end end;
container: bubble;
border: medium solid green;
position: relative;
}
.bubble::after {
content: "";
border: 1em solid transparent;
position: absolute;
}
@container bubble style(--arrow-position: end end) {
.bubble::after {
border-block-start-color: inherit;
inset-block-start: 100%;
inset-inline-end: 1em;
}
}
样式查询和 Web 组件
我们可以将 Web 组件定义为一个容器,并通过样式对其进行查询。首先,我们有组件的<template>
<template id="media-host">
<article>
<div part="img">
<slot name="img">…</slot>
</div>
<div part="content">
<slot name="title">…</slot>
<slot name="content">…</slot>
</div>
</article>
</template>
然后,我们使用:host
伪元素作为容器来设置container-name
、container-type
以及一些高级属性
:host {
container: media-host / inline-size;
--media-location: before;
--media-style: square;
--theme: light;
}
<media-host>
内部的元素可以查询<media-host>
元素的参数
@container media-host style(--media-style: round) {
[part='img'] {
border-radius: 100%;
}
}
接下来呢?
再次强调,这里记录的所有内容都基于 Miriam 的笔记,这些笔记并非官方规范的替代品。但它们表明正在讨论的内容,以及未来可能出现的方向。感谢 Miriam 提供了一些正在进行的讨论,我们可以关注这些讨论,以了解最新进展。
很棒的文章!
不过,CSS
toggle()
表达式与 CSS Toggles 提案无关。后者是一个完全不同的东西,它允许管理表示状态。请参阅 https://brm.us/toggles 以了解相关说明。
啊,谢谢!这很有帮助。
不错的文章。我希望我们很快就能玩玩样式容器。