你需要知道的现代 CSS 技巧(2024 年春季版)
我编写这份可加入书签的指南的目的,是提供一份 CSS 最近新增功能的清单(坦率地说:令人难以置信)。这份清单并没有硬性标准,只是因为这些东西都相当新,而且我感觉很多人都不知道这些东西。或者说,即使他们知道,也不甚了解,他们可能需要一个通俗易懂的解释,说明这是什么,为什么他们应该关心,以及一些参考代码。也许你就是这样的人。
我希望我们能在这些功能上加强集体记忆。就像我说的,”即使你知道这些东西,也需要时间来建立肌肉记忆”。
这些东西的语法、细节和细微差别比我在这里介绍的要多得多。我想让你知道什么是可能的,参考最基本的用法和语法,然后在需要时再深入研究。
容器查询(大小)
什么是大小容器查询(Container Queries)?
通过容器查询Container Queries,您可以编写适用于容器元素子元素的样式,前提是该容器符合特定的媒体条件,通常是宽度测量值。
<div class="element-wrap"> <div class="element"> </div> </div>
.element-wrap { container: element / inline-size; } @container element (min-inline-size: 300px) { .element { display: flex; gap: 1rem; } }
什么时候应该使用它?
如果你曾经想过我希望我能根据这个元素的大小来决定样式,而不是像 @media 查询强迫我做的那样,根据整个页面的大小来决定样式。那么 @container 查询就是为你准备的!在设计系统或大量基于组件的网站上工作的人,可能大多会使用容器查询来根据大小对事物进行样式设置,因为整个页面的大小并不能代表组件的大小。
浏览器支持
Browser Support | Full |
Progressive Enhancement? | Potentially — if it’s not critical what you are styling, then yes. |
Polyfillable | Yes |
简单使用演示
使用中间的调节器,可以看到日历根据空间大小改变布局。它本身有三个断点。
容器查询(样式)
什么是样式容器查询?
容器样式查询允许您在给定自定义属性具有给定值时应用样式。
.container { --variant: 1; &.variant2 { --variant: 2; } } @container style(--variant: 1) { button { } /* You can't style .container, but can select inside it */ .other-things { } } @container style(--variant: 2) { button { } .whatever { } }
什么时候需要用到?
你是否曾经想要在 CSS 中使用一个 mixin?比如,你设置了一个属性,却得到了多个属性。Sass 使得 mixins 变得相当流行。你可以通过样式容器查询来做到这一点。不过,就像 Sass 有了变量之后,CSS 变量变得更强大、更有用一样,样式容器查询也可能会变得更强大、更有用,因为它们遵守级联,可以进行计算等等。
浏览器支持
Browser Support | ✅ Chrome ‘n’ Friends 🔜 Safari ❌ Firefox |
Progressive Enhancement? | Potentially — It depends on what you’re doing with the styles, but let’s say not really. |
Polyfillable | No |
简单使用演示
容器单位
什么是容器单位?
容器单位(字面意思是单位,如 px
、rem
或 vw
)允许你根据容器元素的当前大小来设置事物的大小。就像视口单位 1vw
是浏览器窗口宽度的 1%,1cqw
是容器宽度的 1%
(不过我建议你使用 cqi
,即 “逻辑等价物”,意为 “内联方向”)。
容器单位包括 cqw
(”容器查询宽度”)、cqh
(”容器查询高度”)、cqi
(”容器查询内联方向”)、cqb
(”容器查询块”)、cqmin
(cqi
和 cqb
中较小的值)和 cqmax
(cqi
和 cqb
中较大的值)。
什么时候需要用到它们?
如果觉得元素中任何东西的大小应该基于容器的当前大小,那么容器单位基本上是唯一的方法。排版就是一个例子。一个典型的卡片(Card)元素在渲染时可能需要更大的标题文字,而不需要添加类名来控制。(我是这个演示的粉丝)相对来说,即使是容器查询也是很笨拙的。
浏览器支持
Browser Support | Full |
Progressive Enhancement? | Yes — you could list a declaration using fallback units right before the declaration using container query units. |
Polyfillable | Yes |
基本用法演示
该元素不仅对字体大小使用了容器查询单位,还对 padding
、border
和 margin
使用了容器查询单位。
:has() 伪选择器
什么是 :has() 选择器?
通过 :has()
选择器,我们可以通过子元素来查询父元素。
figure:has(figcaption) { border: 1px solid black; padding: 0.5rem; }
什么时候需要用到它?
如果你曾想在 CSS 中使用 “父级 “选择器,:has()
就可以做到,但它的功能远不止于此,因为一旦你选择了想要的父级,就可以再次向下钻取。Jhey Tompkins 曾称其为 “家族选择器”,这是一种很好的思考方式。当一个元素内部 “没有 “匹配的元素时,你还可以将它与 :not()
结合起来,建立一个选择器。
浏览器支持
Browser Support | Full |
Progressive Enhancement? | Depends on what you’re doing with the styles, but let’s say not really. |
Polyfillable | For the JavaScript side only |
基本用法演示
视图转换
什么是视图转换?
视图转换有两种类型:
- 同页面过渡(需要 JavaScript)
- 多页面转换(仅 CSS)
它们都很有用。同页面转换涉及在页面未发生变化的情况下改变 DOM 的动画,如列表排序。多页面转换用于在页面加载之间对元素进行动画处理,比如将视频缩略图转换为视频元素。这是同页过渡的基本语法:
if (!document.startViewTransition) { updateTheDOM(); } else { document.startViewTransition(() => updateTheDOM()); }
对于多页面转换:您需要使用此元标签:
<meta name="view-transition" content="same-origin">
然后,任何想要在页面之间过渡的元素,都要确保在输出页面和输入页面的样式中应用完全唯一的视图过渡名称。
什么时候需要使用它?
如果一个元素移动到一个新的位置,而不是立即出现在那里,用户就能更好地理解界面。有一种动画概念叫做 “tweening”,即根据开始和结束状态自动创建动画。视图转换本质上就是过渡。您可以控制动画的某些方面,但大部分动画都是根据 DOM 的起始和结束状态自动创建的,而您不必对动画的细节做出特别规定。
浏览器支持
Browser Support | ✅ Chrome ‘n’ Friends ❌ Safari ❌ Firefox |
Progressive Enhancement? | Yes — the transitions can just not run, or you could provide a fallback animation. |
Polyfillable | No |
基本用法演示
这是一个同页面视图转换的示例:
嵌套
什么是嵌套?
嵌套是一种编写 CSS 的方式,它允许你在现有规则集中编写额外的选择器。
.card { padding: 1rem; > h2:first-child { margin-block-start: 0; } footer { border-block-start: 1px solid black; } }
何时需要用到它们?
嵌套主要是为了方便 CSS 的编写,但事实上,它可以将相关的 CSS 很好地组合在一起,让你不必重复编写选择器,这意味着可以避免错误,让 CSS 更容易阅读。嵌套 CSS 也可能是一种 “杀鸡用牛刀”,因为它可能会鼓励以不必要的方式编写与 HTML 嵌套相匹配的 CSS,从而增加了 CSS 的特殊性,降低了某些 CSS 的可重用性。
.card { container: card / inline-size; display: grid; gap: 1rem; @container (min-inline-size: 250px) { gap: 2rem; } }
与 Sass 式嵌套的唯一主要区别是,不能直接组合 &。
.card { body.home & { /* totally fine */ } & .footer { /* totally fine, don't even need the & */ &__big { /* nope, can't do this */ } }
浏览器支持
Browser Support | Full |
Progressive Enhancement? | No |
Polyfillable | You could use a processor like LightningCSS, Sass, Less, etc. |
滚动驱动动画
什么是滚动驱动动画?
任何与元素(通常是页面本身)滚动相关的动画现在都可以在 CSS 中完成,而无需在 JavaScript 中绑定 DOM 滚动事件。它们有两种类型:
- 元素的滚动进度。(animation-timeline: scroll())
- 元素在元素中的当前可视位置。(animation-timeline: view())
何时需要使用它们?
想象一下,当用户向下滚动页面时,阅读进度指示条会从 0% 填满到 100%。这可以通过移动元素的背景位置,使其与页面的整体滚动位置保持一致的动画来实现。用 CSS 而不是 JavaScript 来实现这一功能对性能很有好处。
滚动驱动动画的另一个主要用例是在元素进入(或离开!)视口时运行动画。您可以控制很多细节,例如根据元素的可见度来确定动画的开始和结束时间。
浏览器支持
Browser Support | ✅ Chrome ‘n’ Friends ❌ Safari 🔜 Firefox |
Progressive Enhancement? | Yes — these effects tend to be visual flair, not required functionality. |
Polyfillable | Yes |
基本用法示例
这是我们研究图像缩放和页面滚动时的演示。
锚定定位
什么是锚点定位?
锚点定位允许你将项目放置在另一个元素的相对位置。这样说似乎很明显,但其实就是这样。你可以将一个元素声明为锚点,并为其命名,然后将元素定位到锚点的顶部/右侧/底部/左侧(或中间,或逻辑等价物)。
什么时候使用它们?
一旦你可以自由地使用这个功能,你就不必太在意元素在 DOM 中的精确定位了(除了可访问性方面的考虑)。按照现在的方式,你想相对于另一个元素进行定位的元素必须是一个子元素,而且必须有一个定位上下文。这就决定了元素在 DOM 中的位置,无论这样做是否合理。
主要用例将是工具提示和自定义上下文菜单。
浏览器支持
rowser Support | 🔜 Chrome ‘n’ Friends ❌ Safari ❌ Firefox |
Progressive Enhancement? | Possibly — if you can tolerate a totally different position for elements. |
Polyfillable | Yes |
基本使用示例
在我发布这篇文章时,它只适用于启用了 “实验性网络平台功能 “标志的 Chrome Canary。
scope
什么是范围 CSS?
CSS 中的作用域以 @scope at-rule 的形式存在,它声明 CSS 块仅适用于给定的选择器。此外,还可选择停止应用于另一个给定的选择器。
什么时候需要用到它?
您也可以通过应用一个类并在该类中嵌套来对 CSS 进行范围化。不过,@scope 有一些小技巧,可以让它变得更有趣。甜甜圈范围 “选项是它的一个独特功能:
@scope (.card) to (.markdown-output) { h2 { background: tan; /* stop doing this when we get to the Markdown */ } }
另一个有用的功能是更合理的邻近样式。这一点解释起来有点麻烦,但一旦你看到了,就再也看不到了。考虑一下主题。你有一个 .dark 选择器和一个 .light 选择器。如果你只在整个页面中使用一个,那就没问题,但如果你最终嵌套了它们,因为它们具有相同的特异性,那么无论你后来定义了哪一个,从技术上讲都会优先级更高一些,即使没有意义,也会胜出一筹。最简单的例子:
.purple-paragraphs p { color: purple; } .red-paragraphs p { color: red; }
<div class="purple-paragraphs"> <div class="red-paragraphs"> <div class="purple-paragraphs"> <p>some text</p> </div> </div> </div>
你可能会认为其中的段落元素是紫色的,但实际上它是红色的。这就很尴尬了,不过可以用 @scope 来解决。当作用域选择器匹配时,正如 Bramus 所说,”它会根据选择器与作用域根的距离来权衡两个选择器”,由于 “light “在这里更近,所以它会胜出。
不过,我最喜欢的还是在 DOM 中插入 <style> 标签,然后让它只对 DOM 中的该部分应用范围样式,而无需命名任何内容。
<div class="my-cool-component"> <style> @scope { :scope { /* selects the div above, without having to select it by class or anything */ } .card { } } </style> <article class="card"> </article> </div>
浏览器支持
Browser Support | ✅ Chrome ✅ Safari ❌ Firefox |
Progressive Enhancement? | No |
Polyfillable | No |
基本用法示例
层叠层
什么是层叠层?
CSS 中的层叠层是一种非常强大的语法,它可以影响一大块样式的样式优先级。你可以选择对层进行命名和排序(如果没有明确排序,则按源顺序排序)。无论选择器的优先级如何,较高层的样式都会自动优先于较低层的样式。不在层中的样式是优先级最高的。
<body id="home">
@layer base { body#home { margin: 0; background: #eee; } } body { background: white; }
我们习惯于认为 body#home 是一个优先级更高的选择器,因此背景将是 #eee。但由于这里有未分层的样式,所以它会胜出,使背景变成白色。
你可以拥有任意多的图层,并且可以预先对它们进行排序。我认为分层很可能会成为新的新建项目的最佳实践,并形成类似的形式:
@layer reset, default, themes, patterns, layouts, components, utilities;
有一个问题是,低层的 !important 规则实际上更强大。
什么时候需要使用它们?
如果你参与的项目使用了第三方样式库,你就能从 CSS 层中获得很多价值。你可以将该库放在比你的团队编写的样式更低的层上,这样你就不必担心在选择器优先级方面与第三方库对抗。你在较高层上的样式将永远胜出,这可能会创建出更简洁、更易维护的 CSS。
例如,只需使用层关键字,就可以将所有 Bootstrap 放在较低的层上,这样,即使 Bootstrap 本身使用了优先级更高的选择器,你在其后编写的任何样式优先级也会高于它们。
@import url("https://cdn.com/bootstrap.css") layer; h5 { margin-bottom: 2rem; }
浏览器支持
Browser Support | Full |
Progressive Enhancement? | No |
Polyfillable | Yes |
基本用法示例
逻辑属性
什么是逻辑属性?
逻辑属性是指定方向属性的替代属性。例如,在像英语这样从左到右的语言中,内联方向是水平的,块方向是垂直的,因此 margin-right
相当于 margin-inline-end
,而 margin-top
相当于 margin-block-start
。而在阿拉伯语等从右到左的语言中,margin-inline-end
则相当于 margin-left
,因为这是元素内联流的终点。有很多 CSS 属性和值都有类似的方向性成分(border
、padding
、offset
、set
),因此诀窍在于理解内联和块流,并使用正确的开始或结束值。
何时需要使用它们?
通常情况下,当你在 CSS 中声明方向信息时,你指的是 “文本的内联方向”。这听起来可能有点奇怪,但想象一下一个按钮以及图标和文本之间的空间。如果你在图标上应用 margin-right
,使其与文本保持一定间距,但页面被翻译成了从右到左的语言,那么这个间距现在就在图标的错误一侧了。你指的是图标上的 margin-inline-end
。如果用这种方法使用逻辑属性对你的侧边进行编码,它就会自动翻译得更好,而无需编写任何额外的条件代码。
浏览器支持
Browser Support | Full |
Progressive Enhancement? | You’d have to use @supports and unset to remove the fallback value and reset using a logical property, but it’s possible. |
Polyfillable | I can’t vouch for it, but there is a processing option. |
基本用法示例
P3 色彩
什么是 Display P3 色彩空间?
在网络上,我们通常使用 sRGB 色彩空间。这就是十六进制颜色和 rgb()
、hsl()
和 hsb()
函数所使用的颜色空间。如今,许多显示器都能显示比 sRGB 所能描述的更广泛的色彩,因此局限于该色彩空间是很糟糕的。Display P3 色彩空间比 sRGB 宽 50% 左右,色彩更加丰富、鲜艳。新的 CSS 函数甚至可以使用不同的颜色模型,这些模型都有自己的有用属性,让我们可以在这个空间中声明颜色。
何时需要使用它们?
如果想使用鲜艳的颜色,就需要使用 P3 色彩空间中的颜色。使用较新的颜色模型(和函数)可以实现这一目的,而且对其他各种事情也非常有用。
例如,oklch()
函数(以及 OKLCH 色彩模型)可以显示任何其他方法所能显示的任何颜色(加上 P3),与 hsl()
具有类似的人类可读性,并且具有 “统一感知亮度”,因此第一个数字(亮度)的表现比 hsl()
更容易预测。这对网络色彩来说非常好。但这并不是唯一的新色彩模型和函数!我发现 oklab 色彩模型通常最适合渐变效果。
浏览器支持
Browser Support | Full (e.g. oklab) |
Progressive Enhancement? | Yes — you can declare fallback colors and displays that can’t display the color you declare will come back down into range. |
Polyfillable | Yes |
基本用法示例
您可以编辑这些 <style> 块,因为我将它们设置为 display: block; 和 contenteditable:
颜色混合
什么是 color-mix()?
CSS 中的 color-mix()
函数允许你混合颜色。这种功能在 CSS 处理工具中已经存在了很长时间,而作为 CSS 演进的典型特征,现在它已经被集成到了本地 CSS 中,而且比处理器中的功能更周到、更强大。
何时需要使用它们?
你是否曾想过即时调暗或调亮已有的颜色?color-mix()
就能做到这一点。颜色混合可以在特定的颜色模型中进行,这意味着你可以利用该模型的工作原理。例如,OKLCH 在感知上具有统一的亮度,因此可以用来调整亮度。您可以使用 color-mix()
制作整个调色板。
浏览器支持
Browser Support | Full |
Progressive Enhancement? | Yes, you could declare fallback colors. |
Polyfillable | Could be but I don’t know of one. |
基本用法示例
margin-trim
什么是 margin-trim?
margin-trim
属性会在所选容器的指定方向上删除该方向末端的任何边距。想象一下,在一个容器中,一排有五个块都有右边距。你可能会选择 :last-child
来移除右边距。使用 margin-trim
可以确保从父元素本身移除边距。
.container { /* prevent "extra" margin at the end of the element */ margin-trim: block-end; /* an element like this might be the culprit, but it could be anything */ > p { margin-block-end: 1rem; } }
什么时候需要使用它?
你知道 flexbox 和 grid 的间距属性有多……棒吗?它只会在元素之间拉开间距。好吧,如果你需要在元素之间应用间距,但又不能使用 gap
,那么 margin-trim
就非常不错,因为它意味着你可以在所有子元素上应用方向性边距,而不用担心额外的花哨的选择器来选择第一个或最后一个元素并移除不需要的最后边距。这可能会成为一种最佳做法。
浏览器支持
Browser Support | ✅ Safari ❌ Chrome ❌ Firefox |
Progressive Enhancement? | Yes. A little extra space likely isn’t a major problem. |
Polyfillable | No |
基本用法示例
这里的最后一段是一个众所周知的情况,它的底边在底部产生的空间比其他任何边缘都要大。有了 margin-trim
,我们就可以确保将其切掉,而不必选择最后一段并手动将其删除。
文本换行
什么是文本换行?
在你的长期 CSS 记忆中,可能没有 text-wrap
属性。它可以使用 text-wrap: nowrap;
,但我们通常会想到 white-space: nowrap;
。但现在,text-wrap
又有了两个新花样:
text-wrap: balance;
– 在文本换行时,尽量使行间等宽。text-wrap: pretty;
– 避免孤字。
什么时候需要使用它们?
标题中的一个单词孤零零地出现在下一行,看起来非常突兀,可以说是糟糕的排版。以前没有很好的方法来解决这个问题,只有一些笨拙的技巧,比如在最后两个单词之间插入一个 而不是正常的空格。平衡标题可以避免这种情况的发生,还能进一步使多行文字的宽度大致相同。使用 “漂亮 “更侧重于防止孤字,因此更适合正文。
浏览器支持
Browser Support | Depends on which value. balance has decent support with all browsers ready or coming soon. pretty , less-so. |
Progressive Enhancement? | Yes. While slightly less aesthetic, widows and orphans are not that big of a problem, so if this property doesn’t work, it’s no big deal. |
Polyfillable | Yes. |
基本用法示例
子网格
什么是子网格?
子网格是使用 CSS 网格的一个可选部分,在嵌套网格元素时非常重要。在网格级元素上设置 grid-template-columns: subgrid;
或 grid-template-rows: subgrid;
,就表示 “从我的父网格继承这些相关的列或行”。
什么时候需要使用它们?
一般来说,使用网格进行布局的目的是为了使事物排列整齐。如果没有 subgrid,就意味着网格的子元素无法访问父网格的网格线,因此也就没有机会帮助对齐。而子网格则填补了这一空白。当 DOM 嵌套对于功能性或可访问性(如 <form>
中的功能性或可访问性)非常重要时,subgrid 可以帮助确保事物合理地排成一行。
浏览器支持
Browser Support | Full |
Progressive Enhancement? | Yes. You can fall back to defining your own grid lines that are workable if not perfect. |
Polyfillable | No. There is a grid polyfill but it doesn’t do subgrid. |