一文詳解 CSS-in-JS
幾年前,如果有人提到用 JavaScript 編寫 HTML 作為構建大型網(wǎng)站的一種方式,很多開發(fā)者會當這作不可理喻的想法,但是現(xiàn)在,使用 React、Vue 和 Angular 框架為組件開發(fā)的應用正在慢慢替代傳統(tǒng)的 Web 開發(fā)。
現(xiàn)在 CSS-in-JS 確實也有點像當年的味道,雖然并不是唯一的解決方案,卻提供了一個很大膽的想法和嘗試。
對現(xiàn)代化的 Web 開發(fā)項目說,CSS 也是如此,CSS 做為 Web 的樣式表來呈現(xiàn)豐富多彩的 Web 應用已經(jīng)不再是唯一的選擇了,我們或許應該多考慮其他的擴展性和移植性嘗試未來的 CSS-in-JS。
一 CSS 的介紹
CSS(層疊樣式表)是一種用來為結(jié)構化文檔添加樣式的計算機語言,由 W3C 定義和維護。目前最新版本是 CSS2.1,為 W3C 的推薦標準。CSS3 現(xiàn)在已被大部分現(xiàn)代瀏覽器支持,而下一版的 CSS4 仍在開發(fā)中。
1 模塊和標準化進程
CSS Level 2 經(jīng)歷了 9 年的時間(從 2002 年 8 月到 2011 年 6 月)才達到 Recommendation(推薦) 狀態(tài),主要原因是被一些次要特性拖了后腿。為了加快那些已經(jīng)確認沒有問題的特性的標準化速度,W3C 的 CSS Working Group(CSS 工作組) 作出了一項被稱為 Beijing doctrine 的決定,將 CSS 劃分為許多小組件,稱之為_模塊_。這些模塊彼此獨立,按照各自的進度來進行標準化。其中一些已經(jīng)是 W3C Recommendation 狀態(tài),也有一些仍是 early Working Drafts(早期工作草案)。當新的需求被肯定后, 新的模塊也會同樣地添加進來。
第一個 CSS 于1996年推出,下面是 CSS 版本的時間表:
2 CSS 模塊狀態(tài)
從形式上來說,CSS3 標準自身已經(jīng)不存在了。每個模塊都被獨立的標準化,現(xiàn)在標準 CSS 包括了修訂后的 CSS2.1 以及完整模塊對它的擴充,模塊的 level(級別)數(shù)并不一致。可以在每個時間點上為 CSS 標準定義一個 snapshots(快照),列出 CSS 2.1 和成熟的模塊。
W3C 會定期的發(fā)布這些 snapshots,如 2007, 2010, 2015 或 2017。
目前為止,還沒有 level 超過 3 的模塊被標準化,未來應該會有所改變。不過有些模塊,比如 Selectors(選擇器)4 或 CSS Borders and Backgrounds(邊框和背景)Level 4 早已擁有了 Editor's Draft(編輯草案),即使它們還沒達到 First Published Working Draft(初次發(fā)布工作草案)狀態(tài)。
3 五種 CSS 設計模式
現(xiàn)代化的前端開發(fā)在歷史上發(fā)展了許多的 CSS 設計模式,主要發(fā)展出以下幾種:
- OOCSS(Object Oriented CSS)
- SMACSS(Scalable and Modular Architecture for CSS)
- BEM(Block - Element - Modifier)
- ITCSS(Inverted Triangle Cascading Style Sheets)
- Atomic CSS
其設計的原因基本是基于這幾個問題來做優(yōu)化的:
- 減少選擇器命名和樣式的沖突
- 清晰的 CSS 整體結(jié)構
- 去除冗余代碼,減少樣式的體積
- 可重復利用,組件化的 CSS
- 提高 CSS 代碼的可讀性
4 Atomic CSS 的歷史
- 2013/06/10:Brad Frost 發(fā)布了 Atomic Design 文章,在社區(qū)上有一些文章開始討論 Atomic CSS
- 2015/01/08:《atomic design: the book》 一書發(fā)布
- 2014/10/02:atomizer 項目創(chuàng)建
- 2017/10/06:tailwindcss 項目創(chuàng)建
Tailwind CSS 和其他預編譯器相比還是比較的冷門,如下圖:
在 React 和 Vue 日益吞噬的 Web 開發(fā)界中,組件化的思想和工程化日漸成熟,Atomic CSS 也算是比較早推出的一個設計思想,筆者覺得 Atomic CSS 能做的事情,在 CSS-in-JS 反而能做的更好,因為 JS 框架和工具的盛行和豐富,Atomic(原子化)也是未來 CSS-in-JS 一個可以涉足的區(qū)域。
5 CSS 數(shù)學表達式
根據(jù) CSSWG 的 draft,CSS 目前支持計算的數(shù)學表達式主要包含五大類:
- 基本算數(shù):calc()
- 比較函數(shù):min(), max(), clamp()
- 步進函數(shù):round(), mod(), rem()
- 三角函數(shù):sin(), cos(), tan(), asin(), acos(), atan(), atan2()
- 指數(shù)函數(shù):pow(), sqrt(), hypot(), log(), exp()
日常使用中 calc() 算是最常用的,一般用來計算長寬、響應式布局等等,而比較函數(shù)在一些場景也可能會用的上,剩下的其他函數(shù)很大部分都沒有機會在項目中使用的上。
6 CSS Houdini
Houdini是一組底層API,它們公開了CSS引擎的各個部分,從而使開發(fā)人員能夠通過加入瀏覽器渲染引擎的樣式和布局過程來擴展CSS。Houdini是一組API,它們使開發(fā)人員可以直接訪問CSS 對象模型 (CSSOM),使開發(fā)人員可以編寫瀏覽器可以解析為CSS的代碼,從而創(chuàng)建新的CSS功能,而無需等待它們在瀏覽器中本地實現(xiàn)。
—— 《MDN / CSS Houdini》
如果說 CSS-in-JS 是用現(xiàn)有的標準用 JS 去控制、擴展和實時聯(lián)動 CSS 的一套方案,那么 CSS Houdini 就相當于進階版本的 CSS-in-JS,通過公開 CSS 引擎的各個功能,是開發(fā)人員能更好的擴展 CSS,筆者認為是不是也可以理解為 CSS Houdini 的出現(xiàn)也代表了現(xiàn)在的純 CSS 已經(jīng)很難滿足現(xiàn)在日益豐富的 Web 應用。
CSS Houdini
CSS Parser API
這是直接地暴露出 CSS 解析器的 API接口,能夠把任意 CSS 類語言解析成為一種中間類型,定義新的結(jié)構。
CSS Properties and Values API
- 定義一個用來注冊新的 CSS 屬性的 API。通過該 API 注冊的屬性必須用一種特定的解析語法書寫,以定義其類型、繼承行為以及初始值。
- CSS Properties and Values API reference
- CSS Properties and Values API guide
CSS Typed OM
- 可以把 CSS Typed OM 視為 CSSOM 2.0,它的目的在于解決目前模型的一些問題,并實現(xiàn) CSS Parsing API 和 CSS 屬性與值 API 相關的特性。
- CSS Typed OM reference
- CSS Typed OM guide
CSS Layout API
被設計來提升 CSS 擴展性的 API,該 API 能夠讓開發(fā)者去書寫他們自己的布局算法,比如 masonry 或者 line snapping。
CSS Painting API
- 被設計來提升 CSS 擴展性的 API,該 API 允許開發(fā)者通過 paint() 方法來寫 JavaScript 函數(shù),以控制繪制頁面元素的樣式或內(nèi)容區(qū)域。
- CSS Painting API reference
- CSS Painting API guide
Worklets
- 該 API 允許腳本獨立于 JavaScript 執(zhí)行環(huán)境,運行在渲染流程的各個階段。
- Worklets 在很接近于 JS 的 Web Workers ,由渲染引擎擴展并調(diào)用。
- Worklets reference
7 CSS 預處理器 (CSS Preprocessor)
CSS 預處理器是一個能讓你通過預處理器自己獨有的語法來生成 CSS 的程序。市面上有很多 CSS 預處理器可供選擇,且絕大多數(shù) CSS 預處理器會增加一些原生 CSS 不具備的特性,例如代碼混合,嵌套選擇器,繼承選擇器等。這些特性讓 CSS 的結(jié)構更加具有可讀性且易于維護。
—— 《MDN / CSS 預處理器》
一些最流行的 CSS 預處理器:
- PostCSS:2013/11/04
- Less:2009
- SASS:2006/11/28
- Stylus:2010/12/29
圖中看到 PostCSS 的下載量一直遙遙領先其他 CSS 預處理器,PostCSS 比較大的優(yōu)勢在于社區(qū)有很多插件可以使用,相當于 CSS 屆的 Babel,常見 PostCSS 插件如下:
- Autopre?xer:自動補全瀏覽器私有前綴
- precss:CSS 預處理(整合 Sass、LESS 或 Stylus 功能,語法基本和 Sass 的相同)
- postcss-import:通過 @import,整合多個 CSS 文件
- css-mqpacker:將相同的 CSS 媒體查詢規(guī)則合并為一個
- cssnano:壓縮 CSS 文件
- postcss-color-rgba-fallback:給 rgba 顏色創(chuàng)建降級方案(添加備用顏色)
- postcss-opacity:給 opacity 提供降級方案(給 IE 瀏覽器添加濾鏡屬性)
- node-pixrem:讓 IE8 ?持 rem 單位
- postcss-pseudoelements:將偽元素的 :: 轉(zhuǎn)換為 : ( IE8 不不?支持 ::)
如果一定需要使用 CSS 預處理器,可能 PostCSS 是最好的選擇之一,當然,也是需要看實際你項目的整體方案來選擇。
8 CSS-in-JS VS CSS Preprocessor
在 Google Trends 中我們可以看到 2014 年后 CSS-in-JS 的趨勢就逐漸超越了 CSS 預處理器,這在一方面也說明了開發(fā)人員在 CSS-in-JS 上有著很大興趣。
二 CSS-in-JS 的介紹
CSS-in-JS是一種樣式化技術,其中 JavaScript 用于樣式化組件。解析此 JavaScript 時,將生成 CSS(通常作為<style>元素)并將其附加到 DOM 中。它允許使用JavaScript以聲明性和可維護的方式描述樣式,從而將 CSS 抽象到組件級別本身。
1 CSS-in-JS 起源歷史
- 2000年11月13日:W3C 草案中 Document Object Model (DOM) Level 2 Specification 提出了 CSS Object Model (CSSOM),允許 CSS 通過 JavaScript 操縱的。它非常類似于 DOM,但是用于 CSS 而不是 HTML。它允許用戶動態(tài)讀取和修改 CSS 樣式。
- 2014年11月15日:CSS-in-JS 由 Facebook 的員工 Vjeux 在 NationJS 會議上提出:可以借用 JS 解決許多 CSS 本身的一些“缺陷”,比如全局作用域、死代碼移除、生效順序依賴于樣式加載順序、常量共享等等問題。
- 2014 ~ 現(xiàn)在:大量的 CSS-in-JS 的解決方案的提出,在領域上不斷除舊推新,在工程化和框架的解決方案中不斷探索實現(xiàn)。
CSS-in-JS 的一大特點是它的方案眾多,這種看似混亂的狀態(tài)很符合前端社區(qū)喜歡重復造輪子的特征。發(fā)展初期,社區(qū)在各個方向上探索著用 JS 開發(fā)和維護 CSS 的可能性。每隔一段時間,都會有新的語法方案或?qū)崿F(xiàn),嘗試補充、增強或是修復已有實現(xiàn)。
2 沒有 CSS 的那些平臺和框架
- QT:QStyle Class & Draw Method
- Flutter:Style Object
- ReactNative:ReactNative.StyleSheet
- Unreal Engine:Style Object
- Canvas:Draw Method
- Skia:Draw Method
都是基于各自的設計 imperative & declarative(命令式和聲明式)的樣式編寫,能與程序設計中的各個狀態(tài)綁定,并不局限于樣式表修改這一概念。
3 區(qū)別是什么
如果說純 CSS 框架工具和 CSS-in-JS 的區(qū)別是什么,筆者覺得最大的區(qū)別就是編譯運行的不同時機,我們可以理解成:CSS 框架工具只等于 AOT(Ahead-of-time),CSS-in-JS 則擁有 JIT(Just-in-time) 的能力,例如上面提到的 CSS Houdini API 本質(zhì)其實也是相當于擴展 CSS 框架的實時運行的能力,而 JIT 的框架和工具本質(zhì)上也可以使用 AOT 的工具來優(yōu)化,例如 Babel 和 Webpack。
4 使用 CSS-in-JS 的優(yōu)點
- 組件化思考模式,不再需要維護一堆樣式表。CSS-in-JS 將 CSS 模型抽象到組件級別,而不是文檔級別(模塊化)。
- CSS-in-JS 利用 JavaScript 環(huán)境的全部功能來增強CSS。
- 真正的選擇器隔離。范圍選擇器是不夠的。CSS具有從父元素自動繼承的屬性(如果未明確定義)。
- CSS 要避免選擇器沖突,例如 BEM 之類的命名約定可能在一個項目中有所幫助,但在集成第三方代碼時則會存在很多問題。當 JSS 將 JSON 表示形式編譯為 CSS 時,默認情況下會生成唯一的類名。
- 動態(tài)瀏覽器私有化前綴,使用 CSS-in-JS 可以避免臃腫的 CSS 代碼。
- 代碼共享,輕松在 JS 和 CSS 之間共享常量和函數(shù)。
- CSS-in-JS 的單元化測試。
- TypeScript 的支持。
- 減少項目編譯的依賴,純 JS 或 TS 項目。
- 動態(tài)變化的主題和變量。
5 使用 CSS-in-JS 的缺點
- 學習曲線,需要學習使用
- 新的依賴
6 那些流行的 CSS-in-JS 庫
Run-Time(JIT)
運行時動態(tài)修改樣式的庫:
- emotion
- jss
- styled-components
- aphrodite
- radium
- glamor
如下圖統(tǒng)計,emotion、jss 和 styled-components 都有不錯的開發(fā)者 NPM 下載使用量,保持長期的增長趨勢,這對開發(fā)者來說是比較不錯的,意味著這些庫也有穩(wěn)定的發(fā)展和維護。
逐年遞增的下載數(shù)量反映了開發(fā)社區(qū)和使用范圍的擴大,也表明了開發(fā)者在 CSS-in-JS 上的積極貢獻和參與。
這些庫大部分的動態(tài)修改樣式主要使用這幾種方式:
1)CSS 樣式表
- Scoped CSS:通過每個組件添加 CSS 樣式表,但是添加了 scoped 的作用域
- Global CSS:在 HTML 全局添加修改樣式表的 Content 來修改樣式
2)CSSOM 修改
- 通過修改全局的 CSSOM 的 CSSRule 來達到修改樣式的目的
這幾種方式,筆者比較推薦 CSSOM 修改的方式,頁面的 HTML 結(jié)構和內(nèi)容不會變化,也不會有過多的單組件 CSS,而且在修改樣式方式上也有很多可以優(yōu)化和擴展的余地,期望后續(xù)的開發(fā)者能有優(yōu)秀的實踐可推廣。
Build-Time(AOT)
提前編譯成 CSS 樣式表的庫:
- Linaria
提前編譯的優(yōu)勢在于一些小程序和其他框架需要 CSS 樣式表時是唯一的選擇,在用戶低端手機和性能上比動態(tài)修改樣式要更有優(yōu)勢。
7 都有誰在使用?
UI 庫
material-ui 是筆者很早關注的一個 material design 的一個開源 UI 組件庫,用過 ReactJS 的開發(fā)同學可能有了解過,記得一開始官方采用的是內(nèi)聯(lián)樣式,后續(xù)研發(fā)了自己的一套 CSS-in-JS 的實現(xiàn)方案,單獨發(fā)布了 Material-UI 組件中使用的樣式方案 —— @material-ui/styles。
公司
數(shù)千家公司正在使用 CSS-in-JS 進行開發(fā) Web 應用。
- Patreon
- Target
- Atlassian
- Vogue
- GitHub
- Coinbase
8 Chrome Devtools 對 CSS-in-JS 的支持
在 What's New In DevTools (Chrome 85) 中 Google 更新了 CSS-in-JS 框架的樣式編輯的支持。
現(xiàn)在,“Styles”窗格對編輯使用 CSS 對象模型(CSSOM)API 創(chuàng)建的樣式提供了更好的支持。許多 CSS-in-JS 框架和庫都在底層使用 CSSOM API 來構造樣式。
現(xiàn)在也可以使用 “Constructable Stylesheets” 編輯在 JavaScript 中動態(tài)添加的樣式。
可構造樣式表是使用 Shadow DOM 時創(chuàng)建和修改樣式的一種新的方法。
例如,(CSSOM API)h1添加的樣式 CSSStyleSheet以前不可編輯。現(xiàn)在可以在“Styles”窗格中進行編輯:
三 UI & Code 3.0 新時代
1 自動智能化
在現(xiàn)在前端開發(fā)趨勢越來越智能化的時代,如果用上 CSS-in-JS 在未來的無論是輸出還是輸入都有很大的便利性和可控性。
假如把前端和設計的協(xié)同工作分為三個時代:
- v1.0:設計資源和信息需要設計師手動額外切圖說明,無法復制
- v2.0:設計資源和信息由設計文件自動化生成,可人工復制
- v3.0:設計資源和信息由設計源文件和代碼自動讀取,無需人工復制
也就是說,可以通過接口、SDK或插件,可以把設計文件的資源和信息讀取到代碼中,減少人工維護和開發(fā)的成本,建立起是設計和程序的橋,方便雙方的協(xié)同工作。
現(xiàn)如今,很多設計軟件都推出了自己的一套插件或 SDK 以供開發(fā)者使用,如下圖 Sketch 插件的開發(fā):
2 跨平臺
CSS-in-JS 在跨平臺的優(yōu)勢是比較大的,在不同的系統(tǒng)平臺上都有 JS 的 Runtime 的實現(xiàn),而且 JSON 序列化后的數(shù)據(jù)也能被更多的平臺和語言消費,現(xiàn)在光靠純 CSS 是無法達到這種通用性和擴展性。
四 展望未來
CSS 設計的初衷是為了全局化的控制樣式,通過選擇器去擴展豐富實際的頁面渲染,而 CSS-in-JS 并不是排斥 CSS 樣式,而是說“樣式”在現(xiàn)代化的組件顆?;陌l(fā)展下,使用 CSS-in-JS 能在瞬息萬變的復雜應用場景下更加靈活的解決更多問題。
筆者因早前開發(fā)過自己的一套 React UI 庫 React-UWP,也基于這套 UI 庫做了 CSS-in-JS 的方案,在過去兩年中在開發(fā)中雖然用的組件不是很多,但是用了 CSS-in-JS 來做整體的樣式解決方案,在組件擴展、主題自定義和狀態(tài)同步有著很大的優(yōu)勢,也期望在后續(xù)的社區(qū)中有更多優(yōu)秀的實踐可以參考。
如果在文章中發(fā)現(xiàn)有誤之處,歡迎反饋、糾正。
Links
https://www.w3.org/Style/CSS20/history.html
https://levelup.gitconnected.com/a-brief-history-of-css-in-js-how-we-got-here-and-where-were-going-ea6261c19f04
https://github.com/MicheleBertoli/css-in-js
https://zhuanlan.zhihu.com/p/165089496
https://zhuanlan.zhihu.com/p/103522819
https://zhuanlan.zhihu.com/p/59692295
https://zhuanlan.zhihu.com/p/30118092
https://medium.com/dev-channel/the-cost-of-javascript-84009f51e99e
https://juejin.cn/post/6844903808049348616
https://www.infoq.com/news/2020/04/facebook-cssinjs-react-conf-2019/
https://sebastienlorber.com/atomic-css-in-js
https://www.nonenglishengineer.com/css-design-patterns/
https://dev.to/carlillo/understanding-itcss-real-case-using-itcss-in-a-ghostcms-blog-1p9b
https://engineering.fb.com/2020/05/08/web/facebook-redesign/
https://zhuanlan.zhihu.com/p/98831543
https://www.qed42.com/blog/building-powerful-custom-properties-CSS-houdini
https://laptrinhx.com/the-future-of-css-has-come-3034181035/
https://zhuanlan.zhihu.com/p/20939640
https://www.smashingmagazine.com/2016/03/houdini-maybe-the-most-exciting-development-in-css-youve-never-heard-of/
https://developer.mozilla.org/zh-CN/docs/Archive/CSS3
http://www.airbrite.co.uk/css-training-css-specification/
https://aotu.io/notes/2019/10/29/css-preprocessor/index.html
https://zhuanlan.zhihu.com/p/36103933
https://www.w3.org/Style/CSS/current-work.en.html
https://github.com/ladjzero/ladjzero.github.io/blob/master/assets/a_brief_history_of_css.pdf
https://developer.mozilla.org/en-US/docs/Web/Houdini
https://drafts.csswg.org/css-variables-1/
https://drafts.csswg.org/css-values-4/
https://juejin.cn/post/6844904152548507661
https://engineering.fb.com/2020/05/08/web/facebook-redesign/
https://css-tricks.com/growing-popularity-atomic-css/
https://css-tricks.com/lets-define-exactly-atomic-css/
https://www.smashingmagazine.com/2013/08/other-interface-atomic-design-sass/
https://www.smashingmagazine.com/2013/10/challenging-css-best-practices-atomic-approach/
https://bradfrost.com/blog/post/atomic-web-design/
https://bradfrost.com/blog/post/atomic-design-book/