背景:经常在UI框架进行样式穿透的时候,会发生不生效的情况,既会是因为选择器优先级问题或者穿透写法问题,这种比较好理解,提升优先级调整写法就好,但在vue中有时候需要将scoped去掉才能生效,有时候又不能去掉scoped,有时候因为写法的问题导致不生效【需要用一个大盒子包裹】,整理下具体原因,以后遇到其他场景再逐步完善。
省流核心遵旨:
- 去掉scoped样式就变成全局样式了
- 添加:deep()是把其属性选择器放在最前面
- 第三方UI组件只会为根元素添加 data-v-xxx 属性
- 包一层大盒子实现属性嵌套
scoped与样式穿透的爱恨情仇
1. scoped的作用以及原理
作用:避免样式污染
不加scoped,书写的样式作用于全局,加了的话样式仅针对当前组件生效
原理:
每个配置了scoped的组件分配一个唯一哈希,通过css属性选择器实现域划分
先看案例:
// App.vue
<template>
<RouterView></RouterView>
</template>
<style scoped>
</style>
// layout.vue
<template>
<div class='AdminPage'>
<div class='app-header-box'>
<AppHeader/>
</div>
<div class='app-content-box'>
</div>
</div>
</template>
<style scoped>
.AdminPage{
position: relative;
width: 100%;
background-color: #F3F2F2;
}
.AdminPage .app-header-box{
position: fixed;
width: 100%;
height: 48px;
z-index: 100;
}
.AdminPage .app-content-box{
}
</style>
添加了scoped的组件,最终渲染效果:
(1) 每个添加了scoped的组件渲染出来都会附带一个唯一的属性data-v-xxx;
(2) 一个组件中的所有标签都会带上同样的data-v-xxx属性;
(3) 子组件会带上父组件的data-v-xxx属性;
(4) 在使用第三方的 UI 库时,只会为根元素添加 data-v-xxx 属性,子元素中则不会添加
2. 到底deep()做了什么?
先说结论::deep() 函数会把属性选择器放在最前面
3. 为什么有时候穿透需要多包裹一层大盒子
案例:father.vue嵌套子组件son.vue
// father.vue
<template>
<div>father</div>
<Son />
</template>
<script setup>
import Son from "./Son.vue"
</script>
<style lang="less" scoped>
div{
color: red;
}
:deep(.second-row){
color: yellow;
}
</style>
// son.vue
<template>
<div>111</div>
<div class="second-row">222</div>
<div>333</div>
</template>
<script></script>
<style scoped>
.second-row{
color: green
}
</style>
现象: 按照上述书写,穿透样式并未生效,仅子组件样式生效
这是因为并没有生成嵌套关系,父组件中由于添加scoped注册的样式穿透是针对date-v-father这一前置条件下的,也就是:
[data-v-c61e7f05] .second-row{
color: blue;
}
但是,目前的dom结构下data-v-c61e7f05
下面并没有second-row
的类名
所以需要调整,父组件使用一个大盒子包裹起来,完成嵌套,即:
// father.vue
<template>
<div>
<div>father</div>
<Son />
</div>
</template>
<script setup>
import Son from "./Son.vue"
</script>
<style lang="less" scoped>
div{
color: red;
}
:deep(.second-row){
color: blue;
}
</style>
调整后实际的效果:
也可以在father组件上仅在son组件上包裹一层大盒子,是一样的原理
<template>
<div>father</div>
<div>
<Son />
</div>
</template>
4. 为什么去掉scoped样式穿透有时生效,有时不生效
核心遵旨
- 去掉scoped样式就变成全局样式了
- 添加:deep()是把其属性选择器放在最前面
- 第三方UI组件只会为根元素添加 data-v-xxx 属性
4.1 自定义组件穿透案例
// father.vue
<template>
<div>
<div>father</div>
<Son />
</div>
</template>
<script setup>
import Son from "./Son.vue"
</script>
<style scoped>
div{
color: red;
}
:deep(.second-row){
color: blue;
}
</style>
// son.vue
<template>
<div>111</div>
<div class="second-row">222</div>
<div>333</div>
</template>
<script></script>
<style scoped>
.second-row{
color: green
}
</style>
4.1.1【添加scoped,使用穿透】:穿透样式生效
原因分析:
4.1.2【移除scoped,使用穿透】:穿透样式未生效
原因分析:由于没有了属性, :deep作用.second-row 没有属性可提,子组件定义样式生效,“穿透无效”
这里并不是说父组件中 :deep(.second-row)没有属性选择器,就不提属性,将.second-row{color:blue}提升为全局,而是此条定义无效
4.1.3【添加scoped,不使用穿透】:子组件样式生效
原因分析:父组件定义的样式带了属性,但优先级没有子组件定义的高【按照选择器的计算是优先级相同的,是因为子组件样式后加载?】
注意:这里如果子组件移除掉scoped,子组件的优先级降低,就是父组件样式生效
4.1.4【不添加scoped,不使用穿透】:子组件样式生效
原因分析:父组件定义的是全局,但优先级没有子组件定义的高
4.2 UI封装组件穿透案例
案例:
<template>
<div>
<div>father</div>
<el-input class="ipt"></el-input>
</div>
</template>
<script setup>
</script>
<style scoped>
div{
color: red;
}
.el-input{
width: 300px;
}
.ipt .el-input__wrapper {
background-color: red;
}
</style>
4.2.1【添加scoped,不穿透】:el-input样式生效,.ipt .el-input__wrapper定义样式不生效
原因分析:在scoped中定义是包括data-v-xxx属性选择器的,而UI框架中,只有外层有属性
.ipt .el-input__wrapper[data-v-76ab893a] {
background-color: red;
}
4.2.2【添加scoped,使用穿透】:el-input样式生效,.ipt .el-input__wrapper定义样式生效
原因分析:使用穿透,可以将属性提前
.ipt[data-v-76ab893a] .el-input__wrapper {
background-color: red;
}
4.2.3【移除scoped,使用穿透】:el-input样式生效,.ipt .el-input__wrapper定义样式不生效
原因分析:父组件无scoped,穿透定义无效
4.2.4【移除scoped,不使用穿透】:el-input样式生效,.ipt .el-input__wrapper定义样式生效
原因分析:提升至全局样式,全局生效
5. 样式穿透的一些写法:
写法有:::v-deep
,>>>
,:deep()
,/deep/
具体区分:
如果你使用的是css
,没有使用css预处理器,则可以使用>>>
,/deep/
,::v-deep
。
如果你使用的是less
或者node-sass
,那么可以使用/deep/
,::v-deep
都可以生效。
如果你使用的是dart-sass
,那么就不能使用/deep/
,而是使用::v-deep
才会生效。
但是如果你是使用vue2.7
以上版本以及包括vue3
,::v-deep
也会生效,但是会有警告