《深入解析CSS》模块化CSS

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

《深⼊解析CSS 》模块化CSS
掌握浏览器如何渲染CSS 很重要,了解如何在项⽬中编写和组织CSS 也很重要。

组织CSS 代码使其更易于理解和维护。

模块化CSS (Modular CSS )是指把页⾯分割成不同的组成部分,这些组成部分可以在多种上下⽂中重复使⽤,并且互相之间没有依赖关系。

最终⽬的是:当我们修改其中⼀部分CSS 时,不会对其他部分产⽣意料之外的影响。

我们把样式表的每个组成部分称为模块(module ),每个模块独⽴负责⾃⼰的样式,不会影响其他模块内的样式。

也就是说,在CSS ⾥引⼊了软件封装的原则。

CSS 中没有数据和传统函数的概念,但是有选择器及其命中的页⾯元素。

为了达到封装的⽬的,这些会成为模块的组成部分,并且每个模块都只负责少量的DOM 元素的样式。

有了封装的思想,我们就可以为页⾯上那些彼此分⽴的组件定义模块了。

像导航菜单、对话框、进度条、缩略图,等等。

可以通过为DOM 元素设置⼀个独⼀⽆⼆的的类名来识别每个模块。

同时,每个模块包含⼀系列⼦元素,构建成页⾯上的组件。

模块内部可以嵌套其他模块,最终构成完整的页⾯
开始写模块化样式之前,需要先配置好环境。

每个样式表的开头都要写⼀些给整个页⾯使⽤的通⽤规则,这些规则通常被称为基础样式。

其他的样式是构建在基础样式之上的。

基础样式本⾝并不是模块化的,但它会为后⾯编写模块化样式打好基础:
/*覆盖盒模型*/
:root {
box-sizing: border-box;
}
*,*::before,*::after {
box-sizing: inherit;
}
/*默认字号与字体*/
body {
font-family: Helvetica, Arial, sans-serif;
margin: 0;
}
其他常⽤的基础样式还包括链接的颜⾊、标题的样式、外边距等。

<body>标签默认的外边距很⼩,你可能会考虑将它的外边距去掉。

根据项⽬的实际情况,你也可能想为表单字段、表格和列表等添加⼀些样式。

基础样式应该是通⽤的,只添加那些影响页⾯上⼤部分或者全部内容的样式
选择器应只⽤标签类型或者偶尔⽤⽤伪类选择器
基础样式提供⼀些默认的渲染效果,之后可以很⽅便地根据需要覆盖基础样式
基础样式配置完成以后很少会再修改。

会在基础样式之上构建模块化CSS
基础样式后⾯的内容将主要由各种模块组成
下⾯来创建⼀个短消息通知的模块。

每个模块都需要⼀个独⼀⽆⼆的名称,我们把这个模块叫作“message”

使⽤⼀个类名为message 的div 标记该模块
<div class="message">
Save successful
</div>
.message{
padding:0.8em 1.2em;
border-radius:0.2em;
border:1px solid #265559;
color:#265559;
background-color:#e0f0f2
}
模块的选择器由单个类名构成
这很重要。

对⽐⼀下,如果使⽤⼀个类似于#sidebar .message 的选择器,就意味着这个模块只能⽤在#sidebar
元素内部。

现在没有这些约
束,模块就可以在任意上下⽂中重复使⽤
创建模块不但可以精简代码(减少重复),还可以保证视觉⼀致性。

这样看上去更专业,不会给⼈仓促堆砌的感觉。

⽤户在潜意识⾥也会更容易相信我们的应⽤程序
保持⼀致性确实不错,但有时候需要特意避免⼀致。

对于这个短消息模块,⽐如需要显⽰⼀条报错的消息,这时候应该使⽤红⾊⽽不是之前的蓝绿⾊。

这可以通过定义修饰符来实现
通过定义⼀个以模块名称开头的新类名来创建⼀个修饰符。

例如,消息模块的error修饰符应该叫作message-error。

通过包含模块名称,可以清楚地表明这个类属于消息模块。

下⾯我们为模块创建三个修饰符:成功、警告和错误:
/*基础消息模块*/
.message{
padding:0.8em 1.2em;
border-radius:0.2em;
border:1px solid #265559;
color:#265559;
background-color:#e0f0f2
}
/*1.成功*/
.message-success{
color:#2f5926;
border-color:#2f5926;
background-color:#cfe8c9
}
/*2.警告*/
.message-warning{
color:#594826;
border-color:#594826;
background-color:#e8dec9
}
/*3.错误*/
.message-error{
color:#59262f;
border-color:#59262f;
background-color:#e8c9cf
}
修饰符的样式不需要重新定义整个模块,它覆盖要改变的部分:
<div class="message message-error">
密码错误
</div>
下⾯将实现⼀个按钮模块,其中包含⼤⼩和颜⾊选项的变体。

可以⽤不同的颜⾊为按钮添加视觉意义。

绿⾊代表积极的⾏为,⽐如保存和提交表单;红⾊意味着警告,有利于防⽌⽤户不⼩⼼点击取消按钮:
/*基础按钮模块*/
.button {
padding: 0.5em 0.8em;
border: 1px solid #265559;
border-radius: 0.2em;
background-color: transparent;
font-size: 1rem;
}
/*1.成功的颜⾊变体*/
.button--success {
border-color: #cfe8c9;
color: #fff;
background-color: #2f5926;
}
/*2.危险的颜⾊变体*/
.button--danger {
border-color: #e8c9c9;
color: #fff;
background-color: #a92323;
}
/*1.⼩号字体变体*/
.button--small {
font-size: 0.8rem;
}
/*2.⼤号字体变体*/
.button--large {
font-size: 1.2rem;
}
<button class="button button--large">Read more</button>
<button class="button button--success">Save</button>
<button class="button button--danger button--small">Cancel</button>
双连字符的写法可能看起来有点⼉多余,但当我们开始创建名称很长的模块的时候,⽐如导航菜单或者⽂章摘要,好处就显现出来了。

为这些模块添加修饰符后,类名将如nav-menu--horizontal或者pull-quote--dark。

双连字符的写法很容易区分哪部分是模块名称,哪部分是修饰符。

nav-menu--horizontal和nav--menu-horizontal分别代表了不同的含义。

这样⼀来,即使项⽬⾥有很多名称相似的模块,也很容易分辨它们。

假设我们正在维护⼀个⽹站,⾥⾯有浅⾊调的下拉菜单。

有⼀天⽼板说,⽹页头部的下拉菜单需要改成带⽩⾊⽂本的深⾊调。

如果没有模块化CSS,我们可能会使⽤类似于.page-header.dropdown的选择器来覆盖dropdown类提供的默认颜⾊。

现在要写模块化CSS,这样的选择器是严格禁⽤的。

虽然上⾯写法可以解决需求,但接下来可能会带来很多问题。

下⾯我们来分析⼀下:第⼀,我们必须考虑把这段代码放在哪⾥,是和⽹页头部的样式放在⼀起,还是跟下拉菜单的样式放在⼀起?如果我们添加太多类似的单⼀⽬的的规则,样式之间毫⽆关联,到最后样式表会变得杂乱⽆章。

并且,如果后⾯需要修改样式,你还能想起来它们放在哪⾥吗?
第⼆,这种做法提升了选择器优先级。

当下次需要修改代码的时候,我们需要满⾜或者继续提升优先级。

第三,后⾯可能需要在其他场景⽤到深⾊的下拉列表。

刚才创建的这个下拉列表是限定在⽹页头部使⽤的。

如果侧边栏也需要同样的下拉列表,我们就得为该规则集添加新的选择器来匹配两个场景,或者完整地复制⼀遍样式
第四,重复使⽤这种写法会产⽣越来越长的选择器,将CSS跟特定的HTML结构绑定在⼀起。

例如,如果有个#products-page.sidebar
.social-media div:first-child h3这样的选择器,样式集就会和指定页⾯的指定位置紧紧耦合。

这些问题是开发⼈员处理CSS的时候遭受挫折的根源。

使⽤和维护的样式表越长,情况越糟。

新样式需要覆盖旧样式时,选择器优先级会持续提升。

到后⾯不知不觉地就会发现,我们写了⼀个选择器,其中包含两个ID和五个类名,只是为了匹配⼀个复选框。

在样式表中,元素被各种彼此不相关的选择器匹配,这样很难找到它使⽤的样式。

理解整个样式表的组织⽅式变得越来越困难,你搞不明⽩它是怎样把页⾯渲染成这样的。

搞不懂代码就意味着bug变得常见,可能很⼩的改动就会弄乱⼤⽚的样式。

删除旧代码也不安全,因为你不了解这段代码是⼲什么的,是否还在⽤。

样式表越长,问题就愈发严重。

模块化CSS就是要尝试解决这些问题。

当模块需要有不同的外观或者表现的时候,就创建⼀个可以直接应⽤到指定元素的修饰符类。

⽐如,写.dropdown--dark,⽽不是写成.page-header .dropdown。

通过这种⽅式,模块本⾝,并且只能是它本⾝,可以决定⾃⼰的样式表现。

其他模块不能进⼊别的模块内部去修改它。

这样⼀来,深⾊下拉列表并没有绑定到深层嵌套的HTML结构上,也就可以在页⾯上需要的地⽅随意使⽤。

千万不要使⽤基于页⾯位置的后代选择器来修改模块。

坚决遵守这个原则,就可以有效防⽌样式表变成⼀堆难以维护的代码。

有时模块需要由多个元素组成。

⽐如我们不可能只靠⼀个元素实现下拉菜单或者模态框。

下⾯来创建⼀个更复杂的模块。

这是⼀个媒体对象,这个模块由四个元素组成:div容器、容器包含的⼀张图⽚、标题、正⽂:
/*主容器*/
.media {
padding: 1.5em;
background-color: #eee;
border-radius: 0.5em;
}
/*清除浮动*/
.media::after {
content: "";
display: block;
clear: both;
}
/*图⽚⼦元素*/
.media__image {
float: left;
margin-right: 1.5em;
}
/*正⽂⼦元素*/
.media__body {
overflow: auto;
margin-top: 0;
}
/*正⽂内的标题*/
.media__body > h4 {
margin-top: 0;
}
<div class="media">
<img class="media__image" src="01.jpeg">
<div class="media__body">
<h4>Strength</h4>
<p>
Strength training is an important part of
injury prevention. Focus on your core&mdash;
especially your abs and glutes.
</p>
</div>
</div>
效果如图:
创建模块的变体。

⽐如把图⽚从左浮动改成右浮动
.media--right>.media__image{
float: right;
}
<div class="media media--right">
...
</div>
避免在模块选择器中使⽤通⽤标签名
我们在媒体模块中使⽤了选择器.media__body > h4来匹配标题元素。

这么做是允许的,因为<h4>标签就是⽤来标识⼀个次要标题的。

同样的⽅式也可以⽤在带列表的模块上,相⽐为列表⾥的每个项⽬都添加menu__item类名,使⽤menu > li匹配菜单项简单多了
我们应该避免使⽤基于通⽤标签类型的匹配,类似于.page-header >span的选择器太宽泛了。

最初建⽴模块的时候,可能只是⽤span标签做⼀件事,但谁也说不准以后会不会出于其他⽬的再添加第⼆个span。

后⾯再为span追加类名就⽐较⿇烦了,因为我们需要在HTML标记中找到所有⽤到模块的地⽅,全部改⼀遍。

每个模块应该只做⼀件事情,⽤名称简洁明了地概括出它们的⽬标。

有的模块是为了版⾯布局,有的是为了编写体例。

当模块想要完成不只⼀件事的时候,我们应该考虑把它拆分成更⼩的模块。

做⼀个下拉菜单来演⽰⼀下:
当我们需要使⽤并来描述模块职责的时候,思考⼀下是不是在描述两种甚⾄更多的职责。

如果是,则需要为每个职责分别定义模块。

这是模块封装的⼀个⾮常重要的原则,我们把它叫作单⼀职责原则。

尽可能把多种功能分散到不同的模块中,这样每个模块就可以保持精炼、聚焦,并且容易理解。

拆分不同模块的职责
第⼀个模块叫作下拉,其中包含⼀个控制容器可见性的按钮。

换句话说,这个模块负责展⽰和隐藏容器。

我们也可以描述按钮的外观和代表⾏为的⼩三⾓来阐述模块的细节,这些细节都是从属于⾸要职责的,因此可以描述
第⼆个模块叫作菜单,是放置链接的列表。

把菜单模块的⼀个实例放⼊下拉模块的容器内,就可以构成完整的界⾯。

⽤两个模块构造⼀个下拉菜单
<!-- 下拉模块 -->
<div class="dropdown">
<!-- 下拉的触发按钮 -->
<button class="dropdown__toggle">Main Menu</button>
<!-- 抽屉⼦元素 -->
<div class="dropdown__drawer">
<!-- 菜单模块 -->
<ul class="menu">
<li><a href="/">Home</a></li>
<li><a href="/coffees">Coffees</a></li>
<li><a href="/brewers">Brewers</a></li>
<li><a href="/specials">Specials</a></li>
<li><a href="/about">About us</a></li>
</ul>
</div>
</div>
<script type="text/javascript">
(function () {
var toggle = document.querySelector('.dropdown__toggle');
// 点击按钮触发类
toggle.addEventListener('click', function (event) {
event.preventDefault();
var dropdown = event.target.parentNode;
dropdown.classList.toggle('is-open');
});
}());
</script>
这⾥使⽤了双下划线标记,表⽰触发器和抽屉是下拉模块的⼦元素。

点击触发器可以显⽰或者隐藏抽屉元素。

JavaScript代码为下拉模块的主元素添加或者移除is-open类,以此来实现这个功能。

/*下拉模块*/
.dropdown {
display: inline-block;
position: relative;
}
.dropdown__toggle {
padding: 0.5em 2em 0.5em 1.5em;
border: 1px solid #ccc;
font-size: 1rem;
background-color: #eee;
}
/* 绘制三⾓形 */
.dropdown__toggle::after {
content: "";
position: absolute;
right: 1em;
top: 1em;
border: 0.3em solid;
border-color: black transparent transparent;
}
/* 初始时隐藏抽屉 */
.dropdown__drawer {
display: none;
position: absolute;
left: 0;
top: 2.1em;
min-width: 100%;
background-color: #eee;
}
.dropdown.is-open .dropdown__toggle::after {
top: 0.7em;
border-color: transparent transparent black;
}
/* 触发isopen类显⽰ */
.dropdown.is-open .dropdown__drawer {
display: block;
}
/* 菜单模块 */
.menu {
padding-left: 0;
margin: 0;
list-style-type: none;
border: 1px solid #999;
}
.menu > li + li {
border-top: 1px solid #999;
}
.menu > li > a {
display: block;
padding: 0.5em 1.5em;
background-color: #eee;
color: #369;
text-decoration: none;
}
.menu > li > a:hover {
background-color: #fff;
}
在模块⾥使⽤定位
这是我们第⼀个使⽤定位的模块,其中创建了模块⾃⼰的包含块(主元素的position:relative)。

绝对定位的元素(抽屉元素和::after伪元素)就是基于同⼀个模块内的位置来定位的。

应该尽量让需要定位的元素关联到同⼀个模块内的其他元素。

只有这样,我们把模块放在另⼀个有定位的容器⾥的时候,才不会弄乱样式。

状态类
is-open类在下拉模块中有特定的⽤途。

我们在模块⾥使⽤JavaScript动态地添加或移除它。

它也是状态类(state class)的⼀个⽰例,因为它代表着模块在当前状态下的表现。

按照惯例,状态类⼀般以is-或者has-开头。

这样状态类的⽬的就会⽐较明显,它们表⽰模块当前状态下的⼀些特征或者即将发⽣的变化。

再举⼀些状态类的⽰例,⽐如is-expanded、is-loading或者has-error等。

这些状态类具体会表现成什么样⼦取决于使⽤它们的模块。

菜单模块
每个<li>都是菜单模块的⼦元素,所以没必要为每个元素添加双下划线类,直接使⽤后代选择器.menu > li已经⾜够明确了。

菜单模块是完全独⽴的,不依赖于下拉模块。

这使得代码更简单,因为我们不需要理解在这个模块之前先搞懂另⼀个,也有助于更加灵活地复⽤模块。

所有的预处理器(⽐如Sass或者LESS)都提供了把分散的CSS⽂件合并成⼀个⽂件的功能。

我们可以⽤多个⽂件和多个⽬录来组织样式,最后提供⼀个⽂件给浏览器。

这样可以减少浏览器发起的⽹络请求数,开发者也可以把代码⽂件拆分成易于维护的⼤⼩。

如果你正好在使⽤某种预处理器,强烈建议把CSS⾥的每个模块都放在各⾃对应命名的⽂件⾥,并按实际需要将这些⽂件组织到不同⽬录中。

然后创建⼀个主样式表,引⼊所有的模块。

这样⼀来,你想修改某个模块时就不必到⼀个冗长的样式表⾥⾯搜索了,因为很清楚去哪⼉找它。

你可以创建⼀个main.scss⽂件,⾥⾯只包含@import语句,如下所⽰:
预处理器会从base.scss中引⼊基础样式,并从每个模块⽂件引⼊相应的模块样式,然后输出⼀个包含所有样式的样式表⽂件。

这样每个模块都单独拥有⼀个便于维护的⽂件。

模块的命名应该有意义,⽆论使⽤场景是什么。

同时也要避免使⽤简单地描述视觉效果的名称。

把这个模块叫作“带图⽚的灰盒⼦”看上去⽐较通⽤⼀些,但是如果之后要改成浅蓝⾊背景呢?或者重新设计⽹站呢?这样的名称就不能⽤了
我们应该换⼀种思路,思考模块代表什么含义。

“媒体模块”这个名称就很恰当,它代表了⼀种图⽂混排的版式。

模块要适⽤于各种不同场景,⽽其名称应该简单易记。

当⽹站有很多页⾯的时候,我们可能会多次⽤到某个模块。

⽬前,已经实现了消息模块、媒体模块、下拉模块和菜单模块。

⼀些⽐较好的模块名称包括⾯板(panel)、警告(alert)、可折叠的部分(collapsible-section)、表单控制项(form-control)等。

如果从⼀开始就对⽹站的整体设计有全⾯的了解,会有助于命名。

例如,你可能觉得有两个UI元素都可以叫作板块(tile),然⽽它们毫不相关,这时候就应该更明确地命名它们(⽐如媒体板块和标题板块)。

有些⼈强制使⽤两个词来命名每个模块,这样就可以避免模块指代不明确。

为模块的变体类命名的时候,应该遵守同样的原则。

例如,如果已经有按钮模块了,就不应该使⽤button--red和button--blue命名红⾊和蓝⾊变体⼦类。

⽹站设计在将来有可能会改变,你不知道这些按钮的颜⾊会不会也跟着变化。

应该使⽤⼀些更有意义的名称,⽐如button--danger和button--success。

有时候需要⽤⼀个类来对元素做⼀件简单明确的事,⽐如让⽂字居中、让元素左浮动,或者清除浮动。

这样的类被称为⼯具类(utility class)。

从某种意义上讲,⼯具类有点像⼩号的模块。

⼯具类应该专注于某种功能,⼀般只声明⼀次。

⼯具类是唯⼀应该使⽤important注释的地⽅。

事实上,⼯具类应该优先使⽤它。

这样的话,不管在哪⾥⽤到⼯具类,都可以⽣效。

/*在容器内实现⽂字居中*/
.text-center {
text-align: center !important;
}
/*左浮动*/
.float-left {
float: left;
}
/*清除浮动*/
.clearfix::before,
.clearfix::after {
content: " ";
display: table;
}
.clearfix::after {
clear: both;
}
/*隐藏某个元素*/
.hidden {
display: none !important;
}
⽬前,在模块化CSS的基础上发展建⽴了⼀些新的⽅法论。

这些⽅法论并不是以任何库或者技术的形式出现的,但确实为开发者组织CSS代码提供了⼀些引导。

这些实践对于CSS领域具有⾥程碑意义。

值得花时间研究⼀下其中⽐较重⼤的⼏个。

有的⽐较简单,只提供了⼀些编码指导;有的⽐较严格,硬性规定了样式代码的组织形式。

每种⽅法论都有⾃⼰的术语和命名规范,但最终都是为了实现CSS模块化。

OOCSS——⾯向对象的CSS,由Nicole Sullivan创建。

SMACSS——可扩展的、模块化CSS架构,由JonathanSnook创建。

BEM——块(Block)、元素(Element)和修饰符(Modifier),由Yandex公司提出。

ITCSS——倒三⾓形CSS,由Harry Roberts创建。

OOCSS仅是基于⼀些引导原则,ITCSS对类的命名和样式归类有明确的规则,SMACSS和BEM则介于两者之间。

SMACSS增加了布局样式的部分,⽤来处理页⾯主要区域的布局(侧边栏、页脚、⽹格系统等)。

ITCSS则进⼀步将类别分为七个层。

相关文档
最新文档