Skip to content
本页目录

CSS语法@规则at-rule


相信很多小伙伴都用过 CSS 的屏幕宽度媒体查询,比如这样:

这里表示的是与屏幕宽度相关的样式设置,上面的代码表示当屏幕宽度大于 900px 时,内部的样式代码块才能生效。

其实不仅仅是上面的屏幕宽度媒体查询,在 CSS 中,存在大量的以@符号开头的规则。称之为@规则(at-rule)。本文就将介绍一下除去媒体查询之外,其他有意思的且在未来会越来越重要的@规则规则。

at-rule @规则

@规则(at-rule )是什么呢?

一个at-rule是一个CSS 语句,以 at 符号开头, ‘@‘ (U+0040 COMMERCIAL AT), 后跟一个标识符,并包括直到下一个分号的所有内容, ‘;‘ (U+003B SEMICOLON), 或下一个 CSS 块,以先到者为准。

除去我们最为熟悉的@media之外,CSS 还有哪些 @规则 呢?

下面是一些 @规则,由它们的标示符指定,每种规则都有不同的语法:

  • @charset, 定义样式表使用的字符集。
  • @import, 告诉 CSS 引擎引入一个外部样式表。
  • @namespace, 告诉 CSS 引擎必须考虑 XML 命名空间。

下面是一些嵌套 @ 规则,是嵌套语句的子集,不仅可以作为样式表里的一个语句,也可以用在条件规则组里:

  • @media,如果满足媒介查询的条件则条件规则组里的规则生效。
  • @page,描述打印文档时布局的变化。
  • @font-face,描述将下载的外部的字体。
  • @keyframes,描述 CSS 动画的中间步骤。
  • @supports, 如果满足给定条件则条件规则组里的规则生效。
  • @document,如果文档样式表满足给定条件则条件规则组里的规则生效。(推延至 CSS Level 4 规范)
  • @viewport已废弃),规则让我们可以对文档的大小进行设置。这个特性主要被用于移动设备,但是也可以用在支持类似“固定到边缘”等特性的桌面浏览器,如微软的 Edge。
  • @counter-style— 一个@counter-style规则定义了如何把一个计数器的值转化为字符串表示。
  • @font-feature-values(plus@swash,@ornaments,@annotation,@stylistic,@stylesetand@character-variant), 允许作者在font-variant-alternates中使用通用名称,用于在 OpenType 中以不同方式激活功能。它允许在使用几种字体时简化 CSS。
  • @property实验性),是CSS HoudiniAPI 的一部分,它允许开发者显式地定义他们的css 自定义属性, 允许进行属性类型检查、设定默认值以及定义该自定义属性是否可以被继承。
  • @layer, 声明了一个 级联层,同一层内的规则将级联在一起,这给予了开发者对层叠机制的更多控制。

除去我们非常熟悉的@mediakeyframes以及@font-face,像是@supports@counter-style@property@layer等都已经或将在未来 Web 应用中扮演举足轻重的作用。

下面,就跟随本文,一起对它们一探究竟。你也可以跳过你已经掌握的,翻到对应你还不太了解的 @ 规则下,迅速了解它们。

@charset、@import、@namespace

这三个可以放在一起讲解,他们的语法比较简单,也相对好理解。其中:

1、@charset:指定样式表中使用的字符编码。它必须是样式表中的第一个元素,而前面不得有任何字符。

像这样:

注意,如果有多个 @charset @规则被声明,只有第一个会被使用。

很多人会有疑惑,这个声明到底有什么用呢?

事实上,如果 CSS 文件中有任何非 ASCII 文本,例如字体名称,伪元素的 content 属性值、选择器等中的非 ASCII 字符,都需要确保 CSS 解析器知道如何转换字节正确转换为字符,以便它理解 CSS 代码。

所以如果当你发现你的伪元素 content 中插入了一些内容,但是经过打包编译后它乱码了,很有可能是因为你忘了声明这个字符集。

2、@import:用于从其他样式表导入样式规则。这些规则必须先于所有其他类型的规则,@charset规则除外

@import 有两种语法:

  1. url() 内包含 style sheet 的 URI
  2. 直接写 style sheet 的 URI 的字符串

还可以直接在后面定义媒体查询规则,像是这样:

合理的使用@import其实也是有好处的:

  1. 可以合理的控制 CSS 文件的大小
  2. 更好的分治与复用

很多人可能会经常看到,网络上会有各种抵制 @import的文章,不过既然设计了 @import,总有它的有用之处,不能过于绝对。使用@import影响页面性能的地方主要体现在两个方面:

  1. 影响浏览器的并行下载
  2. 优先级问题,样式互相覆盖
  3. 导致页面闪烁

这里可以简单解释一下。首先我们得知道,加载页面时,link 标签引入的 CSS 被同时加载,而 @import 引入的 CSS 将在页面加载完毕后被加载。

CSS 解析引擎在对一个 CSS 文件进行解析时,如在文件顶部遇到@import规则,将被替换为该 @import 导入的 CSS 文件中的全部样式。而@import内的规则其后被加载,却会在加载完毕后置于样式表顶部,最终渲染时,如果存在同名同优先级样式,会被下面的同名样式层叠,导致所谓的优先级冲突。

实际上,浏览器渲染的动作一般会执行多次的。最后一次渲染,一定是基于之前加载过的所有样式整合后渲染树进行绘制页面的,
而由于@import内的规则的加载时机问题,会在页面内容加载完后再加载。相当于把 CSS 放在了 body 底部,从而造成了页面的闪烁。当网络较差时,闪烁体验更为明显。

3、@namespace@namespace是用来定义使用在 CSS 样式表中的 XML 命名空间的 @规则。定义的命名空间可以把通配、元素和属性选择器限制在指定命名空间里的元素。

并且,任何 @namespace 规则都必须在所有的@charset@import规则之后,并且在样式表中,位于其他任何样式声明之前。

总的来说,@namespace在现如今的 CSS 生态中,属于非常冷门的一个规则。基本上我从业这么久,没怎么见过这个属性的具体使用。

@media、@keyframes、@font-face

这三个 @ 规则,大家应该非常熟悉。

  • @media:如果满足媒介查询的条件则条件规则组里的规则生效
  • @keyframes:定义 CSS 动画的中间步骤
  • @font-face:描述将下载的外部的字体

@keyframes@font-face这两个大家肯定非常熟悉。

但是@media其实内有乾坤!除了屏幕宽度媒体查询外,其实还存在非常多不同功能的媒体查询!

下面我会列出一些在未来,我认为会越来越被提及使用到的@media规则。

prefers-reduced-motion 减弱动画效果

prefers-reduced-motion 规则查询用于减弱动画效果,除了默认规则,只有一种语法取值prefers-reduced-motion: reduce,开启了该规则后,相当于告诉用户代理,希望他看到的页面,可以删除或替换掉一些会让部分视觉运动障碍者不适的动画类型。

规范原文:Indicates that user has notified the system that they prefer an interface that removes or replaces the types of motion-based animation that trigger discomfort for those with vestibular motion disorders.

vestibular motion disorders 是一种视觉运动障碍患者,中文我只能谷歌翻译,翻译出来是前庭运动障碍,我感觉不太对,谷歌了一下是一种会导致眩晕的一类病症,譬如一个动画一秒闪烁多次,就会导致患者的不适。

使用方法,还是上面那段代码:

如果我们有一些类似这样的动画:

prefers-reduced-motion 减弱动画效果

在用户开启了prefers-reduced-motion: reduce 时,就应该把它去掉。那么该如何开启这个选项呢?MDN — prefers-reduced-motion给出的是:

  • 在 GTK/Gnome 中,可以通过 GNOME Tweaks (在“通用”或“外观”菜单中,取决于具体版本) 的配置,设置 gtk-enable-animations 的值为 false
  • 可以在 GTK 3 的配置文件中的 [Settings] 模块下设置 gtk-enable-animations = false
  • 在 Windows 10 中:设置 > 轻松获取 > 显示 > 在 Windows 中显示动画
  • 在 Windows 7 中:控制面板 > 轻松获取 > ?是计算机更易于查看 > 关闭不必要动画
  • 在 MacOS 中:系统偏好 > 辅助使用 > 显示 > 减少运动
  • 在 iOS 上:设置 > 通用 > 辅助性 > 减少运动
  • 在 Android 9+ 上:设置 > 辅助性 > 移除动画

prefers-color-scheme 适配明暗主题

prefers-color-scheme还是非常好理解的,它用于匹配用户通过操作系统设置的明亮或夜间(暗)模式。它有两个不同的取值:

  • prefers-color-scheme: light: 明亮模式
  • prefers-color-scheme: dark: 夜间(暗)模式

语法如下,如果我们默认的是明亮模式,只需要适配夜间模式即可:

当然,上述只是 CSS 代码示意,要做到两套主题的切换肯定不是这么简单,方法也很多,本文不赘述,读者可以自行了解各种实现主题切换,或者是明暗切换的方案。

prefers-contrast 调整内容色彩对比度

prefers-contrast该 CSS 媒体功能是用来检测用户是否要求将网页内容以更高或者更低的对比度进行呈现。其中:

  • prefers-contrast: no-preference:默认值,不作任何变化
  • prefers-contrast: less:希望使用对比度更低的界面
  • prefers-contrast: more:希望使用对比度更高的界面

prefers-contrast: less为例子,语法如下:

上面只是伪 CSS 代码,具体可能需要对具体的一些元素进行处理,或者使用filter: contrast()全局统一处理,当开启配置时,用于实现类似这样的功能:

prefers-contrast 调整内容色彩对比度

什么是色彩对比度

是否曾关心过页面内容的展示,使用的颜色是否恰当?色弱、色盲用户能否正常看清内容?良好的色彩使用,在任何时候都是有益的,而且不仅仅局限于对于色弱、色盲用户。在户外用手机、阳光很强看不清,符合无障碍标准的高清晰度、高对比度文字就更容易阅读。

这里就有一个概念 —颜色对比度,简单地说,描述就是两种颜色在亮度(Brightness)上的差别。运用到我们的页面上,大多数的情况就是背景色(background-color)与内容颜色(color)的对比差异。

最权威的互联网无障碍规范 ——WCAG AA规范规定,所有重要内容的色彩对比度需要达到 4.5:1 或以上(字号大于18号时达到 3:1 或以上),才算拥有较好的可读性。

prefers-reduced-transparency 减少透明元素

prefers-reduced-transparency该 CSS 媒体功能是用来检测用户是否要求减少网页中的透明元素:

  • prefers-contrast: no-preference:默认值,不作任何变化
  • prefers-contrast: reduce:希望界面元素存在尽可能少的透明元素

prefers-contrast: reduce为例子,语法如下:

兼容性如下:

兼容性

prefers-reduced-data 减少数据传输

对于部分网速较差的地区,或者流量很贵的情况,用户会希望减少页面中的流量请求,基于此有了prefers-reduced-data

prefers-reduced-data该 CSS 媒体查询功能是用于告知用户代理,希望减少页面的流量请求。

  • prefers-reduced-data: no-preference:默认值,不作任何变化
  • prefers-reduced-data: reduce:希望界面元素消耗更少的互联网流量

prefers-reduced-data: reduce为例子,语法如下:

当检测到用户开启了prefers-reduced-data: reduce,我们将提供压缩度更高,尺寸更小,消耗流量更少的图片。

当然,上述代码只是个示意,我们可以做的其实有更多。

不过,这是仍处于实验室的功能,暂时没有任何浏览器支持该媒体查询~ 😢

当然,从 Chrome 85+ 开始,可以通过开启#enable-experimental-web-platform-features实验室选项开启该功能!

@supports特性检测

好,介绍完一些后续会非常重要从@media规则后,我们来看看@supports

传统的 CSS 特性检测都是通过 javascript 实现的,但是如今,原生 CSS 即可实现特性检测的功能。

CSS@supports通过 CSS 语法来实现特性检测,并在内部 CSS 区块中写入如果特性检测通过希望实现的 CSS 语句。

语法:

举个例子:

上面的例子中,position: sticky是 position 的一个比较新的属性,用于实现黏性布局,可以轻松实现一些以往需要 Javascript 才能实现的布局,但是不一定在一些低端机型上兼容。

上面的写法,首先定义了 div 的position: fixed,紧接着下面一句@supports (position:sticky)则是特性检测括号内的内容,如果当前浏览器支持@supports语法,并且支持position:sticky语法,那么 div 的 则会被设置为position:sticky

我们可以看到,@supports语法的核心就在于这一句:@supports (...) { },括号内是一个 CSS 表达式,如果浏览器判断括号内的表达式合法,那么接下来就会去渲染括号内的 CSS 表达式。除了这种最常规的用法,还可以配合其他几个关键字:

@supports not&&@supports and&&@supports or

@supports not— 非

not 操作符可以放在任何表达式的前面来产生一个新的表达式,新的表达式为原表达式的值的否定。看个例子:

因为添加了 not 关键字,所以与上面第一个例子相反,这里如果检测到浏览器不支持 transform 这种分开单独的写法 —scale: 1,则将.container的 transform 属性合在一起写,写成transform: translate(50%, 10%) rotate(80deg) scale(1.5)

@supports and— 与

这个也好理解,多重判断,类似 javascript 的&&运算符符。用 and 操作符连接两个原始的表达式。只有两个原始表达式的值都为真,生成的表达式才为真,反之为假。

当然,and 可以连接任意多个表达式看个例子:

上面同时,检测@supports (display:-webkit-box) and (-webkit-line-clamp:2) and (-webkit-box-orient:vertical)了三个语法,如果同时支持,则设定三个 CSS 规则。这三个语法必须同时得到浏览器的支持,如果表达式为真,则可以用于实现多行省略效果:

效果如下:

多行省略效果

@supports or— 或

理解了@supports and,就很好理解@supports or了,与 javascript 的||运算符类似,表达式中只要有一个为真,则生成表达式表达式为真。

看例子:

上面的例子中,只有检测到浏览器支持background:-webkit-linear-gradient(0deg, yellow, red)或者(or)background:linear-gradient(90deg, yellow, red)其中一个,则给 div 元素添加渐变。

效果如下:

div 元素添加渐变

Can i use?

兼容性来看,先看看Can i use(更新至 2022/11/03)吧:

Can i use

大部分浏览器都已经支持了,我们已经可以开始使用起来了,使用@supports实现渐进增强的效果。

@counter-styleCSS 计数器

@counter-style:是一个 CSS at-rule,它让开发者可以自定义 counter 的样式。一个 @counter-style 规则定义了如何把一个计数器的值转化为字符串表示。

利用@counter-style,我们可以构建自定义的计数器样式。

当然,在@counter-style之前,CSS 还有一种实现简单计数器的规范,它由如下几个属性共同构成:

  • counter-reset: 初始化计数器的值
  • counter-increment:在初始化之后,计数器的值就可以使用 counter-increment 来指定其为递增或递减
  • counter():计数器的值可以使用 counter() 或 counters() 函数以在 CSS 伪元素的 content 属性中显示

我们来看最简单的一个例子,我们想实现一个 ul 布局,其中的 li 个数不定,但是均分每行的空间,并且能够自动带上序号,像是这样:

@counter-styleCSS 计数器

使用counter-resetcounter-incrementcounter()这一套,非常的简单就能实现,像是这样:

给每个 li 元素标序号这个事情就可以交给 CSS 计数器:

简单解释一下:

  1. ul的样式中,每次都会初始化一个 CSS 计数器stepCount,默认值为 0
  2. li::before中的counter-increment: stepCount 1表示每次调用到这里,stepCount 的值加 1
  3. 最后通过counter(stepCount)将当前具体的计数值通过伪元素的 content 显现出来

OK,那么为什么有了上述的 CSS 计数器规范后,又新增了@counter-styleCSS 计数器规范呢?

@counter-style的意义

这是因为,上述的counter-resetcounter-incrementcounter()这一套更多的生成的数字类型的计数器。

但是,数字类型的计数器无法满足当前全球化的排版的诉求。基于此,@counter-style规则用一种开放的方式弥补了这一缺点,在预定义的样式不能满足需求时,它可以使开发者自定义他们自己的计数器样式。

举个例子,我们使用 MDN 上的例子作为示例:

这样,我们就可以得到自定义的计数前缀:

自定义的计数前缀

有了这个,我们就可以将上述的symbols替换成其他我们喜欢我计数图形,譬如 emoji 图形:

效果如下:

emoji效果

完整CSS代码:

当然,实际使用过程中,@counter-style的语法会有一点点复杂,可选的属性也有很多,更为具体的可以仔细学习下文档 —MDN — @counter-style

@propertyCSS 自定义属性

@property CSS at-rule 是 CSS Houdini API 的一部分, 它允许开发者显式地定义他们的 CSS 自定义属性,允许进行属性类型检查、设定默认值以及定义该自定义属性是否可以被继承。

正常而言,我们定义和使用一个 CSS 自定义属性的方法是这样的:

而有了@property规则之后,我们还可以像下述代码这样去定义个 CSS 自定义属性:

简单解读下:

  • @property --property-name中的--property-name就是自定义属性的名称,定义后可在 CSS 中通过var(--property-name)进行引用
  • syntax:该自定义属性的语法规则,也可以理解为表示定义的自定义属性的类型
  • inherits:是否允许继承
  • initial-value:初始值

其中,@property规则中的 syntax 和 inherits 描述符是必需的。

当然,在 JavaScript 内定义的写法也很简单,顺便一提:

CSS @property 的优势

为什么要使用这么麻烦的语法定义 CSS 自定义属性呢?CSS Houdini 定义的自定义变量的优势在哪里?

我们来看这样一个例子,我们有这样一个渐变的图案:

渐变的图案

我们改造下上述代码,改为使用 CSS 自定义属性:

得到的还是同样的一个渐变图。

我们再加上一个过渡效果:

鼠标 Hover 的时候,会发生什么:

鼠标 Hover 的时候

虽然我们设定了 1s 的过渡动画transition: 1s background,但是很可惜,CSS 是不支持背景渐变色的直接过渡变化的,我们得到的只是两帧之间的直接变化。

使用 CSS @property 进行改造

OK,接下来我们就使用本文的主角,使用 Houdini API 中的 CSS 自定义属性替换原本的 CSS 自定义属性。

简单进行改造一下,使用colorsyntax 语法类型:

我们使用了@property语法,定义了两个 CSS Houdini 自定义变量--houdini-colorA--houdini-colorB,在 hover 变化的时候,改变这两个颜色。

需要关注的是,我们设定的过渡语句transition: 1s --houdini-colorA, 1s --houdini-colorB,在这里,我们是针对 CSS Houdini 自定义变量设定过渡,而不是针对background设定过渡动画,再看看这次的效果:

过渡动画

完整代码:

CSS @property 规则的强大之处在于,很多以往无法使用 CSS 进行动画的效果,如今,借助它都可以实现!

@layer

@layer可谓是 CSS 圈 2022 年最受瞩目的新特性。

它的出现,目的在于让大型项目中的 CSS 文件及内容,可以得到更好的控制和管理。

CSS @layer 从CSS Cascading and Inheritance Level 5被规范定义。

何为 CSS @layer?简单而言,CSS@规则中的@layer声明了一个 级联层, 同一层内的规则将级联在一起, 这给予了开发者对层叠机制的更多控制。

语法也非常简单,看这样一个例子:

这样,我们就创建一个名为 utilities 的 @layer 级联层。

@layer 级联层如何使用呢?

通过 @layer 级联层管理样式优先级

@layer 级联层最大的功能,就是用于控制不同样式之间的优先级

看下面这样一个例子,我们定义了两个 @layer 级联层 A 和 B:

由于@layer B的顺序排在@layer A之后,所以@layer B内的所有样式优先级都会比@layer A高,最终 div 的颜色为green

div 的颜色为green

当然,如果页面内的 @layer 太多,可能不太好记住所有 @layer 的顺序,因此,还有这样一种写法。

我们可以同时命名多个 @layer 层,其后再补充其中的样式规则。

上述代码,我们首先定义了@layer B, C, A三个 @layer 级联层。而后再后面的 CSS 代码中补充了每个级联层的 CSS 代码,但是样式的优先级为:

A>C>B

因此,最终的 div 的颜色值为 @layer A 中定义的颜色,为blue

div 的颜色值为blue

到这里,CSS @layer 的作用可以清晰的被窥见。

利用 CSS @layer,我们可以将 CSS 不同模块划入不同的 @layer 中,利用先后顺序,非常好的去控制全局的样式优先级

CSS @layer 的诞生,让我们有能力更好的划分页面的样式层级,更好的处理内部样式与外部引用样式的优先级顺序,属于比较重大的一次革新。

@container容器查询

@container:提供了一种,基于容器的可用宽度来改变布局的方式。

容器查询也是一个非常新且重要的特性,弥补了过往媒体查询的不足。

在之前,响应式有这么个掣肘。同一 DOM 的不同布局形态如果想要变化,需要依赖诸如媒体查询来实现。

像是这样:

@container容器查询

通过浏览器视窗大小的变化,借助媒体查询,实现不一样的布局。

但是,在现如今,大部分 PC 端页面使用的是基于 Flex/Grid 的弹性布局。

很多时候,当内容数不确定的时候,即便是相同的浏览器视窗宽度下,元素的布局及宽度可能也是不一致的。

考虑下面这种情况:

效果如下:

基于 Flex/Grid 的弹性布局

这种情况下,如果需要在不同宽度下对最后一个元素做一下处理,传统方式还是比较麻烦的。

在这种情况下,容器查询(CSS Container Queries)就应运而生了!

容器查询的能力

容器查询它给予了 CSS,在不改变浏览器视口宽度的前提下,只是根据容器的宽度变化,对布局做成调整的能力。

还是上面的例子,简单的代码示意:

像是这样,我们通过resize: horizontal来模拟单个容器的宽度变化,在这种情况下,容器查询能够做到在不同宽度下,改变容器内部的布局。

这样,就简单实现了一个容器查询功能:

容器查询功能

注意,仔细和上面的例子作对比,这里,浏览器的视口宽度是没有变化的,变化的只是容器的宽度!

媒体查询与容器查询的异同,通过一张简单的图看看,核心的点在于容器的宽度发生变化时,视口的宽度不一定会发生变化:

媒体查询与容器查询的异同

我们简单拆解下上述的代码,非常好理解。

  1. .warp的样式中,通过container-name: wrap注册一个容器
  2. 注册完容器之后,便可以通过@container wrap ()容器查询语法,在内部写入不同情况下的另外一套样式
  3. 这里@container wrap (max-width: 400px) {}的意思便是,当.wrap容器的宽度小于 400 px 时,采用内部定义的样式,否则,使用外部默认的样式

关于容器查询更为具体的语法,我建议还是上 MDN 或者规范详细看看 —MDN — CSS Container Queries

@scroll-timeline

@scroll-timeline能够设定一个动画的开始和结束由滚动容器内的滚动进度决定,而不是由时间决定。

意思是,我们可以定义一个动画效果,该动画的开始和结束可以通过容器的滚动来进行控制。

利用它,我们可以使用纯 CSS 实现页面滚动与 CSS 动画的结合。

遗憾的是,这个如此好的特性,最近已经被规范废弃,已经不再推荐使用了:

@scroll-timeline

意思是,即便目前有一些浏览器已经支持了@scroll-timeline,但是它很快又将要退出历史舞台。不再建议再使用这个 at-rule 规则。

这里,@scroll-timeline虽然被废弃了,但是 CSS 将会换一种实现方式卷土重来。

总结

到这里,其实还有几个非常冷门且不太实用的 at-rule 规则,譬如:

  • @color-profile:允许定义并命名一个颜色配置文件
  • @font-feature-values:主要是相对字体功能的拓展

能够搜集到资料太少,文档也相对简陋,目前实用的场景太少,就不详细展开。

综上,可以看到,整个 at-rule 家族还是非常强大的,引入了非常多新的特性及功能,让 CSS 生态愈发强大。让 CSS 可以做到的事情越来越多,我们也有理由期待未来 CSS 会在 Web 领域扮演愈发重要的角色。