从模块化CSS到面向对象的CSS

最近做了一个项目,尝试了一些以前未曾上尝试过的方法——基于模块设计整个网站的内容。做到后期才发觉如果初期的时候设计的更好一些,能做到面向对象的CSS来设计,那整体开发效率以及代码复用方面会做比现在好。现在的代码还是有点不干净的地方,有些地方有点乱。这可能是这行当的“完美主义”情结吧 :) 希望下次可以做的更好。

1.模块化问题

首先,什么是模块化?我这里指的模块化并非仅仅指CSS模块化,还指的是HTML的模块化。那为什么要模块化?最主要特点是使各个部分相对独立,同时增加代码复用,最终目的是为了降低开发成本提升开发效率。

举个例子,当拿到一个项目的时候,扫一遍所有设计,就可以发现:
哦,一共有10种侧边栏设计,设计师就是把这10种侧边栏翻来覆去放在不同的页面中。那就很简单了,只要完成了这10种侧边栏,那么就算100个1000个页面也都是一样的,只要把HTML复制,粘贴就可以了。最大化的实现的代码复用。

鬼哥写过一篇文章来阐述这个问题:
http://www.cssforest.org/blog/index.php?id=134

2.面向对象的CSS

当进一步观察,很容易发现,有侧边栏都是有一个标题,再下面是是主面板,再有一类侧边栏是标签页的,下面展示的是新闻之类的内容。这个时候就完全可以抽象出两个类,一个是带标题的侧边栏(title_box),另一个是带标签的侧边栏(tab_box)。这样的好处是可以进一步抽象出代码,提高复用率。增加开发速度。
比如前者的HTML可以是这样的:


可能的CSS是:

.title_box {margin-bottom:10px;border:1px solid #aaa;}
.title_box h3{border-bottom:1px solid #aaa;}
.title_box .act{float:right}
.title_box .panel{margin:0 10px;}

为了保证代码的简洁易读,一些小的padding或者背景修饰我就不写了。
其中act是action的意思,这里偷学来的XD

是的,以上的HTML以及CSS代码就共同构建了一个基本的侧边栏类
他所具有的特诊是有边框,下面空开10px,右上角还有个“更多”

有同学可能会问,我为何要把act类放在那个span上而不是a上?为何要把panel放在一个div上?放在ul上不是可以减少代码么?这里我之所以这样写,是为了最少代码与开发成本之间平衡的考虑。所有成本中,人力成本是最高的,服务器有钱都可以加,但是代码得更快速的写出来才是要紧的。这么写的话可以最大保证通用性。同时,另一个理论用于支持这种写法的是:只有无语义的div和span适合拿来做布局,而有语义的元素不应当参与到布局中。当然,我这里只是一种权衡。

这么写是很灵活的一种写法。如果不需要“更多”,直接删除即可,如果不需要标题,直接删除即可。进一步来看,这段HTML可以应用到整个网站各个地方而不仅限于侧边栏。只要是形式相同的地方都可以用。

3.进一步实现继承和扩展

这里带来的一个问题是如何控制宽度。侧边栏和正文部分宽度不同,有些地方需求又各异,如何保证宽度?跳出思维局限来看,是不是所有某一宽度的div都同属一类?是的,他们是同一类的,所以只要定好了宽度,继承就可以了!

如果应用了栅格化布局,就能很容易理解这一点,那么上面的代码就应该是这样的


前面的g5就代表了其占了5个栅格!这个模块首先是占了5个格子的宽度,然后他是一个带标题的模块。进一步看,如果这个格子在不同的地方背景色不同,那可以继承了前面两个类的基础上,再加上一个 bg_blue于是这就成了:


没错,他就是一个占5个格子的带标题的模块,背景色是蓝色的。
好,可能他在某个地方是左浮动的,需要加上float:left,但不是所有的title_box都需要做浮动,那自然加上一个left类即可:


所以,挑选一个合适的基于栅格的框架很有意义,如YUI,BluePrint,或者OOCSS。
可以参考一下OOCSS,我很欣赏其中的思想
前端观察对其的介绍:http://www.qianduan.net/object-oriented-css.html

针对面向对象CSS的一个很好的PPT
http://www.slideshare.net/stubbornella/object-oriented-css

面向对象的CSS框架(OOCSS)的项目地址
http://wiki.github.com/stubbornella/oocss

我除了不喜欢其中到处都是为了圆角而设计的CSS之外,其他都很喜欢。

另外,写的时候,我搜索了一下,才发现这种思想08年初嗷嗷就已经有啦,我Out啦:
http://www.aoao.org.cn/blog/2008/01/oo-style/

4.这不是银弹

要坚信,在解决一个问题的同时必然会带来其他问题。

优先级问题
其中一个问题是优先级问题,由于模块细分,优先级很容易弄得焦头烂额。我的经验是每个类都负责一个部分——布局归布局,色彩背景归色彩背景。只有把各个类细分出来,才能避免样式的冲突。最后,id也是必不可少的。也需要important。比如上面,我就会定义一个noborder类,表示这个容器一定没有边框。用于个别地方是一个title_box但没有边框的情况。这个类里我就设置了

.noborder{border:0 !important;}

这样就能保证在任何时候,都不会有边框了。

同时,我对于文件规划,通常是一个页面只引入这个页面的css,比如首页的index.css
在这个css里再用import依次导入用于
重置的reset.css(重置标签类,其实一个标签本身不就是一个类么?div类,span类,本身就具有block或者inline属性,我们只是在其上面在扩展而已)
global.css(用于设置header和footer以及中间主体部分的布局,因为这两个部分一般整站是不会变的)
layout.css(用于设置栅格,构建内容以及侧边栏的宽度等。)
module.css(用于建立如title_box这种中等模块)
submod.css(这里放一些细节的模块,比如某一个新闻列表的完整细节样式设定)
如果模块较多,还可能分成若干的子块,侧边栏放侧边栏,中间的放中间。因为我相信绝大多数情况下侧边栏将永远是侧边栏,而不会跑到中间去。
如果有必要,也会建立form.css,专门设置表单元素,定义表单元素的外观。我会在里边设置一些 blue_btn,red_btn之类的class来定义不同颜色的button.
而后,在这个首页的index.css里写特定的样式。
这种规划的方法在很多文章中都能见到影子,如今再回头看,又有了更深一层的体会。

另外,import有潜在的性能问题。见这里
只是我实在抵御不住import带来的便利性,在html里加入一堆的link是多么痛苦的事情。而且大多数时候对性能的要求没那么高,为此我宁可节约一点人力成本。这是种取舍,因网站需求而异。但总体顺序还是不变的。
而解决这个性能问题的方法是用 条件CSS,可以参考这里
这里
id与class
命名的时候到底是以id为主,还是class为主,想必各位同学看到这里也该明白在下的观点了——以class为主,id为辅。因为同一段HTML可能用到页面的任何地方,而id在同页面里是不能重复的。而且,为了体现继承扩展的关系,应当使用class了。
那么id还用不用?当然要用,且不论id在写JS时候的速度,实际在写页面的时候,肯定会某一个模块做“个性化”定义,这就需要使用id了。个性化的东西写在id里,这样就不用担心干扰其他模块了,最大避免触一发动全身的窘境。通用与独立同在。

聪明的同学一定想到了什么,对,是不是记得曾经看到过启蒙读物上,一再强调,千万不要把一个类明明为left,因为指不准哪天他就不在左边了。不要定义为blue,因为哪天就改成红色了?是的,那个说没错,但我这里也没错。我们说的是两个东西。

修改CSS还是修改HTML

曾经看到的那些,都是建立在一旦HTML写完,需求变的时候不许改HTML,只许改CSS的基础上的。但实际上,又多少机会能让你只改CSS而不改动HTML的呢?另外,既然有一套完善的CSS了,为何不直接简单修改一下HTML呢?如果背景变变成了红色,我只需要修改一下class,从bg_blue改成bg_red就可以了,何乐而不为?由此看来,class取名成left和right并非很糟糕的决定,只要合理利用,完全可以应付多样的需求。

开发成本VS维护成本
这样做之后带来的问题就是开发成本与维护成本的矛盾。这样开发确实很快速。但问题是以前小改动起来是不需要动HTML的,而现在改一次就得改HTML,而这方面一旦页面做好后就可能归其他部门那边管了,修改测试上传都得惊动其他部门,这方面如何协调是一个问题。
换句话说:面向对象CSS保持CSS框架不变而修改HTML,而原先那种是保持HTML不变而修改CSS
这方面我没有太多经验,欢迎大家探讨。
Twinsen有过类似的探讨( http://www.twinsenliang.net/skill/20081129.html ) 他就很反对这么来。
其中的大部分观点都赞同,只是最近我也在反思:
到底多大机会能让我碰上“left_box”和“right_box”的互换。全站的blue_button换成红色呢?
如果只是一两个页面的话,修改CSS的成本更低,还是保持CSS不动修改HTML的成本更低?

增加学习成本
当公司招募新人的时候,不得不对其进行培训,以使其学会使用这套自建的框架体系。但带来的好处是使整个网站风格统一,给进一步维护带来方便。所以我认为增加的这点学习成本是值得的。
同时,如果你都已经跳槽到别处了,没留下好的文档,人家学不会也没关系。直接抛开这套体系也能过日子。因为本身这套体系是从reset开始对各个标签初始化对象的,而后逐渐增加class以扩展对应属性。
也就是说,新人也可以抛开这套东西,直接用自己的写法来写。当然,这好不好已经不在今天的讨论范围了。这里只是为了说:这个框架也可以让新手不管这些约束直接写自己的。

class过长导致文件过大
这可以通过压缩来解决。用正则找出css和html中所有的class,以出现频率排序,然后分别从abcdeft…开始,先单字母,后双字母 a一直到zz应该有26*27种组合,足够一般网站应用了。google应该也是这样做的。抱歉这段时间比较慢,代码就没写了。

副作用:js必须用id而不是class了,否则压缩后会找不到对应的class
解决思路:约定JS中保存class的字符串变量为 foo_cls=”bar”,那么只要用正则找到对应_cls结尾的变量进行对应替换即可。
这本身是面向对象的CSS,而进一步压缩这里仅提供一种思路。

总结

结构和样式分离是好事,我这里提出的观点反而有点逆潮流,试图通过HTML中的class属性来控制表现。我认为这是一种理性回归,至少我们不用一个ID下面写一堆的针对他的样式,冗余,容易出错,效率还高。同时这种方式与直接在元素上写style也是有本质区别的,模块化,面向对象的思想体现在其中,合理控制每一个类的颗粒度,或者说控制范围。
我这种介于结构—样式的完全分离与结构—样式完全整合之间的一种状态,试图寻找一种平衡,提升开发效率并降低维护成本。

谨以此文做以探讨。

扩展阅读

重构之美-跨越Web标准,触碰语义网[分离:通用也许是个美丽陷阱] ——另一个反对的声音
面向对象的CSS--OOCSS 国人写的通过JS增加CSS面向对象功能

圆角?抱歉,你的浏览器不支持。请升级以获得更好体验。

之前写过一篇博客,问是否敢对IE6说不。实际上,我虽然自己没有公然抗议,暗自的还是有一些的。

最常见的就是:hover伪类了。除了二级菜单这类关键部位之外,其他地方一律用此处应当使用的标签,而不滥用a。其:hover伪类IE6无法支持,大不了少了点效果而已,不会有功能损失。想获得更好体验?请升级。为此写脚本做兼容?抱歉,我没时间。实际上,很多时候需求方并不在意这些细节交互地方。他们都不在意,你在意什么?

圆角?对不起,我可不高兴用一堆标签弄出圆角来。九宫格之类的办法不是不会,只是嫌麻烦。一堆标签自己看得心烦。直接用border-radius,浏览器要支持你就圆角,不支持就方的好了,没什么了不起的。除非需求方强烈要求,那在说

阴影?呵呵,同样对不起,阴影如果方便直接背景就背景,不方便实在抱歉……我给一个box-shadow已经很不错了,兼容Chrome Firefox 3.5呢。没了阴影功能又不会缺失,可有可无。

文字阴影?text-shadow伺候,如果需求方执意要求,那只能做背景图了……只是Chrome下文字阴影效果真是颗碜。。。

其实CSS3有很多很美妙的特性,利用这些特性可以非常轻易制作很出众的效果,何苦为了一些效果而花费大量时间呢?
适当跟需求方说明这些。告诉他们实现这些所要消耗的人力成本有多高。让他们自己决定哪些一定要,哪些可以放弃一部分低级浏览器用户。
在完善的沟通下,才能说服老板,说服需求方逐步抛开低级的浏览器,才能促进整个大环境的改变。

适当转型——写点生活

之前一直是纯技术博客,现在打算逐渐转一点,逐渐加入些生活元素。
当然,我也有另外的博客,太过私人化的不会放在这里了。大家放心。(就算我想放你们也管不着,这是我的博客,灭哈哈哈哈~~)
虽然有了twitter,但有些时候还是博客可以积淀一些东西。
架设Dabr和twitter的api很容易,我也有架设,能猜到的话,欢迎一起用,但别声张~
你也可以自己架设自己用。很容易搜索到教程

另外,收到 likeren 邀请(他的博客很人文,搞技术测宅男们建议吸取一些人文素养),尝试了一下国外的基于软件同好的SNS Wakoopa有兴趣可以上面来一起玩。

再另外,最近接了个项目后,有了点感悟,想写博客,结果发现想要写的太多,纠结很久。在此次牢骚一下……

给账族写的快速入账命令行GM脚本

最近要开始记账,寻遍国内外各种在线记账的也未能找到完全诚心如意的。最终觉得账族整体界面、操作上都比较符合我要求。
源于在Todoist上任务管理的录入方式,通过一系列命令可以快速输入。所以希望这里入账也有命令行。
而这个网站又恰巧用了jQuery,就写起来更顺手了。
此外,这个网站貌似目前主要开发任务是论坛……我不说啥了……不指望他们近期能开发这个功能。
所以,为了进一步加速入账效率,则开发了这个脚本。

比如我输入:
工资 +5000
回车,早就自动往我工资帐户加上了5000元(其实我工资远不到5K。。。不过做的蛮开心,暂无跳槽之意。)

又比如输入:
3 旅游 1000 信用卡
就自动设置为3天前,旅游用信用卡花了1000,花费分类是旅游

就类似这种功能。

地址是:
http://userscripts.org/scripts/show/55688
我用着还挺爽。