Oct30

CSS设计模式之三权分立模式篇

分类: Html/CSS | 转载请注明: 出自 海玉的博客
本文地址: http://www.hicss.net/separation-of-powers-model-in-css-design-patterns/

市面上我们常常会看到各种各样的设计模式书籍,Java设计模式、C#设计模式、Ruby设计模式等等。在众多的语言设计模式中我唯独找不到关于CSS设计模式的资料,即使在网上找到类似内容,细细一看之下才发觉是南辕北辙。经过浩瀚文章搜索发掘下依旧一无所获之后,直接导致了我萌生一股写一篇CSS设计模式的冲动,至此写下这篇文章,其中叙述如有不当之处,也恳请各位提出意见,分享出您宝贵的经验。

在写页面之中,width, margin, padding这三个CSS属性可以说是用到频率最高的几个属性之一。但根据我的观点来看,许多人,甚至于大多数前端对于这三个属性的书写把握上乏善可陈,以至于兼容和灵活性不得兼顾,导致日后的开发维护成本直线上升,代码不断增长,覆盖重写样式,接着再修复一个又一个的Bug。这样的情况下,使用一种合理高效的CSS设计模式不失为一种明智的选择,个人称之为:width,margin,padding三权分立模式(以下简称三权分立模式)。

什么是三权分立模式:

说明这个模式之前,必定先要明白什么是盒子模型,你可以参考如下Firebug的盒模型截图:

Firebug Box Model Display

注意:在图上表示margin的颜色为白色(实质是透明)。Margin比较特别,它不会影响盒子本身的大小,但是它会影响和盒子有关的其他内容,因此margin也是盒模型的一个重要的组成部分。

盒子本身的大小是这样计算的:

Width: width + padding-left + padding-right + border-left + border-right
Height: height + padding-top + padding-bottom + border-top + border-bottom

通过图中我们得知,Width(物理总宽度)/Height(物理总高度),是由width/height,padding,border三者之和来决定的。但简单浅显的盒模型一旦牵涉到CSS中,便会出现不小的意外。

假设,需要要一个300px宽,80px高的盒子,里面放一段文字,文字离边有10px间距。那么一般Html和CSS写法为:

HTML代码:

<div class="box">
  市面上我们常常会看到各种各样的设计模式书籍,Java设计模式、C#设计模式、Ruby设计模式等等。
</div>

CSS代码:

.box{width:280px; height:60px; padding:10px;}

来分析一下这段CSS代码,因为需要满足300px宽,且文字间隔为10px,所以Width(300px) = width(280px) + padding(10px) X 2。假设过了段时间需要改版,要求为这个Box添加1px颜色为#ccc的边框,那么我们再次找到这段CSS,开始动手修改,加个boder:1px solid #ccc;:

CSS代码:

.box{width:280px; height:60px; padding:10px; border:1px solid #ccc;}

之后测试一下,咦!似乎有点问题,哦,因为加了边框所以又多了2px像素的宽,所以要减去这2px。

CSS代码:

.box{width:278px; height:58px; padding:10px; border:1px solid #ccc;}

测试一下,问题解决了。正当欢心不已准备休息时候,设计师跑来说,根据最新要求,希望能再把文字与边距拉大,上下边距为10px,左右改为15px。继续修改:

CSS代码:

.box{width:268px; height:58px; padding:10px 15px; border:1px solid #ccc;}

经过“精确”计算后终于得出了如上的CSS结果。一次又一次的不断改版,修改的页面数量越来越大,其中的代码越发复杂,计算量也越发庞大,你只能一边为自己的精确到1px像素级的“专业”功力而沾沾自喜,而后又不得不疲于奔波与各个页面中的宽高计算。就这样一个又一个只有你才能明白的代码出现了!

如何才能摆脱这样的无效代码问题呢?这里使用CSS三权分立模式可谓是最佳解决方案。

CSS三权分立模式的核心在于完全分离width,margin,padding这三个CSS属性,一个class里只能拥有三个属性里的其中一个,而通过增加一个额外标签使得能够通过多个class控制元素的外观,解除三者的耦合。

就如以上问题为例,重写300px宽,80px高的盒子,文字离边有10px间距。使用三权分立模式下的写法为:

Html代码:

<div class="box">
  <div class="roundBox">
    市面上我们常常会看到各种各样的设计模式书籍,Java设计模式、C#设计模式、Ruby设计模式等等。
  </div>
</div>

CSS代码:

.box{width:300px; height:80px;}
.box .roundBox{padding:10px;}

这里的宽度、高度、间距的数值同需求完全一致,无需计算。接下来遇到改版问题为Box添加1px颜色为#ccc的边框,只需这么修改:

CSS代码:

.box{width:300px; height:80px;}
.box .roundBox{padding:10px; border:1px solid #ccc;}

直接为内部标签class上添加边框,单刀直入添加边框属性即可。最后一关如需把文字与边距拉大,上下边距为10px,左右边距为15px。

CSS代码:

.box{width:300px; height:80px;}
.box .roundBox{padding:10px 15px; border:1px solid #ccc;}

注意:上例可以看到padding与border其实在“宽度占有”的性质上是一致的(从某个角度来说把border设置为透明再设置一定宽度就是变相的padding),所以完全可以让这俩个属性写在同一class里,二者性质类同,无需再分离这俩个属性。

我们可以看出使用三权分立模式书写代码的简洁与高效,而且从可读性及维护性上有质的飞跃。唯一多的就是为内部添加一个额外的标签保证padding/border不会与width产生干扰,解除二者的耦合关系,这也是获得可维护性需要付出代价。

在刚才的例子中似乎忽略了另外一个属性——Margin。这里的Margin在三权分立模式中的立场相对于上面的width与padding耦合强度下似乎并没有那么明显,但以我的个人观点上来看:Margin的分离是三权分立模式之中最为重要的一环。为什么Margin在这里如此重要?因为这里可以看出一名前端开发人员功力素养——代码可重用性。

提及代码可重用性,任何一门计算机语言都有所阐述,而CSS代码可重用性对于一名前端来说不亚于对JS重构的重要性。CSS的可重用性会直接影响HTML代码的可复用性。

再回头看如上代码,这里的盒子仅仅是一个页面的一小部分而已。纵观整个网站,不会也不可能出现只需要一个模块的页面。每写一个模块,将来就要和各式各样的页面进行整合、维护、开发。

继续上面例子,现在我们做好了这个模块,开始行进页面的整合。假设这个模块所放的位置同左边的间隔需为10px,同上方模块间隔15px,可能会这么写:

CSS代码:

.box{width:300px; height:80px; margin:15px 0 0 10px;}
.box .roundBox{padding:10px 15px; border:1px solid #ccc;}

这里的写法原本是没有大过错的,既然提出了Margin分离的重要性,当然不会那么简单的糊弄过去的。记住:你所写的每一个模块的代码将来都会有被复用到另外一个页面(项目)的可能性,甚至于我曾看到过一个模块被几个不同的页面反复重用。

说到这里,继续下去,假设这个模块需要在另外一块地方使用,而那里设计位置要求必须是同上方间隔为0px,离左边5px。内容结构完全一致,遇到这样的情况,你会如何解决?

一般遇到这种情况,第一种做法有人会对原先代码视而不见,直接重写一份新的HTML和CSS。你别说,我还真遇到过。第二种做法或许会这么修改HTML和CSS:

HTML代码:

<div class="box anotherPlace">
  <div class="roundBox">
    市面上我们常常会看到各种各样的设计模式书籍,Java设计模式、C#设计模式、Ruby设计模式等等。
  </div>
</div>

CSS代码:

.box{width:300px; height:80px; margin:15px 0 0 10px;}
.box.anotherPlace{margin:0 0 0 5px;}
.box .roundBox{padding:10px 15px; border:1px solid #ccc;}

添加一个结合选择器限定覆盖原先的margin属性,这一类做法是比较聪明的办法,如果这个模块多次重用则添加多个结合选择器即可,我曾今也常使用类似做法来修复各类的Bug。

或许上面的方法的确可以解决类似的问题,但作为一名喜欢没事瞎琢磨点东西的我来说总觉得有什么地方不对。在不断的思考下最终发现这个并非是真正重用,只是一种“修复”,从某个角度上来说,这不过是为这个模块打一个“补丁”。一个又一个结合选择器不就是一个个小的补丁嵌入到不同的页面中去么,依然会有那么多多余的代码会被写入,这并非是理想中的重用。

理想的重用是不添加任何代码,仅仅使用原先的代码就能完全搞定所有的模块,虽然只是理想状态,但我们可以不断的向这个目标靠近。在研究一些CSS库后,发现很多地方同自己的想法不谋而合,其中Margin分离就是其中之一,我们来看看以下代码片段:

CSS代码:

.m10{margin:10px}
.m15{margin:15px}
.m30{margin:30px}
.mt5{margin-top:5px}
.mt10{margin-top:10px}
.mt15{margin-top:15px}
.mt20{margin-top:20px}
.mt30{margin-top:30px}
.mt50{margin-top:50px}
.mt100{margin-top:100px}
.mb10{margin-bottom:10px}
.mb15{margin-bottom:15px}
.mb20{margin-bottom:20px}
.mb30{margin-bottom:30px}
.mb50{margin-bottom:50px}
.mb100{margin-bottom:100px}
.ml5{margin-left:5px}
.ml10{margin-left:10px}
.ml15{margin-left:15px}
.ml20{margin-left:20px}
.ml30{margin-left:30px}
.ml50{margin-left:50px}
.ml100{margin-left:100px}
.mr5{margin-right:5px}
.mr10{margin-right:10px}
.mr15{margin-right:15px}
.mr20{margin-right:20px}
.mr30{margin-right:30px}
.mr50{margin-right:50px}
.mr100{margin-right:100px}

以上其实就是一个CSS公用文件的一段代码摘抄,其思想引入这些公用的CSS类,单独定义Margin。通过简写命名,即使用头字母来记忆里面的属性,例如:mt15就是margin-top:15px的意思,下面来看我们如何使用它吧。

原先那个模块整合到页面中需要离上15px,离左10px,使用CSS库的做法就是这么写的:

HTML代码:

<div class="box mt15 ml10">
  <div class="roundBox">
    市面上我们常常会看到各种各样的设计模式书籍,Java设计模式、C#设计模式、Ruby设计模式等等。
  </div>
</div>

CSS代码:

.box{width:300px; height:80px;}
.box .roundBox{padding:10px 15px; border:1px solid #ccc;}

剔除多余的结合选择器,选择可以高度重用的CSS属性,这里你或许觉得没有多大意义,但如果需要把这个模块放入别的页面呢,就如上面又要放置在同上方间隔为0,离左边5px的地方,此效用开始发挥作用了,直接修改HTML代码,无需任何的CSS的修改,模块的高度复用:

HTML代码:

<div class="box ml5">
  <div class="roundBox">
    市面上我们常常会看到各种各样的设计模式书籍,Java设计模式、C#设计模式、Ruby设计模式等等。
  </div>
</div>

这样子你还会认为原先的选择器做法是正确的么。个人认为:Margin是阻碍模块重用的最大杀手,因为任何一个组件都或多或少会添加Margin这个属性来间隔开自身同他人的距离,直接拷贝原先代码,则先前适用Margin的属性反而成为了新组件位置的阻碍,及时分离Margin可以有效的解除组件同Margin的耦合,达到重复使用的效果。

这就是三权分立模式的全貌,width,margin,padding这三个属性完全的分离,大大提高的代码的可复用性,可维护性,解除三者的耦合,为将来的开发维护打下坚实的基础,也是CSS设计模式的优势所在。

当然设计模式也有他的二个弊端:

复杂性:获得可维护性往往要付出代价,那就是代码可能会变得更加复杂、更难被新手理解。三权分立模式中的导入公共CSS重用模块,书写多个class合并代替使用单一class都是所需要考虑和规范的。

性能:多数的设计模式对代码的性能会有所拖累,这种拖累可能微不足道,也可能完全不能接受,这取决于项目的具体要求。这里的三权分立模式就需要额外添加一个内部标签来分离padding属性。

实现设计模式比较容易,而懂得应该在什么时候使用什么模式则较为困难。你应该尽量保证所选用的模式就是最恰当的那种,并且不要过度牺牲性能。这对于个人开发甚至于一个团队的开发维护都具有莫大的帮助,个人认为使用三权分立模式是利远大于弊的,希望你也能够了解他的思想与作用,在团队开发中使用他,维护他,三权分立模式现实应用可以帮助你和你的团队开发出更加茁壮的代码。

写了那么多,只希望你能了解到使用合理的CSS设计模式可以帮助我们优化代码,其最终目的是为了CSS开发维护的高效性和可维护性,而其魅力也在于此。谢谢!

29 个评论

  1. [...] CSS設計模式之三權分立模式篇 [...]

  2. 博主太有才了

  3. 前段时间 Says:
    November 12, 2011 at 12:54 am

    我一直都这样做,只是没有把margin 单独分出来!

  4. 为什么说三权分立呢?

  5. 楼主写的真好!!!!我要关注你这个网站

  6. 楼主真是用心 顶你

  7. 不错,利用auto来自定义。不过就是要额外增加一块DIV。

  8. 博主分析的很有见地,对刚从后台开发起步的我有很大的启发。博客不错,很多文章实属精品,我决定收藏了!

  9. 这种写法确实不错,可维护性提高了,但是问题是我需要预先设置margin的值,将特定值的margin 用于具体模块时,当数值变化时我们需要将 html 代码 和 css 都做调整,本人拙见如果在带有margin的具体模块时增加一个相应的模块名称来控制间距,这样子就不需要预设 margin 值了,并且修改也更有针对性。

    • 你好,其实并没有你所说的那层顾虑,margin的值并非需要预先设置,而是利用CSS库中的margin部件,随时更换margin即可。

  10. 楼主,如你所说,把margin分立,在HTML模块上挂多个margin类。并且这个html模块在多个子页面中都用到。
    但如果有新的需求,让把这个模块的margin更改为其它值,那岂不是每个页面中的这个模块的margin类都要做修改?

    • 对的,你提出的问题或许会出现,但对于一个可以高度移植的模块,在模块设计之初就不应该有margin属性,当一个模块设计完毕之后在具体位置中摆放,再设置margin类是三权分立模式一个特点。现实中没有完美通吃的模式,实现一个模式比较容易,而懂得什么时候应用适合的模式是我们最应该考虑的。

  11. 原版橘子皮 Says:
    June 14, 2012 at 11:54 am

    真心受教!多谢楼主!

  12. 楼主以下的写法,似乎只是宽度自适应了,但是高度如何自适应呢?
    .box{width:300px; height:80px; margin:15px 0 0 10px;}
    .box .roundBox{padding:10px 15px; border:1px solid #ccc;}

    市面上我们常常会看到各种各样的设计模式书籍,Java设计模式、C#设计模式、Ruby设计模式等等。

    这样的写法roundBox, 在不指定width / height 的情况下,width+padding+border 会自动扩展到box的边缘,即宽度上,roundBox的border和box的border重合。但是height 只是紧紧的包裹着roundBox本身,并不扩展到box的边缘!

    如果roundBox写成height:100%, 因为此时roundBox的 height已经==了box的height,那么它+padding和border的之后会冲破父元素box边界,看起来来非常难看。
    指定width:100% 也是同样的效果

    有没有办法让roundBox 的height 适应box的高度?
    除了我用js设置roundBox的offsetHeight之外? 但是offsetHeight不是css的w3c属性,style当中也无法指定,难道只能用js吗?

    • 对于height自适应box高度问题在三权分立模式中实现的话最简单的方式就是最外层的box元素不设置高度,这其实也是最正确的三权分立模式的书写方式。
      文中例子外层box有高度为了加强说明在限定宽高的空间内,如何做到解除width/height, padding, border耦合计算关系。

  13. 很需要这样的文章来指导,收藏了,我一定还会回来的…

  14. cloudypea Says:
    January 21, 2013 at 3:55 pm

    太好了,灰常感动,最近对html和css超有兴趣,多研究研究

  15. 楼主,这样写,确实很方便,但是我去看看下一些网站,它们的css布局都没有采用这种分权的方式,为何呢

    • 每个站点都有他自己的写法,个人认为使用三权分立模式是利远大于弊的,希望你也能够了解他的思想与作用,在团队开发中使用他,维护他,三权分立模式现实应用可以帮助你和你的团队开发出更加茁壮的代码。

  16. 与我需要的宽度分离一样,最近在网站重构,需要吸取更多的精华,谢谢

  17. 干脆这样 你觉得如何
    一个层描述外围 margin float display overflow
    一个层描述中间 border padding
    一个层描述内部 width height color font
    3个层包裹一个内容元素

    • “外围”和“内部”俩个地方可以合并在一起,“中间”分为另外一部分,否则div太多,反而不好维护。

  18. CSS设计模式的问题 就是 层太多了 怎么办

  19. [...] 这是看别人的博客学到的,可以去http://www.hicss.net/separation-of-powers-model-in-css-design-patterns/ [...]

  20. 英雄不问出路 Says:
    October 29, 2013 at 8:22 pm

    学习了

  21. 在IE7及以下,两个宽度都为50%的块级元素并排一行,对它们的子元素添加边框或内边距,不设置宽度,它们会撑大它们的父元素,从而导致父元素中后面的一个被挤下去,不知道站长是怎么解决的

  22. 感谢分享,很受用!

  23. 楼主大才,向前辈致敬

  24. cool

发表评论