可擴(kuò)展 CSS 的演變

大家好,我是 CUGGZ。
自 Web 誕生以來,我們編寫和思考 CSS 的方式發(fā)生了巨大變化。從基于 table 的布局到響應(yīng)式網(wǎng)頁設(shè)計(jì),已經(jīng)走過了漫長的道路,現(xiàn)在進(jìn)入了由現(xiàn)代 CSS 功能提供支持的自適應(yīng)布局的新時代。而管理和組織 CSS 一直具有挑戰(zhàn)性,很難達(dá)成共識。

深入研究在大型項(xiàng)目上擴(kuò)展 CSS 的問題,可以加深對 CSS 的理解。我們將了解隨著時間的推移出現(xiàn)和改變的各種 CSS 最佳實(shí)踐的演變。最后,我們將很好地掌握過去在大型項(xiàng)目上擴(kuò)展 CSS 的方法,以及 Tailwind 和其他一系列流行工具如何以反直覺的方式解決這些問題。

CSS 出現(xiàn)之前
一開始,Web 只有 HTML。我們將其全部大寫,并直接在 HTML 上使用屬性來設(shè)置頁面樣式:

<BODY>
  <P SIZE="8" COLOR="RED">LOUD NOISES</P>
</BODY>
這是一個黑暗的時期,除了可用的樣式數(shù)量有限之外,一個明顯的限制就是需要不斷地重復(fù)。當(dāng)時一個典型例子是舊的 Space Jam 網(wǎng)站。

現(xiàn)在有些人可能會想“這看起來像是傳遞到組件中的 props”。正如我們稍后將看到的那樣,事情往往會隨著創(chuàng)新周期的轉(zhuǎn)折而全面發(fā)展。

樣式表和關(guān)注點(diǎn)分離
CSS 的出現(xiàn)就可以消除所有這些重復(fù)。樣式表使我們能夠以聲明方式設(shè)置頁面樣式,用很少的代碼即可影響大量元素:

p {
  color: red;
}
現(xiàn)在可以分別考慮內(nèi)容的結(jié)構(gòu)及其視覺外觀和布局。將布局問題從使用 table 的 HTML 轉(zhuǎn)移到了 CSS 中。

隨著時間的推移,開始在一個名為 CSS Zen Garden 的網(wǎng)站上收集示例。CSS Zen garden 成為展示人們?nèi)绾蝿?chuàng)造性地使用 CSS 的中心。人們可以以有趣和獨(dú)特的方式提交重新設(shè)計(jì)了相同 HTML 樣式的 CSS 文件。

這對于傳播將內(nèi)容與其樣式、可重新設(shè)置樣式的 HTML 以及將可主題化的“皮膚”應(yīng)用于核心框架的想法等方面非常有影響力。

尋找最佳實(shí)踐
我們開始構(gòu)建更復(fù)雜的站點(diǎn)和應(yīng)用時,對 CSS 提出了新的要求。在最佳實(shí)踐出現(xiàn)之前,任何新技術(shù)通常會在幾個周期采用不同的方法。

Less 和 Sass 等工具的出現(xiàn),擴(kuò)展了原生 CSS 的功能。為我們提供諸如變量和計(jì)算函數(shù)之類的功能,極大地改善了開發(fā)人員的體驗(yàn)。

隨著在這些樣式表上花費(fèi)更多的時間,我們想方設(shè)法組織所有這些規(guī)則和選擇器。

許多不同的擴(kuò)展 CSS 的模式就出現(xiàn)了。這些試圖在維護(hù)、性能和可讀性之間取得平衡,被稱為“CSS 架構(gòu)”。

在深入研究這些架構(gòu)之前,首先來了解為什么在大型項(xiàng)目中管理 CSS 會很快變得復(fù)雜。

為什么CSS在大型項(xiàng)目難以管理?
“大型”指的是多個方面的交集,包括人員、工具、流程和性能。

有效擴(kuò)展需要謹(jǐn)慎管理復(fù)雜性的增長。因此,隨著系統(tǒng)的發(fā)展,它仍然是可理解的、可變的和高效的。添加新代碼的成本盡可能低,人們才有信心更改和刪除舊代碼。

級聯(lián)(CSS中的C)起源于 Web 早期。瀏覽器可以將默認(rèn)樣式應(yīng)用于這些新的電子文檔。然后,文檔作者可以提供自己的樣式,這些樣式可以被單個用戶的首選項(xiàng)覆蓋。然后文檔作者可以提供他們自己的樣式,這些樣式可以被個人用戶偏好覆蓋。

使 CSS 強(qiáng)大的相同屬性也使得在大型項(xiàng)目中難以實(shí)現(xiàn)這些擴(kuò)展屬性。特別是它的全局命名空間、級聯(lián)規(guī)則和選擇器特異性。

全局命名空間
如果謹(jǐn)慎使用,全局 CSS 命名空間會非常強(qiáng)大。但在大型項(xiàng)目中,它會帶來很多問題。

當(dāng)一切都是全局的時,任何事情都可能出乎意料地影響其他事情。要么是現(xiàn)在,要么是將來的某個時候,當(dāng)事情發(fā)生變化時。

這很快就會成為問題。在其他語言中,沒有將所有內(nèi)容都放在全局命名空間中是有原因的。隨著更多代碼的添加,代碼會變得越來越難以預(yù)測和維護(hù)。

值得注意的是,CSS 的 Cascade Layers 是一項(xiàng)全新的功能,可以幫助解決這個問題。

命名困難
使用 CSS 進(jìn)行項(xiàng)目快速迭代時,創(chuàng)建一系列語義化類名通常會很煩瑣。當(dāng)我們試圖將一堆信息壓縮到一個精確的標(biāo)簽中時,想出有用的名字就很困難。當(dāng)一切都是全局的時候,正確地處理這一點(diǎn)變得更加重要。

過早命名事物是過早抽象的一種形式。因?yàn)橥ǔN覀兠臇|西仍然需要完全形成,而且還不能重復(fù)使用。

在前端,更改設(shè)計(jì)是很常見的,這些標(biāo)簽經(jīng)常會過時,需要對樣式和 HTML 進(jìn)行重構(gòu)。

重構(gòu)CSS困難
設(shè)計(jì)和現(xiàn)代軟件開發(fā)都是高度迭代的。這要求我們定期重新評估對正在解決的問題的理解。在代碼中,這意味著隨著我們的理解發(fā)生變化和鞏固而進(jìn)行重構(gòu)。

重構(gòu)可能具有挑戰(zhàn)性,但它是一種久經(jīng)考驗(yàn)的真實(shí)方法,可以根據(jù)實(shí)際需求而不是理論來獲得更好的抽象。

在 CSS 中,重構(gòu)相當(dāng)困難。如果沒有可靠的視覺回歸測試,許多 CSS 錯誤都是“無聲的”,很容易產(chǎn)生無法預(yù)料的錯誤和副作用。這導(dǎo)致了幾個常見的場景:

1. 僅附加樣式表
項(xiàng)目開始時樣式表都是易于管理的。但是,新代碼在經(jīng)過幾次迭代和錯誤修復(fù)后,通常會停留在文件的末尾。我們很難知道何時可以安全地更改或刪除規(guī)則,所以在文件末尾的級聯(lián)中覆蓋了之前的內(nèi)容。

這就是特異性之爭的原因,我們可能都有過需要覆蓋其他樣式的經(jīng)歷。!important 的出現(xiàn)加重了維護(hù)負(fù)擔(dān)。

2. 死代碼
在實(shí)踐中,我們經(jīng)常重復(fù)使用相同的 CSS 屬性。不斷復(fù)制規(guī)則通常比承擔(dān)在全局命名空間中重構(gòu)大量 CSS 的風(fēng)險要安全得多。

這通常會導(dǎo)致大量未使用的 CSS,很難知道是否有東西依賴它。這最終會導(dǎo)致 CSS 散布在各種文件中。

調(diào)試CSS困難
調(diào)試的很大一部分就是在腦海中模擬計(jì)算機(jī)所做的事。調(diào)試復(fù)雜的 CSS 是很困難的,因?yàn)樵诳紤]代碼順序的同時要在心里計(jì)算級聯(lián)和計(jì)算的最終規(guī)則。

特別是 CSS 在定位、對齊、層疊上下文、邊距和高度方面的許多細(xì)微差別。如果沒有系統(tǒng)的方法,常見的 CSS 調(diào)試工作流程通常會涉及調(diào)整一些值以查看會發(fā)生什么。

當(dāng)處理無法控制的代碼或特定于瀏覽器的錯誤時,這尤其具有挑戰(zhàn)性。

用CSS架構(gòu)控制復(fù)雜性
CSS 有一個簡單的模型,但事情很容易很快變得混亂。我們最終開始尋求應(yīng)用軟件工程原理來幫助我們進(jìn)行管理。這些架構(gòu)更像是組織 CSS 文件及其規(guī)則和選擇器的高級藍(lán)圖。下面來快速了解一些有影響力和流行的 CSS 架構(gòu)及其主要思想。

OOCSS:面向?qū)ο蟮?CSS
OOCSS 區(qū)分了我們在實(shí)踐中編寫的不同類型的 CSS。進(jìn)行頁面布局的 CSS,以及為 HTML 設(shè)置主題或“皮膚”的 CSS,如顏色、字體等。

OOCSS 中的“對象”是一種可以抽象和重用的重復(fù)視覺模式,其思想就是識別常見的視覺模式,并將重復(fù)的代碼塊提取到可重用類中。

使用最廣泛的 CSS 框架之一 Bootstrap 就使用了 OOCSS。

SMACSS:可擴(kuò)展和模塊化的 CSS
在實(shí)踐中,大型單文件 CSS 文件會很快變得難以管理且難以調(diào)試。SMCASS 是對不同類型的 CSS 進(jìn)行分類的指南,并且與 OOCSS 等方法兼容。其主要想法是獲取所有類名,并將它們組織到單獨(dú)的桶中,并為 CSS 文件提供一些結(jié)構(gòu)。除了一些關(guān)于命名類的約定之外。

BEM:塊、元素、修飾符
BEM 是一種模型,用于考慮如何將事物分解為組件、子元素以及各種離散狀態(tài)。

BEM 最初創(chuàng)建于 Yandex,它提供了一個系統(tǒng)的命名約定,通過保持所有選擇器扁平化(沒有后代選擇器)來避免特異性之爭,其中每個被設(shè)置樣式的元素都有自己的類名。

BEM 與 Sass 等流行的 CSS 預(yù)處理器很好地融合在一起,嵌套規(guī)則可以編譯成扁平化的 CSS 選擇器:

.nav {
  // 塊樣式
 &__link {
    // 依賴父塊的元素樣式
  &--active {
        // 修飾符樣式
  }
 }
}
ITCSS:倒三角形
ITCSS 背后的主要思想之一是通過分層的角度來考慮樣式表,以幫助控制級聯(lián)。

ITCSS 類似于 CSS 的“元框架”,與其他方法兼容。它通過提供越來越具體的明確層次來控制一切不可預(yù)測地相互壓倒的混亂。

“倒三角形”來自每個漸進(jìn)層形成的倒金字塔形狀。這是管理大型項(xiàng)目 CSS 文件的一種有影響力的方法。

Cube CSS
Cube CSS 與全局命名空間以及級聯(lián)一起工作,它提供了一組定義明確的桶,用于對 CSS 進(jìn)行分類。這些就構(gòu)成了 Cube 的首字母縮寫詞:Composition、Utility、Block、Exception。這是一種松散的方法論,就像組織 CSS 的心智模型。






與 ITCSS 類似,它是一個有影響力的“元 CSS 框架”,兼容各種方法。

重新思考關(guān)注點(diǎn)分離
隨著 SPA 和組件驅(qū)動開發(fā)的興起,我們開始看到 CSS 的新方法。

在這個世界上,管理 CSS 變得更加困難,因?yàn)榻M件現(xiàn)在是異步加載的,無法保證源順序。一個常見的問題是,當(dāng)執(zhí)行從頁面 A 到 B 的 SPA 轉(zhuǎn)換時,頁面上的某些元素看起來有所不同,但如果直接加載到 B 則看起來很好。導(dǎo)致一些有趣的調(diào)試會話。

我們開始尋找更具體的解決方案來管理 CSS,這些解決方案與這種以組件為中心的新方法結(jié)合在一起來構(gòu)建我們的前端。

這些工具通常會打破我們迄今為止一直在建立和思考的許多既定的最佳實(shí)踐。下面來了解一下。

行內(nèi)樣式
基于組件的框架中通常會在組件內(nèi)部應(yīng)用內(nèi)聯(lián)樣式。在像 React 這樣的框架中,可以將一個 JavaScript 對象傳遞給 style 屬性,就會將其轉(zhuǎn)換為內(nèi)聯(lián)樣式。

這會引起許多人的本能反應(yīng),因?yàn)檫@就像我們要回到?jīng)]有外部樣式表的起點(diǎn),拋棄了現(xiàn)有的最佳實(shí)踐。

在組件的上下文中,內(nèi)聯(lián)樣式不會面臨最初的大量重復(fù)問題,因?yàn)樗环庋b在組件內(nèi)部。樣式只影響它們所在的元素,這是在組件中安全地添加和修改 CSS 的好方法。

內(nèi)聯(lián)樣式的主要問題就是無法使用更強(qiáng)大的 CSS 功能,例如偽類選擇器和媒體查詢。

CSS in JS
在 React 早期,Veujx 就 Facebook 的 CSS 方法發(fā)表了演講。從表面上看,這看起來很像內(nèi)聯(lián)樣式,但可以訪問樣式表的強(qiáng)大功能。這次演講使得采用 JavaScript 驅(qū)動的 CSS 方法的開源庫激增。

第一波 CSS in JS 庫因?yàn)?Styled Components、Emotion 等庫而流行。它們解決了原生 CSS 在使用組件的大型項(xiàng)目中遇到的很多問題,使得處理 JavaScript 中的動態(tài)值變得非常容易。

CSS in JS 存在服務(wù)器端渲染效率低下、緩存問題和客戶端運(yùn)行時成本問題。這加劇了緩慢的應(yīng)用啟動時間,一旦 JavaScript 水合就需要多次重新渲染。

最近的第二波 CSS in JS 庫旨在在不增加運(yùn)行時成本的情況下為開發(fā)人員提供最佳體驗(yàn)。Linaria、Compiled、Vanilla extract 等工具可以在編譯中從組件中提取樣式表。這將在用戶瀏覽器運(yùn)行時發(fā)生的大部分事情轉(zhuǎn)移到了編譯時。

CSS 通常被編譯成 Atomic CSS 以避免 CSS 文件臃腫,并且與動態(tài)運(yùn)行時樣式表相比更容易緩存。

CSS Modules
CSS Modules 可以在編寫常規(guī) CSS(或 Sass)和滿足正在尋找的許多擴(kuò)展屬性之間取得平衡。

CSS Modules 允許我們使用 CSS 的全部功能,而不必?fù)?dān)心樣式在組件中溢出,同時將內(nèi)容保持在組件目錄中。

特別是在第一波 CSS in JS 庫的浪潮中,將 CSS 綁定到特定的庫對一些人來說太過分了,CSS Modules 就是一個很好的選擇。然而,有些人可能認(rèn)為這是 CSS in JS 的一種形式,因?yàn)樗蕾囉谙?Webpack 這樣的打包工具來生成并確保選擇器的作用域。

無論如何,CSS Modules 是常規(guī) CSS 世界和完全以組件為中心的方法(如 CSS in JS)之間的一個很好的中間方法。不過,仍然需要提供名稱并與 BEM 等約定兼容。

具有挑戰(zhàn)性的CSS最佳實(shí)踐
同時,在基于組件的 SPA 世界之外,最初的 CSS Zen Garden 影響最佳實(shí)踐在另一個方面受到挑戰(zhàn)。

Atomic CSS 誕生于在大型項(xiàng)目上管理 CSS 的黑暗中。它最初的動機(jī)就是啟用樣式而無需編輯或?qū)⒁?guī)則附加到現(xiàn)有得樣式表。避免隨之而來的所有問題。

與 OOCSS、BEM 和 SMACSS 等其他 CSS 架構(gòu)相比,Atomic CSS 完全違反直覺,它比“塊”和“對象”低一級,專注于單一用途的原子。直接違背既定的最佳實(shí)踐,甚至在 HTML 規(guī)范中概述了如何不命名 CSS 類。

對于感覺修改現(xiàn)有 CSS 風(fēng)險太大的項(xiàng)目團(tuán)隊(duì),它已成為一種流行的提高生產(chǎn)力的方法。一些使用 Atomic CSS 的流行 CSS 庫包括 ACSS、Tachyons、WindiCSS 等。

根據(jù) state of CSS,這種 CSS 架構(gòu)最流行的實(shí)現(xiàn)之一就是 Tailwind CSS 框架。

Tailwind 的崛起
自 2017 年發(fā)布以來,Tailwind 迅速受到歡迎。Tailwind的一個典型證明是,它通過使CSS更容易被非專家使用而提高了生產(chǎn)力,同時也帶來了更易于維護(hù)的 CSS。

Tailwind 的基本原則
為了了解它為什么如此受歡迎,下面來研究一下 Tailwind 方法背后的基本原則。盡管看似拋棄了既定的最佳實(shí)踐,但下面將介紹一系列其在實(shí)踐中行之有效的實(shí)用原則。

減少命名
不必不斷地為事物命名是 Tailwind 感覺如此高效的原因之一。這一工作流程是由自下而上組成單一用途原子的想法支持的。從可維護(hù)性的角度來看,這是避免倉促抽象的好方法。

從不命名任何東西會影響代碼的可讀性。通常會導(dǎo)致一大堆沒有明確界限的原子類(或組件)。不過,在經(jīng)常更改或與許多人一起更改的代碼庫中,這通常是正確的權(quán)衡。

適時抽象
Tailwind 提供了兩種在適當(dāng)?shù)臅r候進(jìn)行抽象的技術(shù),一種是創(chuàng)建一個共享的 CSS 類來表示一個塊(類似于 OOCSS)。或者在使用基于組件的框架時,更鼓勵的做法是將重復(fù)的類提取到可重用(React、Vue、Solid、Svelte 等)組件中并共享它。

有信心重構(gòu)
因?yàn)轭愂歉鶕?jù)它們所在的 HTML 進(jìn)行本地化的,所以可以放心地重構(gòu)這些類,而不必?fù)?dān)心影響其他元素或其他地方的組件。

這適用于作為文檔的 Web 心智模型和以組件為中心的模型。這導(dǎo)致了一種感覺,即 Tailwind 可以根據(jù)正在構(gòu)建的站點(diǎn)或應(yīng)用的類型進(jìn)行擴(kuò)展。

避免死代碼
預(yù)編譯為 Atomic CSS 的 Tailwind 和 CSS in JS 解決了充滿重復(fù)規(guī)則的臃腫 CSS 文件的問題。

使用 Atomic CSS,CSS 的增長與使用的樣式數(shù)量相關(guān),而不是開發(fā)人員提供的功能數(shù)量。例如,到處重用某些屬性(如 flex)是很常見的。與其在不同類名下的樣式表中復(fù)制這些內(nèi)容,不如只寫一次。對于每個屬性/值的組合都是如此。

縮小設(shè)計(jì)差距
需要記住,CSS 最終是關(guān)于實(shí)現(xiàn)視覺設(shè)計(jì)的。許多開發(fā)人員覺得 CSS 很難的一個重要原因是設(shè)計(jì)很難。掌握正確的基礎(chǔ)知識會大有幫助。在視覺設(shè)計(jì)中,一些關(guān)鍵元素是對齊、間距、一致性、大小、排版和顏色。

在 CSS 中,對于任何給定的屬性,如 font-size、color 或者 padding,都有多種方法來實(shí)現(xiàn)值。通常會使用某一種方式來實(shí)現(xiàn),這導(dǎo)致字體大小、間距和顏色不一致的細(xì)微差別激增,最終形成了一種粗糙的外觀和感覺。

擴(kuò)展 CSS 的一個關(guān)鍵部分就是通過具有可共享原語的堅(jiān)實(shí)基礎(chǔ)來彌合這一設(shè)計(jì)差距,這些原語定義了間距、字體大小、顏色值。這些通常被稱為 Design Tokens,并構(gòu)成設(shè)計(jì)系統(tǒng)的基礎(chǔ)。沒有這些基礎(chǔ),事情會變得非常隨意和混亂。

Tailwind 受歡迎的一個關(guān)鍵方面是提供了一組可以開箱即用的預(yù)先考慮好的基礎(chǔ)設(shè)計(jì)原語。這消除了大量的決策,這些決策通常是臨時完成的,且不一致。

結(jié)語
沒有工具是完美的,每個項(xiàng)目和團(tuán)隊(duì)都是不同的。無論采用何種方法,建立縮小設(shè)計(jì)差距的基礎(chǔ)都是擴(kuò)展 CSS 的關(guān)鍵要素。

專注于在其之上組合和構(gòu)建的原語也有很長的路要走。這也適用于使用組件庫的基于組件的大型應(yīng)用。提供可組合的組件布局原語,如 Box、Stack、Inline 等是管理 CSS 的好方法,開發(fā)人員無需編寫任何 CSS。

最近,Evergeen 瀏覽器推出了大量功能,解決了許多使 CSS 難以擴(kuò)展的痛點(diǎn)。級聯(lián)層、容器查詢、子網(wǎng)格、has 等新功能可能會改變我們在未來思考和利用 CSS 的方式。

擴(kuò)展CSS的成功與其說是對特定原則或最佳實(shí)踐的教條式堅(jiān)持,不如說是基于現(xiàn)實(shí)世界的約束來定義你需要什么,并以可持續(xù)和高效的方式來完成工作。

原文:https://frontendmastery.com/posts/the-evolution-of-scalable-css/

作者:CUGGZ


歡迎關(guān)注微信公眾號 :前端充電寶