推荐查看如下文章: https://segmentfault.com/a/1190000000704006 关于BEM,SMACSS,OOCSS的通俗易懂的介绍javascript
http://philipwalton.com/articles/css-architecture/css
http://ux.mailchimp.com/patternshtml
对于不少web开发人员来讲,掌握了css意味着你能够作出很漂亮的mockup而且将这个mockup用代码完美地表现出来。你再也不使用table布局,你尽量少的使用image。若是你认为你CSS很牛,你须要使用最新的技术:好比media query,transition,transform等。虽然这些都是做为一个优秀的css开发人员必备的功底,可是有一个重要的方面却每每被忽视,本文就试图说说这个被css开发人员经常忽略的方面。前端
有趣的是,和其余programming语言相似:一个Rails开发人员并不会仅仅由于他能作出符合功能的程序而被认为是一个优秀的程序员。功能符合要求是基本要求。代码是否readable?是否易于修改和扩展?是否和其余部分充分解耦?是否方便scale?这几个问题的答案每每决定了对一个程序员的评判。对于CSS,也有同样的要求。今天的web应用愈来愈大和复杂,一个思虑不周的css架构将会拖慢开发进度。是时候向通常通用程序语言同样来评估好的开发方法了。html5
在CSS社区,一个通用的best practices是很难被你们公认的。本文并非要引发什么是css best practice的论战,我想咱们应该首先定义好咱们的目标。若是咱们对目标是认同的,但愿咱们可以首先认同哪些是错误的实践,由于它们将拖后咱们的开发过程。java
我相信一个好的css架构的目标和一个好的软件开发目标没有什么大的区别。我但愿咱们的CSS有如下几个特色和目标: predictable,reusable,maintainable,scalable:程序员
可预测的css意味着你的rules正如你所预期的同样工做。当你增长或者更新一个rule,它不该该影响到你不但愿影响到的部分。web
css rule应该被抽象和完全解耦一边你能够以已作好的部分中很快建立新的components,而不用从头开始解决一些你已经解决过的问题编程
当有新的components或者功能须要增长,更新或者从新安排到你的网站上时,作这个工做不须要refactoring已存的css代码。增长一个X组件并不会由于X的存在而破坏了Y组件的功能或形状;segmentfault
随着你的网站在尺寸和复杂度上不断膨胀,一般须要更多的开发人员来维护它。可扩展的css意味着它能够很方便地被一我的或者一个大的engineering团队来维护。这也意味着你的css架构易于学习。今天只有你一我的来维护css不意味着明天也是这个样子。
在咱们探讨达成好的css架构目标的方法以前,我想若是先看看常见的阻碍咱们达成好架构目标的很差的practice是有益的。一般咱们经过重复的犯错误中学会寻找一个好的方法。
在几乎每一个网站上老是有个别特殊的可视元素,它和正常状态下如出一辙,可是却有一个例外状况。面对这种场景,几乎每一个新的css developer,甚至是老手会使用下面的方法:你指出这种场景下元素的parent(或者本身主动建立一个),而且写出下面的css来处理他:
.widget { background: yellow; border: 1px solid black; color: black; width: 50%; } #sidebar .widget { width: 200px; } body.homepage .widget { background: white; }
乍一看上面的代码很好啊,可是咱们来经过比对咱们的目标来看看有什么不妥:
首先,例子中的widget是不可预测的。一个用了这个widgets屡次的开发人员会预期该widget具体是什么式样,有什么功能,他都有预期。可是当她在sidebar里面时或者homepage时,他们却有着不一样的样式,即便html markup多是彻底同样的。
它一样不是很reusable和scalable.若是它在Homepage上的长相但愿放到其余页面上时会怎样?新的rule必须被添加才能达到目的。
最后,它也不是很好维护,由于若是widget须要redesign,则必须在多处来更新css。
试想一下若是这种编码方式在其余语言中,你可能会定义一个class,而后在代码的其余地方,你将修改类的定义以便知足一个特定的需求。这直接破坏了 open/closed principle of software development
software entities(classes,modules,functions etc)should be open for extension, but closed for modification.
本文的后面咱们会看看如何可以在不依赖于parent selector的状况下实现修改components.
越复杂的选择器每每意味着css和html的耦合越深;依赖于HTML Tag或者他们的组合每每貌似html干净,可是却使得css愈来愈大愈来愈难以维护和肮脏。
#main-nav ul li ul li div { } #content article h1:first-child { } #sidebar > div > h3 + p { }
上面代码示意了前面的观点。第一行可能要给一个dropdown menu设置样式,第二行可能要要给article的h1应该和其余的h1长的不同;最后一行可能要给sidebar中的第一个p设置更多的spacing。
若是HTML永远不会变动,那么可能上面的代码安排还能够说的过去,可是要知道假设HTML永远不会变动自己就是一个问题。过于复杂的选择器使得HTML去除样式的耦合印象深入(可是实际上只是html和css解耦了,可是css和html却彻底没有解耦!由于css须要依赖于html markup的structure),可是他们每每不多可以帮助咱们达到咱们组织好咱们的css架构的目标。
上面这些例子基本上没法重用。因为selector指向markup中很是特定的一个地方,那么另一个地方若是html的结构有所不一样,咱们如何可以重用这些style?第一个selector做为例子,若是在另一个页面中相似的dropdown咱们是须要的,而它又不在一个#main-na元素中,那又怎么办呢?你必须从新建立整个style.
这些选择器若是html须要变动将会变得不可预测。想一想一下,一个开发人员但愿更改第三行中的<div>成为html5 <section>tag,整个rule将再也不工做。
最后,既然这些选择器只有在html保持永恒不变时有用,那么这个css就不具备可维护性和可扩展性。
在大的应用中,你必须作些折中。复杂选择器的脆弱性对于其“保持html clean”的优点每每由于代价太大而名存实亡。
当建立一个可复用的设计组件时,很是广泛的作法是scope(as it were)the component's sub-elements inside the component's class name.例如:
<div class="widget"> <h3 class="title">...</h3> <div class="contents"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. In condimentum justo et est dapibus sit amet euismod ligula ornare. Vivamus elementum accumsan dignissim. <button class="action">Click Me!</button> </div> </div> .widget {} .widget .title {} .widget .contents {} .widget .action {}
这里的想法是.title,.contents,.action sub-element classes能够不用担忧这里的style会溢出影响到其余有相同class name的元素的状况下被安全地style。这确实是事实,可是这并不会阻止其余地方具备相同class name的style来污染这个组件。
在一个大型项目中,一个通用的相似.title的class name颇有可能被其余的context中所定义。若是这个状况发生了,那么widget的title将可能和咱们的预期相差甚远。
过分通用的类名称将产生不可预知的css!
有时你可能须要设计一个组件,它须要top,left偏移20个px:
.widget { position: absolute; top: 20px; left: 20px; background-color: red; font-size: 1.5em; text-transform: uppercase; }
后面,你可能须要这个组件也放到其余的页面的其余地方。上面的css将不能很好的完成重用的要求,由于它在不一样的context中不能被复用。
问题的根本缘由是你将这个选择器作了太多的工做。你在同一个rule中既定义了look and fell又定义了layout and position。look and feel是可重用的可是layout和position却不是reusable的。因为他们被在一个rule中定义,那么整个rule都被连累成为不可重用的了!!
注意:通常地,咱们把对一个component/element的css style划分为如下几类:
下面就是按照上面三个分类思路组织的一个semantic css类:
.OrderActionsPane { /* --- Layout --- */ height: 45px; padding: 3px; border-bottom-width: 1px; /* --- Typography --- */ font-size: 14px; font-weight: bold; /* --- Appearance --- */ background: #fff; background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fff), color-stop(100%, #ededed)); background: -webkit-linear-gradient(top center, #fff, #ededed); background: -moz-linear-gradient(top center, #fff), #ededed); background: -o-linear-gradient(top center, #fff, #ededed); background: linear-gradient(top center, #fff, #ededed); box-shadow: 0 0 5px rgba(0,0,0,.25); -webkit-box-shadow: 0 0 5px rgba(0,0,0,.25); -moz-box-shadow: 0 0 5px rgba(0,0,0,.25); -ms-box-shadow: 0 0 5px rgba(0,0,0,.25); -o-box-shadow: 0 0 5px rgba(0,0,0,.25); border-bottom-style: solid; border-bottom-color: #e6e6e6; }
之因此这么分类是由于layout rules(box model stuff,mostly)对于你的layout布局有着最大的影响。Type rules则可能影响你的layout(好比若是你有一个fluid container而增长你的font size,则这时container就会增加).而Appearance style则和你的layout无关。
注意我将border rule作了隔离,你可能会说我只要写一行"border-bottom:1px solid #e6e6e6"就行了啊,可是一旦这么作你将失去appearance和layout effects of the border之间的隔离!
另外考虑的是你在layout时须要使用的单位:px适用于solid layout,可是若是你使用xx%或者em则比较适合于mobile-friendly/scalable layout的设计。
http://stackoverflow.com/questions/9067504/is-it-worth-separating-style-from-layout-in-css
这在起初可能并不能看到他的危害,一般对于css悟性较差的开发人员来讲,他们的动做就是copy和paste。若是一个新的team member但愿基此建立一个特定的组件,好比说.infobox,他们极可能首先尝试那个class.可是他们发现因为position并不符合他们的要求,他们会怎么作?大多数新手并不会把rule重构,他们每每作的是拷贝他们须要的feel and look部分css代码到另一个selector中去并加以引用,而这将不可避免地形成代码的重复。
全部上面的bad practice都有一个共性:他们place far too much of the styling burden on the CSS.(将样式定义所有放到css上去!)
这句话听起来有些奇怪。由于,毕竟它是stylesheet,难道咱们不该该将样式定义的重担放到css上去吗?
对这个问题的简单回答是“yes”。可是一般,事情并不那么简单。内容和呈现(样式)的分离是一个好的东西,可是仅仅经过把你的css和你的HTML相隔离并不意味着你的内容和呈现(样式)隔离了。也能够换句话说,若是咱们的css须要关于html结构的密切知识才能工做的话,仅仅经过将css和你的html分开是不能达到咱们的css良好架构的目标的。
并且,HTML自己并不只仅是content,它也老是structure。一般这个html结构会包含一些container元素,而这些container元素自己除了容许css隔离一组元素外自己并没有其余content方面的含义。
And often that structure consists of container elements with no purpose other than to allow the CSS to isolate a certain group of elements. 这种状况下即使是没有presentational css classes,这也仍然是清晰的presentation和html混合工做的例证。可是是否将presentation和content相混合就很好呢?
我相信,在当前的HTML和css状态下,内容和样式混合一般是必要的甚至是睿智的:若是咱们将HTML和css放到一块儿工做做为presentational layer,而content layer能够经过模版和partial: templates and partials被抽象出来。
若是你的HTML和CSS准备一块儿打包工做来造成web应用的presentation layer,那么他们须要以协同推动好的css架构的方式来工做。
我发现的最好的方式是:对于CSS来讲,它应该预设越少的HTML结构越好。CSS的职责是定义一组可视化元素自己的渲染效果而且确保这些元素的渲染效果始终如一符合预期,而不会(目的是为了最小化CSS和HTML的耦合)随着他们在HTML的不一样位置而发生外观的变化。若是一个组件须要在不一样的场景中呈现出不一样的渲染结果,那么这就应该是另一种东西(something different),而这须要HTML负起责任to call it that.
做为一个例子,CSS可能经过.button class来定义一个button component,若是HTML但愿一个特别的元素看似像一个按钮,则应该使用.button类。若是有一种场景:但愿这个button看起来不一样(或许要求更大或者full-width),那么css须要使用一个新类来定义那个样式,而HTML则经过引用这个新的class来实现新的外观样式。
CSS定义了一个组件的外观样式:即长什么样,而HTML经过给页面上的element赋值一个class来定义元素有那个CSS外观样式。须要CSS知道越少的HTML structure则越好。
确切地声明在HTML中你想要什么的一个巨大好处是:它容许其余的开发人员经过查阅markup便能确切地知道元素将会长成什么样子。目的性是很是明显的,没有这种practice,几乎不可能知道一个元素的外观是不是有意为之的仍是偶然为之的,而这将给团队带来很大的困惑和干扰。
对于在markup中放置一大堆的classes的一个广泛异议是:这样作须要大量的effort。一个单一的css rule能够target一个特定组件的成千上万个实例(instance)。真的有必要为了在markup中明确地申明要达到什么外观目标而将一个class写上成千上万次吗??
虽然这个concern自己是没有问题的,可是他却容易让人误入歧途。在这里有一个可能的暗示:或者你经过在css中使用一个paraent selector或者你手工将那个html class写上10000便,可是要知道必定有其余的更好方法。View level abstraction in Rails或者其余的框架对于并不经过对同一个class书写成千上万遍就可以使得visual look explicitly declared in the HTML!~
在对上面的错误屡犯屡错后,痛定思痛,我有下面的几个建议。虽然这并不意味着必定对,但个人经历显示:坚持这些原则将帮助你更好地达到良好css架构的目标。
确保你的选择器不会style unwanted元素的最佳方法是不给他们这个机会。一个相似#main-nav ul li ul li div的选择器可能会很是容易地避免将样式应用到unwanted elements上去。另外一方面,一个相似.subnav类,将几乎不可能有意外应用style到一unwanted element上去的机会。只对你须要style的元素来应用相应的css class是保持css predictable的最佳方法。
/* Grenade:手榴弹 */ #main-nav ul li ul { } /* Sniper Rifle 来福枪 */ .subnav { }
对上面的两个例子,想象第一种就像一个手榴弹,第二个就像来福枪。手榴弹可能今天能够很好地工做,可是你永远不会知道有一天一个笨蛋可能会跑到手榴弹的冲击波里面去(而受害)
我已经提到过一个组织良好的component layer能够帮助loosen the coupling of HTML structure in the CSS.除此以外,你的CSS components本身应该是模块化的。components应该知道应该如何style他们本身而且作的很好,可是他们不该该负责他们的layout或者positioning,他们也不该该过多地假设他们将如何被其余元素来surrrounding.
通常来讲,components应该定义他们的外观样式,可是不该该定义他们的layout或者position属性。当你发现像background,color,font相似的属性和position,width,heigh,margin属性放在一个rule时,你就须要特别的注意了!!
layout和position属性要么应该被一个独立的layout class来处理或者一个独立的container element来处理。(记住:为了有效地实现内容(content)和呈现(presentation)的隔离,一般须要separate content from its container做为基本的原则)
咱们已经调查过为何parent selectors在封装而且防止样式交叉污染上面并非100%有效的。一个更好的方式是将namespace应用到classes类自己上去。若是一个元素是一个可视组件的一个成员,每一个他的sub-element类应该使用component的base class name做为他的namespace.
/* High risk of style cross-contamination */ .widget { } .widget .title { } /* Low risk of style cross-contamination */ .widget { } .widget-title { }
namespacing your classes将保持你的组件是自包含的和模块化的。这将使得和已存class相冲突最小化,而且也会lower the specificity required to style child elements。
当一个已经设计好的component须要在特定的context中长的略微有所不一样的话,经过建立一个modifier class来扩展它。
/* Bad */ .widget { } #sidebar .widget { } /* Good */ .widget { } .widget-sidebar { }
咱们已经看到基于一个组件的父元素来修改组件样式自己的缺点,可是还须要重申:一个modifier class(修饰类)能够在任何地方使用。Location based overrides can only be used in a specific location. Modifier classes也能够随你任意次数的重复使用。最后,一个modifier class就在HTML里清晰表达了开发人员的意图。另外i啊一方面,Location based classes对于开发人员来讲,若是仅仅检阅HTML,开发人员彻底是不知道这个location based样式的存在的,而这将大大增长这个selector将被忽视的可能性(这每每意味着css代码重复)
Jonathan snook在他的新书SMACSS中主张:你应该将你的CSS rules组织称4个不一样的类别: base,layout,modules(components)以及state.Base由reset rules和元素的默认style构成;layout则负责网站级别的元素positioning以及好比相似于grid system的通用layout helper组成;modules则是一些能够重用的可视化元素,而state则指能够经过javascript切换为On或者off的一些样式。
在SMACSS系统中,modules(和本文中所称的components是相同的概念)则包含了大部分的CSS rules,因此我一般发现颇有必要将modules/components继续分解为抽象的templates.
components是一些单独的可视元素。而templates不多描述look and feel。相反,他们是单一的,可重复的pattern,这些templates组合在一块儿能够造成一个components.
以一个实际的例子来讲明这个概念,一个component多是一个modal dialog box. modal可能须要在header中使用网站的签名背景,可能须要一个drop shadow,可能须要一个右上方的close button,而且被水平垂直地布放在屏幕中间。这4个pattern中的每个可能可以在这个网站上不停地重复使用,因此你不该该对这些pattern不断去code,而应该不断重复使用这些pattern(template).这样他们都是一个template而在一块儿配合使用他们就造成了modal component.
我一般在html中并不会直接使用template classes.相反,我使用一个preprocessor来包含这些template style到component defination中去。我将后续继续探讨这方面的内容
任何参加过大型项目的人员可能都会无心发现一个html element拥有一个目的彻底不明确的clsss.你想删除它,可是你又在犹豫:由于这个class有可能有一些你并不知道的目的而存在,因此你不敢轻易删除。这种状况一再发生,随着实践的推移,你的html就将充斥着不少并没有存在的理由但又继续存在着的一些class,仅仅是由于开发人员惧怕删除它。
这里的问题是:在前端开发中,class一般被赋予了太多的责任:他们负责style HTML元素,他们做为javascript的hook,他们放到HTML中用于feature detection,他们用于自动化测试,等等。。。
这就是问题。当class被应用的太多,而部分可能已经超出了传统的css范畴,从HTML中删除他们将因为人们不确信是否会产生问题而变得让人胆战心惊。
然而,若是树立一套公约,这个问题就能够被完全避免。当你在HTML中看到一个class,你应该可以迅速地告知这个class的目的是什么。个人建议是给全部非style目的的class一个前缀。我一般使用.js-做为javascript匹配使用目的的class,使用.supports-来做为modernizr js库使用的class。全部没有prefix的class用于且仅被用于styling的工做
遵循这种公约,将使得从HTML中发现和删除那些没必要存在的class变得很是容易:只须要搜索style目录便可。你甚至能够将这个过程自动化:经过检查documents.styleSheets对象,你就能够把那些再也不document.styleSheets中引用的class能够安全地删除掉。
通常地说,正如将内容和呈现(样式)相隔离是一个业内的最佳实践,将样式(presentation)和功能相隔离也是很是重要的。使用styled classes做为javascript hook将使得你的CSS和javascript深度耦合,而这将使得更新一些元素的look而不会破坏functionality变得很是困难,甚至是不可能。
这些天大多数人使用'-'中杠来隔离单词。可是hyphens并不足以区别不一样的class。
Nicolas Gallagher最近写了一片关于解决这个问题的方案。为了演示naming convention,看看下面的例子:
/* A component */ .button-group { } /* A component modifier (modifying .button) */ .button-primary { } /* A component sub-object (lives within .button) */ .button-icon { } /* Is this a component class or a layout class? */ .header { }
看看上面的class,几乎不可能说出他们将会应用什么样式。这不只在开发中增长混淆,并且也所以很难自动化地测试css和html。而一个组织良好的结构化的naming convention则容许你看到一个class名称,你就可以清楚地知道它和其余的class之间的关系以及它应该在HTML的哪一个地方出现----使得命名更简化而测试也更简单。。。
# Templates Rules (using Sass placeholders) Template在这里就是一些能够重复使用的pattern %template-name { } %template-name--modifier-name { } %template-name__sub-object { } %template-name__sub-object--modifier-name { } # Component Rules .component-name { } .component-name--modifier-name { } .component-name__sub-object { } .component-name__sub-object--modifier-name { } # Layout Rules .l-layout-method { } .grid { } # State Rules .is-state-type { } # Non-styled JavaScript Hooks .js-action-name { }
将第一个例子按照新的思路从新作一下:
/* A component */ .button-group { } /* A component modifier (modifying .button) */ .button--primary { } /* A component sub-object (lives within .button) */ .button__icon { } /* A layout class */ .l-header { }
维护一个有效的组织良好的css架构是很是困难的,特别是对于大型团队来讲。少数几个bad rules可能像滚雪球同样使得整个css造成一个没法管理的混乱。一旦你的应用程序的css进入specificity的战争和!important的对决,那么几乎不可能不经过推倒重来来改变这个状况。因此要避免这些问题,必须在项目的起始阶段就要作好谋划。
幸运的是,有一些工具能够帮助咱们更好的规划和保持css架构的有效正确,不走偏路
如今谈到CSS几乎不可避免要谈到预编译工具。在赞赏他们的功用以前,得先提几点须要注意的地方:
Preprocessor能够帮助你更快地编码css,可是并不能帮助你更好!最终sass/less被编译为普通的css,相同的规则将被应用。若是说一个预编译工具能帮你更快地写css,那么他也可让你更快地写出bad css.因此在想到preprocess能够解决你的问题以前,你必须对一个好的css架构有清晰的概念。
许多预编译工具的所谓feature实际上对于css架构是很是有害的。下面就是以sass为例,列出一些我会避免使用的feature:
预编译器的最好的部分是相似@extend和%placeholder的函数。他们容许你方便地管理css abstraction而不用被最终放到编译的css中去。
@extend应该当心使用由于有时你但愿那些class放到你的html中去。好比,当你第一次学习@extend时,你可能会在modifier class中像下面的用法来使用:
.button { /* button styles */ } /* Bad */ .button--primary { @extend .button; /* modification styles */ }
这样作的问题是:你将会在html中丢失inheritance chain。这样就将很难使用javascript来选择到全部的button instances。
做为一个通用的规则,我永远不会extend 一个我可能接下来就知道type的UI Components or anything
这是templates应该负责的地方,也是帮助分别template和components的另一种方法。一个template是一个你永远不会在应用逻辑中target的部分,所以可使用preprocessor安全地extend.
下面就是使用modal的例子来具体解释:
.modal { @extend %dialog; 在这里%dialog,drop-shadow,statically-centered,background-gradient都是一个template,能够被无限次地重复使用,可是又不会被直接target,就像function同样 @extend %drop-shadow; @extend %statically-centered; /* other modal styles */ } .modal__close { @extend %dialog__close; /* other close button styles */ } .modal__header { @extend %background-gradient; /* other modal header styles */ }
CSS并不只仅是visual design。不要仅仅由于你是在写css而不是编写PHP代码而抛弃编程的通常最佳实践。像OOP,DRY,Open/close原则,seperation of concerns等等,一样适用于CSS的编写。最低的底线是不管你如何组织你的代码,只要这种组织方法有利于你更好的开发css更利于长期的维护,就是好的方法
BEM与OOCSS的映射概念图
https://css-tricks.com/bem-101/ :一篇关于BEM的很好的文章
https://en.bem.info/method/definitions/
http://getbem.com/introduction/