昨天在群里看到大家对CSS BEM和CSSmodule的讨论,激起了我的好奇心,我也想通过这篇文章向更多人科普一下CSS BEM / CSS Module / scoped 在代码中到底是个啥,他们究竟在做些什么。
方便起见,Scoped和CSS module的内容我主要使用vue作为示例,因为vue cli创建的工程是默认支持以上两种情况的。
为什么会出现这么多名词
首先我们需要了解这句话 —— CSS的影响是全局的。
但是更多时候我们更喜欢他局部生效,所以就出现了上面的几种解决方案,下面是对此的详解。
CSS BEM
更多详情参考:github.com/Tencent/tmt… [规范] CSS BEM 书写规范》
<style>
.模块名-元素名--修饰符{}
.moduleName-elementName--modify {}
.paging-button--prev {}
.paging-button--next {}
</style>
如上所示,CSS BEM (Block - Element - Modify)其实是一种约定俗成的 模块-标签--修饰符 的 CSS 命名方式。
举例:在分页组件(/app/components/paging.vue)中,模块名就是paging
,模块内的每一个元素的class命名都必须是模块名开头,上一页按钮对应的元素名或者内容(可以自己取一个合适的名字)为button
,最终他的class就为paging-button--prev
。
优点:
1. 一种CSS局部样式的实现方案;
2. 可以很清楚的知道样式来源哪个组件;
3. CSS基本都为平级,易于覆盖和修改;
4. 可以尽可能地避免子代元素名称重复,导致的渲染错误;
缺点:
1. 编码麻烦,而且会导致代码量增加,每一个class都增加了长度;
Scoped
我们看到的scoped通常是如下使用的:
<template> <button class=”button” /> </template>
<style scoped>
.button { color: red; }
</style>
最后的渲染实际是这样的:
<style>
.button[data-v-f61kqi1] { color: red; }
</style>
<button class="button" data-v-f61kqi1></button>
scoped实际上是给每个标签加入了一个data属性,每个scoped里的样式渲染的时候,是根据这个标记的data属性渲染,从而实现的局部样式。
穿透scoped
场景 参考文章:www.cnblogs.com/iamsmiling/… 《Vue中的scope样式穿透(转载)》
但是在某些场景下,scoped是不够用的。
比如我们的页面嵌套了第三方的一个组件,我们在css上定位到组件需要修改的class为a,scoped规定我们是不能修改这个class的,这个时候就需要穿透scoped,这种情况可以使用 >>>
连接符(或者 /deep/
)实现。
<style scoped>
外层 >>> 第三方组件 {
样式
}
例如,
.container >>> .a {
color: 'red'
}
</style>
优点:
1. 一种CSS局部样式的实现方案;
2. 使用简单;
3. CSS基本都为平级,易于覆盖和修改;
缺点:
1. 如果父组件和子组件出现了同名的class,父组件的样式会泄露到子组件上;
CSS Module
如何在vue项目里开启 CSS Module,请移步官网:vue-loader.vuejs.org/zh/guide/cs…
参考文章:juejin.cn/post/684490… 《[译] Vue: scoped 样式与 CSS Module 对比》
更多CSS Module技巧,请参考:www.ruanyifeng.com/blog/2016/0… 《CSS Modules 用法教程》
我平时没有用过CSS Module,但是经过这一次的了解,我非常期待在项目中使用它,他的使用如下:
<style module>
.button { color: red }
</style>
乍一看,他在vue中的使用几乎和scoped没有差别,但他非常不同。
- CSS Module生成的CSS如下,类似BEM规范;
<style>
.ComponentName__button__2Kxy {
color: red;
}
</style>
- 他和scope的不同之处,在于页面对于class的引用需要通过
$style
来获取;
<template>
<button :class="$style.button" />
</template>
<style module>
.button {
color: red
}
</style>
以上生成如下代码:
<style>
.ComponentName__button__2Kxy {
color: red;
}
</style>
<button class=”ComponentName__button__2Kxy”></button>
这样乍一看很麻烦,但厉害在这里,可以在 CSS 中定义你的色彩变量的同时将其导出,以供你的组件使用,从此不需要在组件里重复声明了!
<template>
<div>{{ $style.primaryColor }}</div> <!-- #B4DC47 -->
</template>
<style module lang="scss">
$primary-color: #B4DC47;
:export {
primaryColor: $primary-color
}
</style>
修改第三方组件
CSS Modules 允许使用:global(.className)的语法,声明一个全局规则。凡是这样声明的class,都不会被编译成哈希字符串。
其他文章参考:blog.csdn.net/mfwscq/arti… modules怎么修改antd组件的默认样式,修改antd的样式》
如果想修改全局的第三方样式:
:global{
.ant-modal-content{
background-color: transparent;
}
}
如果只想修改制定的第三方样式,那就给模块新增一个class,再修改:
<Modal className="tableBottomPop"></Modal>
:global{
.tableBottomPop{
.ant-modal-content{
background-color: transparent;
}
}
}
优点:
1. 不需要写BEM,但是会生成类似BEM规范的class样式,不会给css增加额外的权重;
2. 可以很清楚的知道样式来自哪个组件,同时可以显示地分辨这个样式是组件自身的还是继承父组件的;
3. 可以复用css中定义的变量;
缺点:
1. 使用稍许复杂,需要通过$style来对每个class进行引用;
总结
以上的三种都是针对CSS局部样式的实现方案,没有太多的孰是孰非,我们根据自己的使用场景进行挑选就好。
选定某一种方案,我们多记住他的“优缺点”,使用好的尽量避免差的就很可以了,实际中可没那么多的大问题“吓人”,总可以找到方法解决的。