【Vue3】29-CSS 新特性

193 阅读3分钟

1. scoped 与 样式穿透 :deep()

1.1 scoped 原理

vue 中的 scoped 通过在 DOM 结构以及 css 样式上加唯一的标记 data-v-hash 的方式,达到样式私有化的目的

总结一下 scoped 三条渲染规则

1. 给 HTML 的 DOM 节点加一个不重复的 data 属性(形如:data-v-123)来表示他的唯一性
2. 在每句 css 选择器的末尾(编译后生成的 css 语句)加一个当前组件的 data 属性选择器(如:[data-v-123])来私有化样式
3. 如果组件内部包含有其它组件,只会给其它组件的最外层标签加上当前组件的 data 属性

例如:

在 App.vue 里使用了自定义的 Deep.vue 组件,在 Deep 里面引用了 Element-UI 的 el-input 组件

可以看到:只有最外层 div(class="el-input") 上有 [data-v-hash] 属性,而往下的标签是没有的

image.png

那么当选中 .el-input 类设置样式(scoped 情况下)时

image.png

浏览器会解析成如下 CSS 代码,类选择+属性选择,保证全局的唯一性

image.png

页面效果如下,虽然是设置了红色背景,但被子元素覆盖了,不是我们想要的效果

image.png

1.2 样式穿透

假如我们直接给 el-input 组件的最本质的 input 标签(class="el-input__inner")设置背景色,会发现实现不了

image.png

因为在 CSS 中它最终被解析成这样

image.png

这个属性 [data-v-75b2bfc6]是它父亲的,但根据 scoped 的第二条渲染规则,它被放到了选择器的最后面,然而 .el-input__inner 类所属标签身上没有这个属性,所以选不中原本的那条标签

为了解决这个问题,就需要用到样式穿透了

样式穿透的作用就是避免 浏览器自动生成的唯一标记属性 总是放到选择器的最后面,而导致设置 css 样式失效。多用于修改 组件库中 组件深层次标签 的样式

具体使用

/* 用 :deep() 包裹标签的类名,然后设置样式,就可以实现这个属性穿透到 [data-v-hash] 的后面 */
:deep(.el-input__inner){
    background-color: red;
}

解析结果

image.png

页面效果,成功实现

image.png

2. 插槽选择器 :slotted

在父组件(App.vue)中引入子组件(Slotted.vue)

<template>
  <slotted>
    <h1 class="a">插槽选择器</h1>
  </slotted>
</template>

<script setup lang="ts">
import Slotted from './components/Slotted.vue'
</script>

然后在子组件中写插槽中的样式时,会发现不起作用

<template>
    <slot></slot>
</template>

<style scoped>
.a {
    color: red;
}
</style>

image.png

这时候就需要另一个选择器(:slotted())来表明这个选择器(.a)是插槽标签的

<template>
    <slot></slot>
</template>

<script setup lang="ts">

</script>

<style scoped>
:slotted(.a){
    color: red;
}
/* .a {
    color: red;
} */
</style>

这时候就起作用了

image.png

3. 全局选择器 :global

与上面两个选择器的用法类似,:global(css选择器) 这样写就可以在任何地方的 <style scoped> 写全局的样式

4. 动态 CSS:v-bind(响应式数据)

可以在 css 中应用 v-bind 绑定响应式的数据,这样就可以在 js 中控制 css

字符串写法

<template>
  <h3 class="title">动态 CSS</h3>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const titleColor = ref('red')
</script>

<style scoped>
.title {
  color: v-bind(titleColor);
}
</style>

对象写法

<template>
  <h3 class="title">动态 CSS</h3>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const titleStyle= ref({
  color: 'red'
})
</script>

<style scoped>
.title {
  /* 此处一定要写 '' ,不然会报错 */
  color: v-bind('titleStyle.color');
}
</style>

5. CSS module

<template>
  <!-- 字符串写法 和 数组写法,前缀默认都为 $style -->
  <h2 :id="$style.title" :class="[$style.bacc, $style.border]" >CSS module</h2>
</template>

<script setup lang="ts">

</script>

<style module>
#title {
  color: red
}
.bacc {
  background-color: aqua;
}
.border {
  border: 2px solid orange;
}
</style>

可以给 module 加上 key,表示这个 module 的名称

<template>
  <!-- 自定义了一个 module 名称,为 mod -->
  <h2 :id="mod.title" :class="[mod.bacc, mod.border]" >CSS module</h2>
</template>

<script setup lang="ts">

</script>

<style module="mod">
#title {
  color: red
}
.bacc {
  background-color: aqua;
}
.border {
  border: 2px solid orange;
}
</style>

效果是一样的

image.png

同时,在 ts 中提供了一个 hook useCssModule() 来获取 css 模块

<script setup lang="ts">
    import { useCssModule } from 'vue';
    const mod = useCssModule('mod')
    console.log(mod)
</script>

打印结果如下:

image.png

一般是在 tsx 中使用这种模块化的方式比较多