6. 模块化设计
6.1 样式的作用域──页面重构中的模块化设计(一)
模块化设计我已经提过很多了,都是跟模块化相关的,不过之前一直没有讲到具体实现方面的内容,只是一些思维。这次重点讲一下实现方面的内容,权当到目前为止我对模块化的一些总结整理。
要做好模块化,我觉得理解好样式的作用域是很重要的。写过程序的同学应该都知道,变量是有作用域的(不知道的同学自己去问谷歌,这里就不作解释了),样式的定义也同样存在着作用域的问题,即定义的作 用范围,很容易就能理解,如下面的p的作用域:
CODE:
/*作用域:全局*/ p{text-indent:2em;}
CODE:
/*作用域:.demo这个类中*/ .demo p{color:#000000;}
样式选择器的优先级是学习样式的基础知识,一起简单回顾下:
● 标签的权值为0,0,0,1
● 类的权值为0,0,1,0
● 属性选择的权值为0,0,1,1
● ID的权值为0,1,0,0
● important的权值为最高1,0,0,0
使用的规则也很简单,就是 选择器的权值加到一起,大的优先;如果权值相同,后定义的优先 。虽然很简单,但如果书写的时候没有注意,很容易就会导致CSS的重复定义,代码冗余。从上面我们可以得出两个关键的因素:
1)权值的大小跟选择器的类型和数量有关
2)样式的优先级跟样式的定义顺序有关
了解样式的权值后有什么作用呢?比如可以这样用:举一个最简单的例子
CODE:
body{color:#555555;}.demo{color:#000000;}
CODE:
<p>这里的文字颜色受全局定义的影响</p>
<div class="demo"><p>这里的文字颜色受类demo定义的影响</p></div>
<p class="demo">这里的文字颜色受类demo定义的影响</p>
知道了样式的权值,你就知道上面例子的表现是怎样的了。进一步的应用,就是模块化了。
再来说说“作用域”,相信大家很容易就会想到“全局”、“公共”这些词,关注过模块化的同学应该都知道,网上说得最多的一种“模块化”,就是像 header、footer这样的以大区域划分。在去年web标准交流会(页面重构合理化讨论)上,克军提出了“样式的三层架构”——公共规则层、公共模 块层、项目层。这些都有它们适用的范围,而且最大的优点是容易理解和应用。这里也不再作重诉了,上面已经讲过。
我在这一块的划分上,有点类似克军的“样式的三层架构”,有一点小的差别,我是以“作用域”来分的: 公共级(全局)、栏目级 (局部公共)、页面级 。如何划分这个“作用域”呢?很简单,全局的global就是公共级的;只在栏目中用到的局部global是属于栏目级的;只影响单个页面的就是属于页面 级的了。
最后几点要特别注意的:
除了标签选择器之外,哪些类是使用于公共级、栏目级中的,如
CODE:
.tx_hit{color:#FF0000 !important;}
的适用范围是公共级的,应该放于全局的定义中。但,如果它只影响于某个栏目,那么就应该把它放于栏目级的作用域中。
● 标签选择器一般属于栏目定义,有时会用于公共级作用域中,除了最基础的reset之外,应尽可能少使用在公共级定义中
● 可继承的属性定义使用时须注意影响的范围,特别是在标签选择器中使用时
● 同类选择器无加权
接下来的内容就是以这个为基础的,希望大家能理解“样式的作用域”,对于后继内容的理解会很有帮助。
6.2 栏目级作用域──页面重构中的模块化设计(二)
在《 样式的作用域── 页面重构中的模块化设计(一)》中,我将样式的作用域分为了三个部分:公共级(全局)、栏目级(局部公共)、页面级。公共级(全局)容易理解,即影响站点中所有页面。简单解释下栏目级(局部公共)和页面级:
页面级
可分为两种情况:在多个页面间,页面级作用域指针对某一单独的页面定义;在同一个页面中,页面级作用指针对某 一标签的定义。它将决定最终的页面效果。
栏目级(局部公共)
介于全局与单个页面之间的一个作用 域,影响一个栏目(或某区域)。通常以某一类选择符做为开始,以包含选择符的方式将样式定义限定在某一区域中。
CODE:
/* 只影响demo这个区域 */
.demo a{...}
.demo p{...}
.demo .title{...}
需要消化下的内容,决定一个样式定义是属于哪个作用域的因素有以下两点:
样式定义所在样式文件中的位置。(同样的一个定义,放在不同的位置,所影响的范围会有所不同。)
HTML中绑定demo这个类的标签位置。(同样一个类,绑定在body标签和绑定在页面中某个标签上,所影响的范围也会不同。)
在一个站点中,可能会分为几个不同的栏目,同一个栏目中,一般风格会保持一致。而不同的栏目间,相似的风格则不一定会相同。即使是全站通用的模 块,如翻页,也可能会因为栏目的不同而会有一些差异,比如链接的颜色等等。使用栏目级的样式定义,能很好的减少代码的冗余,提高模块的复用性。
另外需要在思维上注意的一点,以作用域划分,并不意味着有着对应的文件,可能有些同学会习惯的以为一个作用域就应该对应着一个文件。比如一个小的 栏目,可能只有两三个页面,这时我们就不一定需要再把栏目级的定义单独出来一个文件,而是与页面级的定义一起放在一个文件里,像这样:
CODE:
/* S 栏目级定义 */
.class{...}
/* E 栏目级定义 */
/* S 页面级定义 */
.page{...}
/* E 页面级定义 */
6.3 继承──页面重构中的模块化设计(三)
前面我们了解了 样式的作用域的分类 和 栏目级作用域。在权值中,还有一个很重要的因素,需要做下补充,起因是这样的,有个同学在CSS森林群里问了个问题:根据样式权值两个关键的因素
权值的大小跟选择器的类型和数量有关
样式的优先级跟样式的定义顺序有关
可以知道,如果10个标签选择器的权值应该比一个类选择的权值高,像这样:
CODE:
div div div div div div div div div div div{color:blue;}
.c10{color:red;}
CODE:
<div class="c1">
<div class="c2">
<div class="c3">
<div class="c4">
<div class="c5">
<div class="c6">
<div class="c7">
<div class="c8">
<div class="c9">
<div class="c10">
<div>这段文字是什么颜色?</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
CODE:
div{color:blue;}
.c10{color:red;}
是不是跟想的不太一样?难道前面所说的权值是有问题的?前面讲的权值并没有问题,不过漏了一个重要的规则: 继承的权值小于 0,0,0,1。
样式的继承
指被包在内部的标签将拥有外部标签的样式性质。
继承最大的意义在于可以减少重复的定义,比如要定义整个页面的文本颜色,只需要定义body的color样式,body里的所有标签都会继承 body的color定义。是不是很方便?方便是相对的,当你想要为body内部分标签定义另一种文本颜色时,继承也许会成为增加重复定义、降低性能的祸 首。
并不是所有的样式定义都具有继承的性质,整理了一下常用有继承性的定义, 这些定义在使用的时候要比较注意。
简单分析下上面的例子,最后一部分的代码:
CODE:
<div class="c10">
<div>这段文字是什么颜色?</div>
</div>
当定义了c10后,根据权值,类定义的权值是0,0,1,0,应该是比div这个定义0,0,0,1要高的,但由于div是直接定义到标签上的, 比起从c10的定义中继承来的定义权值更高。稍微改下就清楚了:
CODE:
<div class="c1">
<div class="c2">
<div class="c3">
<div class="c4">
<div class="c5">
<div class="c6">
<div class="c7">
<div class="c8">
<div class="c9">
<div class="c10">
<p>这段文字是什么颜色?</p>
<div>这段文字是什么颜色?</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
从 修改后的例子 可以看到,p标签继承了c10的定义,显示为红色。因此,在使用标签选择器的时候,应特别注意它的作用域,个人的建议是,除了最基本的reset之外,在 公共作用域中最好不要使用标签选择器,在栏目级作用域中也应尽可能的少用。
常用有继承性的样式定义:
● text-indent
● text-align
● layout-flow
● writing-mode
● line-break
● white-space
● word-wrap
● list-style
● list-style-image
● list-style-position
● list-style-type
● font
● font-style
● font-variant
● font-weight
● font-size
● line-height
● font-family
● color
● text-transform
● letter-spacing
● word-spacing
6.4 模块化的核心思想──页面重构中的模块化设计(四)
有不少同学觉得前面的内容过于简单了,对于 样式的作用域的分类 、 栏目级作用域 、 继承等内容的确十分基础,不过基础还是很重要的。下面就一起进入这个系列真正的主题——“模块化”吧。
早在Qzone4.0的页面架构中已经在项目中开始摸索提高代码复用的方法,只不过当时并没有很清晰的认识到“模块化”这个思想。从去年的《 从宜家的家具设计 讲模块化》开始,模块化成了我主要的一个学习方向。借着无数的提问、思考、讨论,渐渐形成了一个比较清晰的、较为完整的方案。后面的内容,更多的是出于我在实际项 目中总结出来的方法,虽然已经尽可能为出现的问题提供了解决方法,不过还是少不了会有些我没遇到过或没考虑到的,欢迎各位指出。
首先来了解下页面重构中模块化的核心思想: 将HTML和CSS通过一定的规则进行分类、组合,以达到特定HTML、CSS在特 定范围内最大程度的复用。 有三个关键词: 规则 、 特定范围 、 最大程度的复用 。怎么理解呢?
规则
编写模块时需要遵循的规范
特定范围
模块可使用的范围。与样式的作用域有关,大部分模块的使用范围仅仅是某一个栏目或站点。
最大程度的复用
做最少的修改即可重复使用。很多同学都把“复用”理解成不用修改的直接使用,但在页面 制作中,由于实际的项目环境,基本是不可能做到“一个模块走天下”的。不同的栏目会有不同的需求,大家应该都多少有所体会,我就不多讲了。
从实际出发,才能最终服务于实际。我们知道一个HTML标签可以绑定多个样式,所以我们可以这样去定义一个模块:
CODE:
<div class="class-a class-b class-c">
...
</div>
不少同学已经知道这个方法了,而且还很形像的称之为“拼样式”。这样的定义很容易引出其它的问题,比如样式类的个数多少个适合?样式类如何命名? 等等。下面讲下我的方法,从前面我们学到的样式作用域及模块化的核心思想,我们可以把样式进行一个分类,像这样:
CODE:
.mode-a{/* 定义一个模块 */}
.type-a{/* 模块中的差异化定义 */}
.mode-name{/* 针对单个模块的个性化定义 */}
CODE:
<div class="mode-a type-a mode-name">
...
</div>
上面的“mode-a”,我称它叫为“基类”;“type-a”为“扩展类”;“mode-name”为“模块名”,作用分别是:
基类
(基础样式)模块的基础表现。包含了模块中大部分的状态。
扩展类
(扩展样式)用于对使用基类的模块进行小范围的修改
模块名
模块在某一作用域中的唯一名称。
这里有一个 简单的例子 可以帮助理解。
也有同学主张用ID去表示“模块名”,我认为这种方式扩展性比较差,而且很容易与开发的ID冲突,不过也不失为一个方法。
6.5 基类、扩展类──页面重构中的模块化设计(五)
基类 和 扩展类 是这个系列的主要内容,上一篇《 模块化的核心思想──页面重构中的模块 化设计(四)》中只是简单提了一下,我们再深入的来了解下它们。
一般所使用的“模块化”的方法,就是以某一个类做为定义的开始,比如:
CODE:
/* S 图片列表 */
.pic_lists li,
.pic_lists li img{float:left;width:122px;height:122px;margin-bottom:8px;}
.pic_lists li{list-style:none;margin:0 0 0 6px;text-align:center;}
.pic_lists li .pic{display:block;border:1px solid #476081;}
/* E 图片列表 */
/* S mtv列表 */
.mtv_lists{width:930px;height:130px;}
.mtv_lists li,
.mtv_lists li img{float:left;width:120px;margin-bottom:8px;}
.mtv_lists li{list-style:none;margin:0 10px 0 0;text-align:center;}
.mtv_lists li img{height:90px;border:1px solid #476081;}
.mtv_lists li .pic{display:block;width:120px;height:90px;margin-bottom:8px;}
/* E mtv列表 */
这个例子: 两个列表模块 。这种方式是比较常见的,可以很好的将一个模块独立出来。如果使用新学习到的“方法”来写这两个列表模块,应该是怎样?
基类(基础样式)模块的基础表现。包含了模块中大部分的状态。也就是说,当出现多个类似的模块时,基类包含了这些模块的大部分的效果(或者理解为公共的部分),在基类的基础上,我们可以通过添加很少的代码 ——扩展类,来达到所需要要效果。像这样:
CODE:
/* S 列表 基类 */
.mode_lists li,
.mode_lists li img{float:left;width:122px;margin-bottom:8px;}
.mode_lists li{list-style:none;margin:0 10px 18px 0;text-align:center;}
.mode_lists li img{border:1px solid #476081;}
/* E 列表 基类 */
/* S 图片列表 */
.pic_lists li,
.pic_lists li img{height:122px;}
.pic_lists li{margin:0 0 8px 6px;}
.pic_lists li .pic{display:block;border:1px solid #476081;}
/* E 图片列表 */
/* S mtv列表 */
.mtv_lists{width:930px;height:130px;}
.mtv_lists li,
.mtv_lists li img{width:120px;height:90px;}
.mtv_lists li .pic{display:block;margin-bottom:8px;}
/* E mtv列表 */
可能你会觉得这样的样式不就多写了,还得把原先的模块类变成两个。的确不是所有的模块都值得这样去做,于是我们可以得到一种“偷懒”的作法,把其 中一个模块直接变成基类。对于经常会被使用的模块,像图片列表、播放列表等,这种写法在代码的复用和效率会有一定的提高。一般情况下只需要做下简单的修改 即可应用,来看一个复杂些的例子:
一个带头像的消息列表(A)
看看这两个图,在脑中先想想如果是你,你要怎么实现。……5分钟过去了……差不多有方案了,按上面的思路,基类是包含了大部分的效果的,也就是说 基类应该能满足大部分效果的需要,两个模块间差异的地方,可以通过扩展类来完成。当然前提是这两个模块有能找到类似的点,能够形成基类。
在这两个模块中,我们不难看出,A模块和B模块在信息的部分是很类似的,虽然B模块的列表不需要A模块的评论部分,但这并不影响B模块的表现。所 以我们可以把这两个模块看成的类似模块。另个,以哪个为基类呢?从满足大部分效果这个要求来看,很明显A模块做为基类是要比B模块做为基类更合适的,如果 用B模块做基类,那么需要写更多的扩展类来满足A的需要。另外还有一个重要的点,之所以选择A模块为基类,是因为A在栏目中被更多的页面使用。
OK,来看看A模块怎么实现(样式部分):
CODE:
/* S 消息 基类 */
.mode_message{position:relative;padding:8px 3px 8px 48px;
border-bottom:1px solid #DAECF6;_zoom:1;line-height:1.3;}
.mode_message .user_info{position:absolute;left:3px;top:10px;}
.mode_message .user_info .pic img{width:35px;height:35px;}
.mode_message .mode_message_cont{color:#797979;
word-break:normal;word-wrap:break-word;}
.mode_message .mode_message_cont .info{display:block;zoom:1;}
.mode_message .mode_message_cont .info .music_name{color:#22639B;}
.mode_message .mode_message_cont .info .op_music{display:none;}
.mode_message .mode_message_cont .info:hover .op_music,
.mode_message .mode_message_cont .info.hover .op_music{display:block;position:absolute;right:5px;top:7px;
background-color:#FFFFFF;}
.mode_message .msg{padding:2px 0;word-break:normal;word-wrap:break-word;}
.mode_message .mode_message_cont .op{margin-bottom:3px;}
.mode_message .time{display:inline-block;*display:inline;*zoom:1;font-size:10px;}
.mode_message .msg .p_zt_l,.mode_message .msg .p_zt_r{display:inline-block;*display:inline;*zoom:1;
width:13px;height:8px;background:url(img/_g_other.png) no-repeat -17px -17px;
vertical-align:text-middle;*vertical-align:middle;}
.mode_message .msg .p_zt_r{background-position:0 -28px;}
.mode_message .write_back .cont{margin-bottom:2px;padding:5px;background-color:#EAF6FA;_zoom:1;}
.mode_message .write_back .cont .cont{border-left:1px solid #ABCFE1;}
.mode_message .write_back .cont .zt{*overflow:hidden;}
.mode_message .write_back .cont .zt2{*padding-right:6px;}
.mode_message .write_back .cont .zt textarea{width:100%;height:40px;padding:0 2px;
border:1px solid #D1E1EC;line-height:20px;color:#4F4F4F;}
.mode_message .write_back .cont .zt .normal textarea{height:23px;color:#B1B4B8;}
.mode_message .write_back .cont .zt .normal .op{display:none;}
.mode_message .write_back .cont .op{margin:5px 0 0;}
.mode_message .write_back .cont .op .bt_v2{padding:0 2px;vertical-align:middle;}
.mode_message .write_back .cont .zt{width:98.5%;*width:99.9%}
.mode_message:nth-last-child(1){border-bottom:none;}
/* E 消息 基类 */
别忘了提示条,虽然是用于模块中,但它应该是可以被更广泛使用的模块,因此我把它单独提了出来:
CODE:
/* S 提示条 基类 */
.mode_hint{position:relative;margin:3px 0;padding:5px;
background-color:#FFFEAB;color:#000000;_zoom:1;}
.mode_hint .op{position:absolute;right:8px;top:5px;}
.mode_hint .op a{color:#000000;}
/* E 提示条 基类 */
还有像按钮、全局定义这些内容,就不列出了。完整的可以看: 基类、扩展类实例 。例子中可以看到,扩展类的定义很少,只是一些简单的定义,像B模块:
CODE:
/* S 消息 扩展 */
.message_nopic{padding-left:0;}
/* E 消息 扩展 */
只需要一句,将头像去掉即可。
6.6 CSS模块的注释——页面重构中的模块化设计(六)
从前面的内容我们已经知道,样式是可以分成各个模块去写的,如何表示各个模块的作用及它们之间的关系呢?CSS的注释是不二的选择。
与普通的注释不同,模块的注释需要一些更详细的内容,比如:功能说明、模块版本、关联信息等等。 像 《基类、扩展类──页面重构中的模块化设计(五)》中例子的注释,显然是比较简单的。为了减少不必要的沟通,我们可以使用较为固定的格式去完成这个注释。
举个例子:
CODE:
/**
* @name:mode_name
* @author:ghostzhang
* @version:1.0
* @type:基类
* @explain:Demo
*/
.mode_name{...}
.mode_name h2{
...
}
.mode_name .cont{
...
}
/* @end **/
/**
* @name:mode_name_b
* @author:ghostzhang
* @version:1.0
* @type:扩展类
* @explain:Demo
* @dependent:mode_name
*/
.mode_name_b{...}
.mode_name_b h2{
...
}
.mode_name_b .cont{
...
}
/* @end **/
从注释中就可以知道mode_name_b和mode_name_a之间的关系。
主要的关键字有:
@name
标明模块的名称
@author
标明模块的作者
@version
标明该模块的版本
@explain
功能说明
@relating
标明该关联的模块
@dependent
标明该所依赖的模块
@type标明该模块的类型:公共、基类、扩展类
需要注意的规则:
以“/**”标记模块的开始
从“/**”到第一个“*/”作为模块相关信息的说明,包含关键字
关键字以==@==开头,“:”后开始到“*”的内容为相关的值,即:
@关键字:值*
以“/* @end **/”标记模块的结束
模块注释内不可嵌套
提供了一个小工具( cssModeCODE: )帮助大家填写样式模块的注释。