在过去的 4 年中,大量的 CSS 新特性涌现。不过对能在浏览器中安全使用的 CSS 特性我们要甄别,不然学过之后发现兼容性差得不行,那就有点得不偿失了。
这里总结了你可以在 2024 年可以安全使用的 CSS 特性。
1、CSS 逻辑属性
图片
兼容性:https://caniuse.com/css-logical-props
长期以来,许多 CSS 属性使用物理方向来设置值,如margin-top、padding-right、bottom、border-left 等。但在国际化(多语言环境)背景下,基于物理方位的设置无法适应不同书写模式下(writing-mode)一致的阅读、布局体验,于是便有了基于 Inline、Block 概念的逻辑属性(Logical properties)。
width: 40px; --> inline-size: 40px;
height: 20px; --> block-size: 20px;
margin-top: 12px; --> margin-block-start: 12px;
margin-bottom: 12px; --> margin-block-end: 12px;
margin-left: 14px; --> margin-inline-start: 14px;
margin-right: 14px; --> margin-inline-end: 14px;
margin-top: 12px; margin-bottom: 16px; --> margin-block: 12px 16px;
margin-left: 16px; margin-right: 16px; --> marign-inline: 16px;
2、容器查询
图片
兼容性:https://caniuse.com/css-container-queries
容器查询(Container queries)与媒体查询(Media queries)类似,不过媒体查询只支持使用浏览器宽度或高度作为查询条件,但容器查询则适用于任何容器,而不仅仅是浏览器。
容器查询根据功能又可以拆分成尺寸查询(Size Queries)和样式查询(Style Queries),其中样式查询支持有限,因此目前当我们谈论容器查询时,实际上是指容器查询中的尺寸查询。
.post {
container: sidebar / inline-size;
/*等同于 container-name: sidebar; container-type: inline-size;*/
}
@container sidebar (min-width: 700px) {
.card {
font-size: 2em;
}
}
以上代码的含义:
- 将类名为 .post 的元素声明为名为 sidebar 的容器,inline-size 表示这个容器的查询将基于它的内联尺寸(在默认文档流下 writing-mode: horizontal-tb;,效果等同于宽度 width)
- container sidebar (min-width: 700px) {...} 则表示当名为 sidebar 的容器(即前面定义的 .post 元素)的内联尺寸(宽度)达到或超过 700p 时,其内部 .card 的元素的字体大小设置为 2em
3、:has()
图片
兼容性:https://caniuse.com/css-has
顾名思义,当需要根据元素内部是否某些特定元素来决定外部元素的样式时,就可以使用 :has 伪类。
理解 :has() 伪类最好的方式就是通过案例学习。
案例 1:当元素 <main> 内包含 <strong> 标签时,为 <main> 应用样式
main:has(strong) {
/*... */
}
案例 2:当元素 <main> 内包含 p 和 <strong> 标签时,为 <main> 应用样式
main:has(p):has(strong) {
/*... */
}
案例 3:当元素 <main> 内包含 p 或 <strong> 标签时,为 <main> 应用样式
main:has(p, strong) {
/*... */
}
案例 4:当元素 <main> 内不含 <strong> 标签时,为 <main> 应用样式
main:not(:has(strong)) {
/*... */
}
案例 5:当元素 <p> 后面是 <strong> 标签时,为 <p> 应用样式
p:has(+ strong) {
/*... */
}
案例 6:当元素 <p> 后面有 <figure> 标签时,为 <p> 应用样式
p:has(~ figure) {
/*... */
}
4、:is 和 :where
图片
:is 兼容性:https://caniuse.com/css-matches-pseudo
图片
:where 兼容性:https://caniuse.com/mdn-css_selectors_where
:is 和 :where 功能上完全一致,接受一个选择器列表作为参数,当列表中的任何一个选择器匹配时,就应用样式。
下面的写法是等价的:
ul li,
ol li {}
/* 等价于 */
:is(ul, ol) li {}
/* 或 */
:where(ul, ol) li {}
:is 和 :where 唯一不同之处在于,:where() 本身修改的选择器不算在权重计算,而 :is() 修改的选择器是算在权重计算的。
以下面的代码举例:
<div class="parent">
<p class="child1">Child 1</p>
<p class="child2">Child 2</p>
</div>
<style>
.parent p {
color: blue;
}
:where(p.child1,p.child2) {
color: green;
}
</style>
因为 :where 选择器权重为 0,因此 .parent p 的样式优先级更高,最终文本的颜色会是蓝色而不是绿色。
图片
如果把 :where 换成 :is,文本颜色就会是绿色,因为 :is 选择器会按照正常的选择器优先级规则来处理。
图片
CSS 权重计算工具:https://specificity.keegan.st/
5、CSS 嵌套
CSS 嵌套语法(CSS Nesting)允许在一个选择器内部嵌套另一个选择器,这样可以更好地提现 HTML 文档元素间的结构关系,这个语法最早是通过 SASS 这类 CSS 预处理器引入的,由于特别好用,最终成为 CSS 规范的一部分了!
图片
兼容性:https://caniuse.com/css-nesting
Chrome 从 112 版本开始支持 CSS 嵌套[2]。
借助 CSS 嵌套,你可以在某个选择器上下文中指定内部元素样式。
.parent {
color: blue;
.child {
color: red;
}
}
不过第一版本实现的 CSS 嵌套版本有一个限制,就是无法嵌套裸元素标记。
.card {
h1 {
/* Chrome 112 版本中这种写法是无效的! */
}
}
/* 要写成带子引用前缀 & 的 */
.card {
& h1 {
/* Chrome 112 版本中这种写法才是有效的 */
}
}
不过,这一限制在 Chrome 120 版本中修复了[3]。
.card {
h1 {
/* Chrome 120 版本开始这种写法是有效的! */
}
}
这是我们在使用 CSS 原生嵌套语法时,唯一要注意的地方。
6、CSS 比较函数
CSS 比较函数(Comparison Functions)类似 if/else 判断,允许浏览器在你设定的多个值选择一个合适的值应用。这类比较函数共有 3 个:
- min(a, b):取 a 和 b 之间的最小值
- max(a, b):取 a 和 b 之间的最大值
- clamp(a, b, c) :取最小值 a、期望的值 b 和最大值 c 之间的最合适值
图片
兼容性:https://caniuse.com/css-math-functions
min、max 相对来说比较简单,因此我们略过直接讲 clamp。
以下面一段声明举例:
font-size: clamp(16px, 1vw + 1rem, 32px);
字体大小会根据视口宽度和根元素字体大小进行动态调整。
- 浏览器首先会尝试使用1vw + 1rem这个计算出来的值作为字体大小
- 如果这个值小于 16px,就会使用 16px
- 如果这个值大于 32px,就会使用 32px
这种方式可以在不同的设备(如手机、平板、桌面浏览器)上,提供一个相对合适的、自适应的字体大小,既保证了文本在小屏幕设备上字体足够大,又避免了在大屏幕设备上字体过大的问题。
7、级联层 @layer
图片
兼容性:https://caniuse.com/css-cascade-layers
级联层允许创建多个 CSS 级联并指定级联排序。
我们直接看案例:
<div class="overly" id="powerful">
<div class="framework widget my-single_class">widget</div>
</div>
<style>
@layer framework {
.overly#powerful .framework.widget {
color: blue;
}
}
@layer site {
.my-single_class {
color: red;
}
}
</style>
以上,我们的“widget”文本最终会呈现红色。下面解释原因:
这段代码通过 @layer 将针对同一个元素的样式规则划分到了两个不同的层 framework 和 site 中。按照 @layer 的优先级规则,后声明的层优先级更高——也就是说,先看层级顺序,再看同层级内的权重。
图片
因此,“widget”文本最终会呈现红色。
当然,你还可以通过 @layer layer1, layer2..., layerN; 指定层级应用的顺序:
图片
可以看到,虽然书写顺序上 site 层级出现在 framework 之后,不过由于手动指定了层级次序:@layer site, framework;—— framework 在 site 之后,所以文本最终呈现蓝色。
8、子网格
图片
兼容性:https://caniuse.com/css-subgrid
子网格(Subgrid)的出现是为了解决传统的 CSS 网格布局(Grid Layout)在处理多层嵌套时的对齐问题。
直接听我说会比较抽象,我们先举一个例子进行说明:
<div class="wrapper">
<div class="item">
<div class="desc">short desc</div>
<img src="https://picsum.photos/id/237/200/300" alt="" class="src">
</div>
<div class="item">
<div class="desc">long desc long desc long desc</div>
<img src="https://picsum.photos/id/237/200/300" alt="" class="src">
</div>
</div>
<style>
.wrapper {
display: inline-grid;
grid-template-columns: 1fr 1fr;
grid-gap: 1rem;
}
.item {
width: 200px;
grid-row: 1 / 4;
}
</style>
效果如下:
图片
可以看到,左右两边的描述文本由于长度不一样,导致最终渲染出来的元素视觉上不一样高。
这个时候,我们可以通过 subgrid 将父级网格行传递给内部项目。这里需要修改 .item 元素来实现。
.item {
width: 200px;
grid-row: 1 / 4;
+ display: grid;
+ grid-template-rows: subgrid;
}
这里增加了 2 行,首先明确网格项目本身也是一个网格容器,这是子网格生效的前提;接着,我们将 grid-template-rows 设定成一个特殊的关键字 subgrid。
再来看看效果:
图片
发现两个网格项目的描述文本对齐了!
grid-template-rows: subgrid; 的作用就是让网格项目继承父网格容器的行分配系统,或者说父子共用一套行分配系统,这样就能实现一致的对齐效果了。
更加使用案例可以参考 Ahmad Shadeed 的《Learn CSS Subgrid》[4] 这篇文章。
其他一些小而美的特性
text-wrap: balance
图片
兼容性:https://caniuse.com/css-text-wrap-balance
text-wrap: balance 让文本这行更好看,标题、内容元素皆可使用。兼容性不太好,但可以无脑用,作为渐进式特性还是很好的。
h1, h2, h3, h4, h5, h6, blockquote {
text-wrap: balance;
}
效果:
图片
动态视口高度 dvh
图片
兼容性:https://caniuse.com/viewport-unit-variants
解决了 vh 单位不会考虑手机顶部地址栏、底部工具栏的问题。
图片
详情可以参考文章 The large, small, and dynamic viewport units[5]。
accent-color
图片
兼容性:https://caniuse.com/mdn-css_properties_accent-color
这个属性就比较牛了,可以用来设置某些用户界面控件元素的强调色。
图片
目前支持的元素包括单选框、复选框和进度元素(<input type=“range|checkbox|radio”>)。
媒体查询里的范围查询
图片
兼容性:https://caniuse.com/css-media-range-syntax
范围查询允许我们在媒体查询里使用使用 >、<、>= 或 <= 的数学比较运算符。其实旧语法也支持,不过不太直观。
我们直接举例说明范围查询的使用[6]:
/* 场景一:大于等于 400px 时应该样式 */
/* 旧语法 */
@media (min-width: 400px) {
}
/* 新语法 */
@media (width >= 400px) {
}
/* 场景二:小于等于 30em 时应用样式 */
/* 旧语法 */
@media (max-width: 30em) {
}
/* 新语法 */
@media (width <= 30em) {
}
/* 场景二:在 400px、600px 之间时(包含)应用样式 */
/* 旧语法 */
@media (min-width: 400px) and (max-width: 600px) {
}
/* 新语法 */
@media (400px <= width <= 600px ) {
}
弹性布局 gap 属性
图片
兼容性:https://caniuse.com/flexbox-gap
弹性布局中的 gap 属性可以让我们非常便捷的设置行/列间隔,替代了古早需要通过设置 margin/padding 属性来实现间隔效果的麻烦步骤。
值得注意的是,gap 属性最先在网格布局(Grid Layout)和多列布局(Multi-column Layout)中实现(Chrome 66+),最终在弹性布局中也支持了,Chrome 84+ 兼容性足够我们放心使用了。
参考资料
[1]New CSS that can actually be used in 2024: https://thomasorus.com/new-css-that-can-actually-be-used-in-2024.html
[2]Chrome 从 112 版本开始支持 CSS 嵌套: https://developer.chrome.com/blog/new-in-chrome-112?hl=zh-cn#nesting-rules
[3]这一限制在 Chrome 120 版本中修复了: https://developer.chrome.com/blog/css-nesting-relaxed-syntax-update?hl=zh-cn
[4]《Learn CSS Subgrid》: https://ishadeed.com/article/learn-css-subgrid/
[5]The large, small, and dynamic viewport units: https://web.dev/blog/viewport-units
[6]举例说明范围查询的使用: https://web.dev/articles/media-query-range-syntax