推薦查看如下文章: 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/