个人使用CSS的过程中,有两个很困扰的问题就是作用域和优先级,还有一个主观问题,就是CSS的语义化
我之前写CSS的时候就在感慨好乱啊,又没有办法,难道真的没有好的方法吗?本文就是想寻求CSS的最佳实践
- 先来解析一下CSS都有哪些问题,一般的解决办法是什么,有没有什么其他的解决办法,哪一种更好?
- 听闻CSS有好多种模块化方案,我个人比较喜欢BEM,先介绍一下BEM
- 再解析一下其他的模块化方案,他们也有可借鉴之处,也可以融入到BEM中
CSS作用域
你根据需求修改订单列表的样式,把background修改为blue
.list {
// background: grey;
background: blue;
}
当你以为没有问题的时候,你发现了用户列表的背景也变成了blue
由于CSS只有全局作用域,订单列表和用户列表都使用了list修饰,所以修改为以下代码
.order .list {
// background: grey;
background: blue;
}
或者以下代码
.fuckyoulist {
// background: grey;
background: blue;
}
唯一性
其实CSS作用域的问题也好解决,也不好解决,只要保证class的唯一性就可以了
使用hash属性
Vue Loader的Scoped CSS就是采用这种方案
BEM
通过Block的命名的唯一(不强制)来保证唯一性
从规则上来讲,Bolck命名必须是不同的;如果相同,则表示同一个Block
使用后代选择器做限制
上面的解决办法就是使用了后代选择器做限制,这个办法很多时候可以解决问题,不过麻烦在于要多添加一个父类,甚至有可能需要修改HTML结构,而且还会引入优先级的陷阱
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
/* 内部样式 */
.account-list .item {height:150px; background:white; color:red;}
.contact-list .item {height:100px; background:gray;}
</style>
</head>
<body>
<div id="id" class="account-list">
<div class="item">
<span>account-list元素</span>
</div>
<div class="contact-list">
<div class="item">
<span>contact-list元素</span>
</div>
</div>
</div>
</body>
</html>
这里的.contact-list .item
没有设置color
,但是使用了.account-list .item
的color
样式,即使使用了命名空间,也没有避免样式污染,而这种情况在组件化的今天,很容易发生。根本原因就是item
类名不够特殊。
个人观点:
- 后代选择器,不是将一个类选择器作为另一个类选择器的后代来使用的,这样做意义不大,完全可以分开定义
- 将元素选择器作为除了通配选择器以外的选择器的后代,倒是很方便的用法
CSS优先级
你根据需求修改订单列表的样式,把background修改为blue
.list {
background: blue;
}
发现并没有生效,排查了很久,发现了以下代码
.wrapper .list {
background: grey;
}
你修改为以下代码
.wrapper .list {
// background: grey;
background: blue;
}
如果这时候起作用了,也没什么其他问题了,那算你幸运,如果同时发生了作用域问题,其他页面也引用了这个样式,那么就要再次修改了,修改为以下代码
.order .wrapper .list {
// background: grey;
background: blue;
}
其实优先级的问题加重都是由于使用了后代选择器而引入的,想象一下,如果都是类选择器,就不会有很严重的优先级问题
为什么要使用后代选择器?
我仔细想了一下,好像很少的场景必须使用后代选择器才能解决,欢迎提问和反驳
有一种情况使用后代选择器会更方便,但也不是无法替代的
.wrapper ul li {
color: grey;
}
这样使用虽然方便,不过要注意,因为(元素选择器)影响范围很广
尽量使用类选择器?
只要能使用类选择器解决的,就是用类选择器
可以使用伪类选择器,伪元素选择器,因为类选择器很难模拟同样的效果
尽量不使用后代选择器,子代选择器,兄弟选择器,ID选择器,因为他们会加重优先级问题
BEM
BEM,三个字母分别代表 Block、Element、Modifier。
他们提出了一套非常类似的代码原则。他们的核心概念是 —— 块(block)(Nicole 称之为“物体(object)”)由子元素(element)构成,并且可以修改(modified)(或“主题化”)。
命名约定
BEM 所做的另一件事是定义了非常严格的命名约定:.block-name__element-name--modifier-name
不嵌套CSS
嵌套选择器扰乱了优先度,使得重用代码变得更加困难。例如,只需使用 .btn__price
而不是 .btn .btn__price
。
这个原则不出问题是因为严格的命名约定。我们曾经使用嵌套选择器将它们隔离在命名空间的上下文中。而 BEM 的命名约定本身就提供了命名空间,因此我们不再需要嵌套。即使 CSS 的根级别的所有内容都是单个类,但这些名称的具体程度足以避免冲突。
一般来说,选择器可以在没有嵌套的情况下生效,就不要嵌套它。 BEM 允许此规则的唯一例外是基于块状态或其修饰符的样式元素。例如,可以使用 .btn__text
然后用 .btn--orange .btn__text
来覆盖应用了修饰符按钮的文本颜色。
OOCSS
上下文无关
首先,无论你把它放在哪里,一个对象都应该看起来无差别,不应根据对象的上下文设置对象的样式。
例如,不是将侧边栏中的所有按钮都设置为橙色,将主区域中的所有按钮设置为蓝色,而是应该创建一个蓝色的按钮类,以及一个橙色的 modifier。这样做橙色按钮可以在任何地方使用,它们没有被绑定在侧边栏上,它们只是你的按钮样式之一。
使用Class
使用 class 来命名对象及其子元素,这样可以在不影响样式的情况下修改 HTML 标签。
她不希望 CSS 由 HTML 标签来确定,这样的话如果将标题从“h1”更改为“h4”,则不必更新 CSS。无论选择哪个标签,该标题应该有一个固有的 class。例如,你的导航应该类似于 .site-nav
而不是 #header ul
。
不使用ID
根据定义,ID 是唯一的。因此,如果在对象上设置 ID,则无法在同一页面上重复使用它,缺少了模块化对象的要点。
SMACSS
类别(Categories)
以下是他为 CSS 系统可能包含的规则定义的类别:
- 基础(Base) 规则是HTML元素的默认样式,如链接,段落和标题。
- 布局(Layout) 规则将页面分成几个部分,并将一个或多个模块组合在一起。它们只定义布局,而不管颜色或排版。
- 模块(Module)(又名“对象”或“块”)是可重用的,设计中的一个模块。例如,按钮,媒体对象,产品列表等。
- 状态(State) 规则描述了模块或布局在特定状态下的外观。通常使用 JavaScript 应用或删除。例如,隐藏,扩展,激活等。
- 主题(Theme) 规则描述了模块或布局在主题应用时的外观,例如,在 Yahoo Mail 中,可以使用用户主题,这会影响页面上的每个模块。(这非常适用于像雅虎这样的应用程序,但大多数网站都不会使用此类别。)
命名约定前缀
下一个原则是使用前缀来区分类别,他喜欢 BEM 明确的命名约定,但他还希望能够一目了然地看出模块的类型。
-
l-
用作布局规则的前缀:l-inline
-
m-
用作模块规则的前缀:m-callout
-
is-
用作状态规则的前缀:is-collapsed
自我总结实践
- 尽量使用类选择器
- 不使用ID选择器,不能复用
- 不嵌套CSS,会加重优先级问题
- 根据分类为命名添加前缀,提高可读性
示例
<template>
<div
class="express-info__wrapper">
<van-steps
direction="vertical"
:active="0"
>
<van-step
v-for="(item, index) of expressInfo"
:key="index">
<template v-slot:active-icon>
<van-icon
name="checked"
color="#0099ff"
size="24"
></van-icon>
</template>
<template v-slot:inactive-icon>
<van-icon
name="checked"
color="#d8d8d8"
size="24"
></van-icon>
</template>
<div class="express-info-item__wrapper">
<p class="express-info-item__title">
{{ item.firstLine }}
</p>
<p class="express-info-item__content">
{{ item.secondLine }}
</p>
<p class="express-info-item__content">
{{ item.time }}
</p>
</div>
</van-step>
</van-steps>
</div>
</template>
<script>
export default {
name: 'ExpressInfo',
data() {
return {
expressInfo: [
{
firstLine: '已签收',
secondLine: '快件已在合肥市高新区本人签收',
time: '2020-05 12:12',
},
{
firstLine: '派送中',
secondLine: '合肥市高新区钱三[13509999990]正在派件',
time: '2020-05 11:12',
},
{
firstLine: '',
secondLine: '快件已在合肥市高新区',
time: '2020-05 10:12',
},
{
firstLine: '已揽件',
secondLine: '合肥市蜀山区王五[13509999991]已揽收',
time: '2020-05 9:12',
},
{
firstLine: '已发货',
secondLine: '包裹正在等待揽收',
time: '2020-05 8:12',
},
],
};
},
}
</script>
<style lang="scss" scoped>
.express-info__wrapper {
background: #ffffff;
padding-left: 16px;
}
.express-info-item__wrapper {
margin-left: 17px;
}
.express-info-item__title {
font-size: 16px;
font-weight:600;
color: #333333;
margin-bottom: 4px;
}
.express-info-item__content {
font-size: 12px;
color: #666666;
margin-bottom: 4px;
}
</style>
后续问题
怎么识别可复用css?
有些css是不能复用的或者很难复用,有些css是可以复用的,不过需要精心设计和维护,怎么区分他们?
不要过早封装,当出现了大量的重复样式,再去封装也不迟
不过要有一个蓝图,最终是形成什么样子的结构,里面包含什么,有什么好处
怎么使用scss变量,使得css变化更容易?
css中的变量一般都是数字和颜色,像素数
需要UI图有一整套规范,比如外边距都是偶数,标题1标题2的字体大小是多少?主要的文字什么颜色,次要的文字什么颜色?
如果UI目前没有这样的规范,那应该怎么做呢?
先写出一些基础的class,例如主要文字,可能会有几个,那就后缀加数字区分,后期去和UI核对,哪些可以修改,尽量统一,之后再抽取出来公共变量,也就是所谓的倒推
- 无论目前是什么样子,都按照UI去实现
- 实现的过程中,区分哪些是主要文字,哪些是次要文字,封装到不通的class中,如果有多个次要文字class,后缀加数字区分,里面的数字,颜色不需要使用变量,后面再修改
- 和UI核对,删除一些不必要的重复class,使样式更加统一
- 整理css,把class中公用的部分抽取出来,比如形成函数,定义变量
参考vant的做法,但是应该把各个组件的变量分散到各个组件中
我理解vant是分了层级,基础变量,组件变量,组件样式;组件变量是由基础变量组合而来的,组件样式是由组件变量组合而来的,
组件样式不再依赖具体的数值,而是依赖组件变量,如果组件样式需要调整,可能直接修改组件样式就可以了,可能需要修改组件变量,看情况,如果改动的样式是组件内部复用的样式,那就需要修改样式变量,否则直接修改组件样式
怎么构建css层次?
- 之前听说过reset.css和normalize.css,查阅了之后,发现normalize.css更符合需要,就只使用normalize.css
- 创建一些独立的布局,函数,mixin等公共样式,可以在页面中使用
- 页面样式需要使用scoped关键字,依然使用BEM规范
- 覆盖组件样式需要视情况而定
层次关系:normalize -> 公共样式 -> 页面样式 -> 组件样式
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。