记一次由于 layer 层级造成 vuetify 样式与 unocss 样式冲突问题

前言

记一次由于 layer 层级造成 vuetify 样式与 unocss 样式冲突问题。

最近也是发布了 vite8 ,所以把手头的 jm-desktop 进行依赖升级,顺带升了 vuetify 和 unocss 。

不过升级完发现 vuetify 的组件中的 margin padding 几乎都变为 0 了。

于是打开 F12 观察了一下,发现是 unocss 的样式覆盖了 vuetify 的样式。

正文

CSS Layer

vuetify 在 v3.6.0 引入了 css layer ,所有的样式都会被分类到对应的 layer 上,比如 v-btn 的样式:

官方文档地址:点我直达

关于 css layer ,在 MDN 上给的解释是:

The @layer CSS at-rule is used to declare a cascade layer and can also be used to define the order of precedence in case of multiple cascade layers.

用我们中文的大白话讲,就是给 css 分类了,可以给一组样式起一个名字,然后指定这些名字的顺序,从而固定住优先级。

在先前,如果我们要覆盖某些样式,可能有以下几个办法:

  • 在标签上的 style 编写样式
  • 使用 !important 标记
  • 使用 id 选择器
  • 在该样式的“后面”编写一段选择器相同的样式

这几个其实都是一个原理,那就是提高所编写样式的权重,相关文章可以看 CSS样式权重

而 css layer 的出现,意味着我们无需再计算权重,权重低(层级高)的也能覆盖权重高的(层级低)。

定义 Layer

定义一个层级,在 CSS 钟使用 @layer 关键字,比如定义一个 utilities 层级:

1
2
3
4
5
6
7
8
9
@layer utilities {
.padding-sm {
padding: 0.5rem;
}

.padding-lg {
padding: 0.8rem;
}
}

层级也可以嵌套,也可以不指定名字,这里我们只已最简单的举例,详情可以查看 MDN 上的文档

定义 Layer 顺序

在定义完一些 Layer 之后,我们就需要知道如何定义他们的顺序,也就是优先级了,同样,也是使用 @layer 关键字,比如现在有两个层级 layer1 ,layer2 ,我们想让 layer2 优先级比 layer1 高,那么可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
@layer layer1, layer2;

@layer layer1 {
p {
color: red;
}
}

@layer layer2 {
p {
color: green;
}
}

没错,如果我们不写那个花括号,这个语句就是用来确定优先级的,排越后的优先级越高,效果如下:

这时候眼尖的帅哥就会问了,你这不对啊,如果不用 @layer ,在相同的权重下也是后面的生效啊。

没错,所以我们可以再使用以下例子,这次我们把 layer2 的定义放 layer1 前面:

1
2
3
4
5
6
7
8
9
10
11
12
13
@layer layer1, layer2;

@layer layer2 {
p {
color: green;
}
}

@layer layer1 {
p {
color: red;
}
}

效果如下:

可以看到依然应用的是 layer2 的绿色样式。

或许这样还是不那么明显,我们可以将 layer1 的样式优先级提高,效果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@layer layer1, layer2;

@layer layer2 {
p {
color: green;
}
}

@layer layer1 {
p.text {
color: red;
}
}

如果不使用 @layer ,那么 p.text 的优先级是比 p 要高的,文本的颜色就是红色,而在上面的代码下,应用的是 layer2 的绿色,效果如下:

那要是不写 @layer layer1, layer2 会是什么结果呢,我们在 layer1 内使用较高的优先级以排除不使用 @layer 下的影响:

1
2
3
4
5
6
7
8
9
10
11
@layer layer1 {
p.text {
color: red;
}
}

@layer layer2 {
p {
color: green;
}
}

效果如下:

可以发现 layer2 的 p 生效了,这其实是因为 @layer layerName {} 也是一种定义层级优先级的方式,越后写的层级优先级越高。

这时候聪明的小伙子就要问了,要是我在后面加上 @layer layer2, layer1 能让 layer2 的层级优先级比 layer1 高吗?事实是不能,层级顺序只要确定,就无法再变更,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@layer layer1 {
p.text {
color: red;
}
}

@layer layer2 {
p {
color: green;
}
}
/* 企图让 layer1 高于 layer2 */
@layer layer2, layer1;

效果如下:

当然这其实也符合逻辑,如果后写能覆盖的话,那其实就和之前的样式优先级问题一样了,大家(每个库)都想尽可能的往后引入,以避免被覆盖,这就违背提出 @layer 的理念了。

其他

如果某个样式不在任何一个层级中,那么它和层级中的样式是如何比较的?

我们先让 layer1 放非 layer 样式之后,代码如下:

1
2
3
4
5
6
7
8
9
p {
color: green;
}

@layer layer1 {
p {
color: red;
}
}

效果如下:

可以看到生效的是绿色,这是因为非 layer 的样式就是高于 layer 内的样式的,你可以理解为没有被 @layer 包裹的样式默认生成了一个匿名的 layer ,这个 layer 位于所有定义的 layer 之后。

解决

在了解上面的东西后,为了解决 unocss 和 vuetify 的样式冲突问题,我们要知道问题在哪。

首先,vuetify 在 3.6.0 引入了 css layer ,所有的样式都被定义在了层级里面,vuetify 定义了如下的层级:

1
@layer vuetify-core, vuetify-components, vuetify-overrides, vuetify-utilities, vuetify-final;

unocss 在默认情况下并不会生成 layer ,需要我们手动配置:

在更新依赖之前,我们在 main.ts 编写的导入顺序是:

1
2
3
4
// ...
import 'virtual:uno.css'
import 'vuetify/styles'
// ...

根据相同优先级下后写的覆盖前写的,所以这段代码在非 layer 样式下的表现几乎正常,虽然在 unocss 中我们并没有关闭 reset 的样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { defineConfig, presetWind4 } from 'unocss'

export default defineConfig({
presets: [
presetWind4({
prefix: 'wind-',
// 并没有去除 unocss 的 reset
// preflights: {
// reset: false,
// },
}),
],
})

但是当 vuetify 改为 layer 样式后,问题就来了,由于 unocss 依然是非 layer 样式,导致 unocss 样式优先级全部高于了 vuetify 的样式,而 我们又没关闭 unocss 的 reset 样式,导致了组件的样式不正确,最明显的就是边框全部归零,效果如下:

ok,知道了这个问题后,我们就大体知道如何解决了,首先是去掉 unocss 的 reset 样式注入,因为 vuetify 其实已经自带了一套 reset 了,并且在这个项目中,我们主要是使用 unocss 工具类多一些,比如 flex flex-col h-full 等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { defineConfig, presetWind4 } from 'unocss'

export default defineConfig({
presets: [
presetWind4({
prefix: 'wind-',
// 去除 unocss 的 reset
preflights: {
reset: false,
},
}),
],
})

接着我们要让 unocss 也生成基于 layer 的样式,这样可以保证我们可以随时调整每个 layer 的优先级

1
2
3
4
5
6
7
import { defineConfi } from 'unocss'

export default defineConfig({
outputToCssLayers: {
cssLayerName: (layer) => `unocss-${layer}`,
},
})

这样子 unocss 就会把样式包裹在 layer 中。

经过这个修改后,基本上 unocss 的样式就不会影响到 vuetify 了,从导入顺序看, vuetify 的 layer 优先级都是高于 unocss 。

如果我们想让 unocss 的样式高于 vuetify ,那么也无需修改引入顺序了,我们可以创建一个 layer.css 来定义层级顺序,在 main.ts 开头就引入这个文件,代码如下:

1
2
3
4
5
6
7
// 根据层优先级定义后无法变更的特性,确保定义层优先级的 css 在第一个引入
import './layer.css'
//...

// 无需 vuetify 和 unocss 的引入顺序
import 'virtual:uno.css'
import 'vuetify/styles/core'
1
2
/* 定义这些层的顺序(优先级) */
@layer unocss-base, unocss-properties, unocss-theme, unocss-default, unocss-icons, vuetify-core, vuetify-components, vuetify-overrides, vuetify-utilities, vuetify-final;

后记

最后,由于 vuetify 自身其实也是有一套工具类的,但是我们几乎用不到,所以我们只引入 core 层的样式即可,vuetify 的组件通过插件自动导入对应样式:

1
2
import 'virtual:uno.css'
import 'vuetify/styles/core'