從試著改進可重用CSS做起
回想起每次更新泊學網站,最讓我頭疼的,就是改寫CSS。在不同的階段,對CSS不斷深入的理解,對網站內容的調整,對UI的重用需求,都影響著CSS的編寫方式,因此,稍不留神,你的程式碼理解就會充斥著各種風格和各種作用的CSS,讓你什麼時候想起這些,都覺得心情不那麼愉快。
因此,就和大家分享一些心得,如何理解CSS,以及如何更有效的編寫CSS。
從基於語義的CSS說起
首先,我們從一個最簡單的例子開始,回想一下你的第一個CSS例子,一定和下面這樣是類似的,所謂CSS,表達的就是頁面DOM的樣式:
<p class="text-center">
Hello world!
</p>
然後,在text-center
裡,我們指定文字居中對齊的樣式:
.text-center {
text-align: center;
}
很簡單對不對?隨著樣式越寫越多,我們很快就會開始關注到一些編寫CSS的建議。例如:應該把HTML和CSS的職責分開,HTML中不應該包含任何和具體樣式(例如居中對齊)有關的資訊,這些具體的樣式都應該放到CSS中處理。
於是,我們就開始嘗試著用樣式要表達的語意來替換掉它表達的具體樣式:
<p class="greeting">
Hello world!
</p>
<style>
.greeting {
text-align: center;
}
</style>
這樣看起來就好多了。無論.greeting
指定的具體樣式是什麼,都不影響它在HTML中表示歡迎資訊樣式的含義。這樣,從理論上說,我們就可以用一套HTML模板,實現各種不同風格的UI了。
於是,我們就開始基於這種語義的方式,來編寫各種介面了。例如,我們新增一個表示視訊作者的資訊卡,它的HTML模板是這樣的:
<div class="container">
<div class="creator-info">
<img src="http://7xncmx.com1.z0.glb.clouddn.com/dora11.png" alt="">
<div>
<h2>Mars</h2>
<p>
The creator of boxue.io. Bla bla bla...
</p>
</div>
</div>
</div>
同樣,在這個模板裡,creator-info
是一個按語義命名的樣式,接下來,是這個樣式的實現:
.creator-info {
background-color: white;
border: 1px solid rgba(0,0,0,0.1);
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
> img {
display: block;
width: 100%;
height: auto;
}
> div {
padding: 1rem;
> h2 {
font-size: 1.25rem;
color: rgba(0,0,0,0.8);
}
> p {
font-size: 1rem;
color: rgba(0,0,0,0.75);
line-height: 1.5;
}
}
}
它看上去的結果是這樣的:
這裡,我們的重點不是這些樣式的具體內容,而是這個CSS的結構,如果我們把所有具體的樣式都去掉,你就會發現,這個樣式嚴重依賴於HTML中DOM的層次結構:
.creator-info
> img
> div
> h2
> p
因此,儘管在HTML中,我們依靠基於語義的樣式剝離了CSS,但這種方式卻很容易在CSS中暴露過多和HTML相關的細節。因此,這樣的做法,實際上並沒有完全實現剝離CSS和HTML職責的目的,我們需要更好的做法。
把樣式從DOM結構中剝離出來
為了避免樣式依賴DOM結構的問題,我們的思路是:讓樣式的命名方式兼具格式和語義的功能。然後,在DOM裡,對不同位置的元素,使用對應的樣式。這裡,我們借鑑了BEM命名方法,對我們要使用的樣式名稱,統一使用這樣的命名格式:主體-依賴主體的內容__內容的屬性:
<div class="container">
<div class="creator-info">
<img class="creator-info__image"
src="http://7xncmx.com1.z0.glb.clouddn.com/dora11.png" alt="">
<div class="creator-info__content">
<h2 class="creator-info__name">Mars</h2>
<p class="creator-info__description">
The creator of boxue.io. Bla bla bla...
</p>
</div>
</div>
</div>
這次,我們給DOM中,每一個需要樣式的元素繫結了有特定命名規則的樣式。這樣,在樣式表裡,所有的樣式就可以是扁平結構的了:
.creator-info {
background-color: white;
border: 1px solid rgba(0,0,0,0.1);
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.creator-info__image {
display: block;
width: 100%;
height: auto;
}
.creator-info__content {
padding: 1rem;
}
.creator-info__name {
font-size: 1.25rem;
color: rgba(0,0,0,0.8);
}
.creator-info__description {
font-size: 1rem;
color: rgba(0,0,0,0.75);
line-height: 1.5;
}
還記得當時自己把泊學網站樣式修改成這樣之後,著實興奮了一陣子,因為這樣的方式似乎徹底解決了HTML模板和CSS之間相互依賴的問題。
處理重複的介面佈局
但是沒過多久,我就發現了新的問題。當我編寫首頁上每個視訊系列的UI元件時,結構上,它和之前的作者資訊卡幾乎是一樣的。於是我幾乎不假思索的寫出了這樣的HTML模板:
<div class="container">
<div class="series-info">
<img class="series-info__image"
src="https://dn-boxueio.qbox.me/YourFirstMLProject@2x-911fdc56906f05fc0757e5577084c840.jpg"
alt="">
<div class="series-info__content">
<h2 class="series-info__name">Machine Learning from Scratch</h2>
<p class="series-info__description">
Let`s create a real-world machine learning demo from scratch.
</p>
</div>
</div>
</div>
它同樣包含了一個封面圖,一個標題和一個簡介。只不過,我們把樣式名稱中的主體從creator
換成了series
。但是,當我要給這些新的樣式設定值的時候,就有點兒糾結了。該如何設定這些series-***
的樣式呢?你可能想到了兩種選擇。
第一種,最直接的方法,就是把series-***
按照creator-***
複製一遍。這肯定可以工作,但是估計沒多少人會認同這種做法,因為它違反了Don`t Repeat Yourself的原則;
第二種,如果你使用了SCSS,就可以實現從某個樣式繼承這樣的用法:
.series-info {
@extend .creator-info;
}
.series-info__image {
@extend .creator-info__image;
}
.series-info__content {
@extend .creator-info__content;
}
.series-info__name {
@extend .creator-info__name;
}
.series-info__description {
@extend .creator-info__description;
}
但這樣做也有它自己的問題,@extend
應該只在彼此有關聯的樣式之間使用,而不僅僅是為了避免重複編寫相同的樣式。並且,如果稍後我們還要視訊資訊卡呢?真的需要這些使用了相同樣式的selector麼?顯然,目前的這種解決方案仍舊不夠理想。
去除掉過於細緻的語義
實際上,造成樣式難以重用的原因,是因為selector表達的語義過於細緻了。語義越細緻,重用就越困難。因此,我們只要把這種繫結類似介面佈局UI的selector,起個名字替代掉類似creator
或series
這樣的名字就好了:
.media-card {
background-color: white;
border: 1px solid rgba(0,0,0,0.1);
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.media-card__image {
display: block;
width: 100%;
height: auto;
}
.media-card__content {
padding: 1rem;
}
.media-card__name {
font-size: 1.25rem;
color: rgba(0,0,0,0.8);
}
.media-card__description {
font-size: 1rem;
color: rgba(0,0,0,0.75);
line-height: 1.5;
}
這樣,無論是作者資訊還是視訊系列資訊,就都可以用同一套樣式來表示了:
<div class="container">
<div class="media-card">
<img class="media-card__image"
src="https://dn-boxueio.qbox.me/YourFirstMLProject@2x-911fdc56906f05fc0757e5577084c840.jpg" alt="">
<div class="media-card__content">
<h2 class="media-card__name">Machine Learning from Scratch</h2>
<p class="media-card__description">
Let`s create a real-world machine learning demo from scratch.
</p>
</div>
</div>
</div>
甚至,只要UI佈局和media-card
描述的體系相同,這套樣式就可以直接重用。
但是情到此結束了麼?顯然沒有,現在,你可能又會想了:假設我們需要修改作者資訊卡的樣式,但仍儲存視訊系列資訊卡的樣式該怎麼辦呢?
如果像之前一樣,它們的樣式是獨立的,只修改對應的樣式就好了。現在,它們共享樣式了,我不僅要建立新的樣式,還要連同對應的HTML一起修改,這樣做真的好麼?
實際上根本沒有絕對的職責分離
為了回答這個問題,我們得回到這一節開始提出的目的:分離HTML和CSS的職責。面對這個話題,我們直覺上就會認為,只有徹底剝離了才算完成達成目標。但實際的情況則是,它們兩者根本無法做到完全分離。我們只能根據自己專案的實際情況,選擇一種適合自己的方式。
對於哪些具備詳細語義(.creator-info
和.series-info
)的樣式而言,此時,HTML是獨立的,它完全不關心這些DOM會長成什麼樣子。它只暴露了一個介面,允許我們定製其中的樣式。因此,這種選擇下的CSS不是獨立的,它依賴於樣式繫結的HTML,需要以HTML為參考,定義樣式的內容。
對於那些具備中立語義(.media-card
)的樣式而言,此時,CSS是獨立的,它完全不關心自己會被用在什麼元素上。此時,HTML就不是獨立的了,它需要知道樣式表提供了哪些內容,並基於這些內容,來編排DOM。
實際上,這兩種方法,沒有絕對的誰優誰劣的問題。只是你要想清楚,哪種方式更適合自己的專案。
What`s next?
看到這裡,如果你和我之前有過類似的困惑,現在,你應該躍躍欲試地要調整下自己的CSS了。先彆著急,在下一節裡,我們將繼續討論,如何通過合理的命名,最大化實現樣式的可重用目標。