首頁 >web前端 >css教學 >重新思考 JS 中的 CSS

重新思考 JS 中的 CSS

王林
王林原創
2024-09-12 16:18:551222瀏覽

0. 簡介

在 Web 開發領域,CSS 是使用者介面美觀且實用的關鍵元素

然而,隨著 Web 應用程式複雜性的增加,CSS 管理已成為一項越來越具有挑戰性的任務。風格衝突、效能下降、維護困難是許多開發者關心的問題。

這些問題是否阻礙了您的專案進度? (圖片來源)

Rethinking CSS in JS

本文深入探討了解決這些問題的新方法,特別是 JS 中的 CSS
它從 CSS 的歷史背景開始,涵蓋了廣泛的主題從現代樣式方法到未來的設計系統。

文章架構如下:

  1. JS中CSS的定義與背景
    • 1. JS 中的 CSS 是什麼?
    • 2. CSS在JS的背景
  2. CSS 與設計的歷史背景
    • 3. CSS的背景
    • 4.設計背景
    • 5.設計系統背景
  3. 風格管理方法分析及新建議
    • 6.樣式是如何管理的?
    • 7.樣式該如何管理?
  4. CSS在JS中的具體實作方案
    • 8.為什麼在 JS 使用 CSS?
    • 9.介紹一下mincho專案
    • 10。 JS 中 CSS 友善的 CSS
    • 11。 JS 中的可擴展 CSS
  5. 與設計系統整合
    • 12。用於設計系統的 JS 中的 CSS

特別是,本文介紹了稱為 SCALE CSS 方法論和 StyleStack 的新概念,並基於這些概念提出了一個 mincho 項目。它的目標是在 JS 中實現 CSS,對 CSS 友好且可擴展。

本文的最終目的是向開發人員、設計師和其他 Web 專案利害關係人展示更好的樣式解決方案的可能性

現在,讓我們在正文中深入探討 JS 中 CSS 的世界。這將是一段漫長的旅程,但我希望它能為你帶來新的靈感和挑戰的機會

1.JS中的CSS是什麼?

JS 中的 CSS 是一種允許您直接在 JavaScript(或 TypeScript)程式碼中編寫 CSS 樣式的技術。
您可以在 JavaScript 檔案中與元件一起定義樣式,而不是建立單獨的 CSS 檔案。

/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";

const buttonStyles = (primary) => css({
  backgroundColor: primary ? "blue" : "white",
  color: primary ? "white" : "black",
  fontSize: "1em",
  padding: "0.25em 1em",
  border: "2px solid blue",
  borderRadius: "3px",
  cursor: "pointer",
});

function Button({ primary, children }) {
  return (
    <button css={buttonStyles(primary)}>
      {children}
    </button>
  );
}

function App() {
  return (
    <div>
      <Button>Normal Button</Button>
      <Button primary>Primary Button</Button>
    </div>
  );
}

能夠將其整合到 JavaScript 中當然看起來很方便?

2.CSS在JS的背景

CSS in JS 是在 Facebook 開發者 Vjeux 的題為「React: CSS in JS – NationJS」的演講中介紹的。

CSS-in-JS旨在解決的問題如下:
Rethinking CSS in JS

更具體的問題是什麼?
而JS中的CSS又是如何解決的呢?

我將它們整理在下表中:

Problem Solution
Global namespace Need unique class names that are not duplicated as all styles are declared globally Use Local values as default
- Creating unique class names
- Dynamic styling
Implicit Dependencies The difficulty of managing dependencies between CSS and JS
- Side effect: CSS is applied globally, so it still works if another file is already using that CSS
- Difficulty in call automation: It's not easy to statically analyze and automate CSS file calls, so developers have to manage them directly
Using the module system of JS
Dead Code Elimination Difficulty in removing unnecessary CSS during the process of adding, changing, or deleting features Utilize the optimization features of the bundler
Minification Dependencies should be identified and reduced As dependencies are identified, it becomes easier
to reduce them.
Sharing Constants Unable to share JS code and state values Use JS values as they are, or utilize CSS Variables
Non-deterministic Resolution Style priority varies depending on the CSS loading order - Specificity is automatically calculated and applied
- Compose and use the final value
Breaking Isolation Difficulty in managing external modifications to CSS (encapsulation) - Encapsulation based on components
- Styling based on state
- Prevent styles that break encapsulation, such as .selector > *

But it's not a silverbullet, and it has its drawbacks.

  1. CSS-in-JS adds runtime overhead.
  2. CSS-in-JS increases your bundle size.
  3. CSS-in-JS clutters the React DevTools.
  4. Frequently inserting CSS rules forces the browser to do a lot of extra work.
  5. With CSS-in-JS, there's a lot more that can go wrong, especially when using SSR and/or component libraries.

Aside from the DevTools issue, it appears to be mostly a performance issue.
Of course, there are CSS in JS, which overcomes these issues by extracting the CSS and making it zero runtime, but there are some tradeoffs.
Here are two examples.

  1. Co‑location: To support co-location while removing as much runtime as possible, the module graph and AST should be analyzed and build times will increase. Alternatively, there is a method of abandoning co-location and isolating on a file-by-file basis, similar to Vanilla Extract.
  2. Dynamic styling restrictions: The combination of build issues and the use of CSS Variables forces us to support only some representations, like Styling based on props in Pigment CSS, or learn to do things differently, like Coming from Emotion or styled-components. Dynamicity is also one of the main metrics that can be used to distinguish between CSS in JS.

Therefore, pursuing zero(or near-zero) runtime in CSS-in-JS implementation methods creates a significant difference in terms of expressiveness and API.

3. The background of CSS

3.1 The Beginning of CSS

Where did CSS come from?
Early web pages were composed only of HTML, with very limited styling options.

<p><font color="red">This text is red.</font></p>
<p>This is <strong>emphasized</strong> text.</p>
<p>This is <em>italicized</em> text.</p>
<p>This is <u>underlined</u> text.</p>
<p>This is <strike>strikethrough</strike> text.</p>
<p>This is <big>big</big> text, and this is <small>small</small> text.</p>
<p>H<sub>2</sub>O is the chemical formula for water.</p>
<p>2<sup>3</sup> is 8.</p>

For example, the font tag could change color and size, but it couldn't adjust letter spacing, line height, margins, and so on.

You might think, "Why not just extend HTML tags?" However, it's difficult to create tags for all styling options, and when changing designs, you'd have to modify the HTML structure itself.
This deviates from HTML's original purpose as a document markup language and also means that it's hard to style dynamically.

If you want to change an underline to a strikethrough at runtime, you'd have to create a strike element, clone the inner elements, and then replace them.

const strikeElement = document.createElement("strike");
strikeElement.innerHTML = uElement.innerHTML;
uElement.parentNode.replaceChild(strikeElement, uElement);

When separated by style, you only need to change the attributes.

element.style.textDecoration = "line-through";

If you convert to inline style, it would be as follows:

<p style="color: red;">This text is red.</p>
<p>This is <span style="font-weight: bold;">bold</span> text.</p>
<p>This is <span style="font-style: italic;">italic</span> text.</p>
<p>This is <span style="text-decoration: underline;">underlined</span> text.</p>
<p>This is <span style="text-decoration: line-through;">strikethrough</span> text.</p>
<p>This is <span style="font-size: larger;">large</span> text, and this is <span style="font-size: smaller;">small</span> text.</p>
<p>H<span style="vertical-align: sub; font-size: smaller;">2</span>O is the chemical formula for water.</p>
<p>2<span style="vertical-align: super; font-size: smaller;">3</span> is 8.</p>

However, inline style must be written repeatedly every time.
That's why CSS, which styles using selectors and declarations, was introduced.

<p>This is the <strong>important part</strong> of this sentence.</p>
<p>Hello! I want to <strong>emphasize this in red</strong></p>
<p>In a new sentence, there is still an <strong>important part</strong>.</p>

<style>
strong { color: red; text-decoration: underline; }
</style>

Since CSS is a method that applies multiple styles collectively, rules are needed to determine which style should take precedence when the target and style of CSS Rulesets overlap.

CSS was created with a feature called Cascade to address this issue. Cascade is a method of layering styles, starting with the simple ones and moving on to the more specific ones later. The idea was that it would be good to create a system where basic styles are first applied to the whole, and then increasingly specific styles are applied, in order to reduce repetitive work.

Therefore, CSS was designed to apply priorities differently according to the inherent specificity of CSS Rules, rather than the order in which they were written.

/* The following four codes produce the same result even if their order is changed. */
#id { color: red; }
.class { color: green; }
h1 { color: blue; }
[href] { color: yellow; }

/* Even if the order is changed, the result is the same as the above code. */
h1 { color: blue; }
#id { color: red; }
[href] { color: yellow; }
.class { color: green; }

However, as CSS became more scalable, a problem arose..

3.2 Scalable CSS

Despite the advancements in CSS, issues related to scalability in CSS are still being discussed.
In addition to the issues raised by CSS in JS, several other obstacles exist in CSS.

  1. Code duplication: When writing media queries, pseudo-classes, and pseudo-elements, a lot of duplication occurs if logic is required.
  2. Specificity wars: As a workaround for name collisions and non-deterministic ordering, specificity keeps raising the specificity to override the style. You can have fun reading Specificity Battle!
  3. Lack of type-safety: CSS does not work type-safely with TypeScript or Flow.

These issues can be addressed as follows:

  1. Penduaan kod: Gunakan bersarang dalam prapemproses CSS, dsb.
  2. Peperangan kekhususan: CSS atom ditakrifkan untuk setiap sifat secara berasingan, jadi ia mempunyai kekhususan yang sama kecuali untuk susunan pemuatan dan !penting.
  3. Kekurangan jenis-keselamatan: Hanya gunakan CSS dalam JS dengan sokongan jenis-selamat.

Menyatakan reka letak ialah satu lagi halangan dalam CSS, yang dibuat lebih kompleks dengan interaksi antara pelbagai sifat.
Rethinking CSS in JS

CSS mungkin kelihatan mudah di permukaan, ia tidak mudah untuk dikuasai. Umum mengetahui bahawa ramai orang bergelut walaupun dengan penjajaran tengah yang mudah(1, 2). Kesederhanaan CSS yang jelas boleh mengelirukan, kerana kedalaman dan nuansanya menjadikannya lebih mencabar daripada yang kelihatan pada mulanya.

Sebagai contoh, paparan dalam CSS mempunyai model reka letak yang berbeza: blok, sebaris, jadual, lentur dan grid.
Bayangkan kerumitan apabila sifat berikut digunakan dalam kombinasi: model kotak, Reka bentuk responsif, Terapung, Kedudukan, transformasi, mod penulisan, topeng, dll.

Apabila skala projek meningkat, ia menjadi lebih mencabar kerana kesan sampingan yang berkaitan dengan Penempatan DOM, lata dan kekhususan.

Isu reka letak harus ditangani melalui rangka kerja CSS yang direka dengan baik, dan seperti yang dinyatakan sebelum ini, menggunakan CSS dalam JS untuk mengasingkan gaya boleh mengurangkan kesan sampingan.

Bagaimanapun, pendekatan ini tidak menyelesaikan semua masalah sepenuhnya. Pengasingan gaya boleh membawa kepada kesan sampingan baharu, seperti saiz fail meningkat disebabkan oleh pengisytiharan gaya pendua dalam setiap komponen atau kesukaran dalam mengekalkan konsistensi gaya biasa merentas aplikasi.
Ini secara langsung bercanggah dengan reka bentuk isu letupan dan konsistensi gabungan yang akan diperkenalkan seterusnya.

Buat masa ini, kami boleh mewakilkan kebimbangan reka letak kepada rangka kerja seperti Bootstrap atau Bulma dan memfokuskan lebih pada aspek pengurusan.

4. Latar belakang Reka Bentuk

Pada terasnya, CSS ialah alat yang berkuasa untuk menyatakan dan melaksanakan reka bentuk dalam pembangunan web.

Terdapat banyak faktor yang perlu dipertimbangkan semasa mencipta UI/UX dan elemen berikut adalah penting untuk diwakili dalam reka bentuk anda:
Rethinking CSS in JS

  1. Reka Bentuk Visual
    • Reka Letak: Menentukan struktur skrin dan peletakan elemen. Pertimbangkan jarak, penjajaran dan hierarki antara elemen.
    • Warna: Pilih palet warna yang mempertimbangkan identiti jenama dan pengalaman pengguna. Pemahaman tentang teori warna adalah perlu.
    • Tipografi: Pilih fon dan gaya teks yang sepadan dengan kebolehbacaan dan imej jenama.
    • Ikon dan Elemen Grafik: Reka bentuk ikon dan grafik yang intuitif dan konsisten.
  2. Reka Bentuk Interaksi
    • Reka bentuk gelagat elemen UI seperti butang, peluncur dan bar skrol.
    • Berikan pengalaman pengguna semula jadi melalui animasi dan kesan peralihan.
    • Pertimbangkan reka bentuk responsif yang menyesuaikan diri dengan pelbagai saiz skrin.
  3. Seni Bina Maklumat
    • Reka bentuk struktur dan hierarki kandungan untuk membolehkan pengguna mencari dan memahami maklumat dengan mudah.
    • Reka bentuk sistem navigasi untuk membolehkan pengguna bergerak ke lokasi yang dikehendaki dengan mudah.
  4. Kebolehaksesan dan Kebolehgunaan
    • Kejar reka bentuk inklusif yang mempertimbangkan pengguna yang pelbagai. i18n juga boleh disertakan.
    • Buat antara muka yang intuitif dan mudah digunakan untuk mengurangkan beban kognitif pengguna.
  5. Panduan Ketekalan dan Gaya
    • Buat panduan gaya untuk mengekalkan bahasa reka bentuk yang konsisten sepanjang aplikasi.
    • Membangunkan komponen dan corak yang boleh diguna semula untuk meningkatkan kecekapan.

Menyatakan pelbagai elemen reka bentuk dengan tepat merentas pelbagai keadaan memberikan cabaran yang ketara.
Pertimbangkan bahawa anda perlu mengambil kira peranti (telefon, tablet, komputer riba, monitor, TV), peranti input (papan kekunci, tetikus, sentuhan, suara), mod landskap/potret, tema gelap/cerah, mod kontras tinggi, pengantarabangsaan (bahasa). , LTR/RTL), dan banyak lagi.
Selain itu, UI yang berbeza mungkin perlu dipaparkan berdasarkan tetapan pengguna.

Oleh itu, Letupan Kombinatorial tidak dapat dielakkan, dan adalah mustahil untuk melaksanakannya satu demi satu secara manual. (sumber imej)

Rethinking CSS in JS

作為代表性範例,請參閱我的 Firefox 主題中標籤欄佈局的定義和編譯。
儘管只考慮作業系統和使用者選項,但大約 360 行的檔案編譯結果達到大約 1400 行。

結論是,有效的設計實作本質上需要可擴展,通常以程式設計方式或透過明確定義的規則集進行管理。
結果是一個用於大規模一致管理的設計系統。

五、設計系統背景

設計系統作為單一事實來源,涵蓋從視覺樣式到 UI 模式和程式碼實現的設計和開發的各個方面。

Rethinking CSS in JS

根據 Nielsen Norman Group 的說法,設計系統包括以下內容:

  • 風格指南:針對特定風格需求提供風格指導的文檔,包括品牌、內容和視覺設計。
  • 元件庫:這些指定可重複使用的單一 UI 元素,例如按鈕。對於每個UI 元素,都提供了具體的設計和實現細節,包括可自定義的屬性(大小、複製等)、不同狀態(啟用、懸停、焦點、禁用)等信息,以及每個元素的可重複使用的乾淨、緊湊的程式碼元素。
  • 模式庫: 這些指定可重複使用模式,或從元件庫中取得的單一 UI 元素組。例如,您可能會看到頁面標題的模式,它可以由標題、麵包屑、搜尋以及主要和輔助按鈕組成。
  • 設計資源:為了讓設計師實際使用和設計元件和函式庫,需要一個設計文件(通常在 Figma 中)。通常還包含徽標、字體和字體以及圖標等資源,供設計師和開發人員使用。

設計系統應該充當設計師和開發人員的十字路口,支援功能表單可訪問性自訂
但設計師和開發者的想法不同,視角也不同。

讓我們以組件為鏡頭,來認識設計師和開發者視角的差異! !

5.1 組件結構

設計者也應該決定複選框控制項將使用哪個圖示。
Rethinking CSS in JS

設計師傾向於關注形式,而開發者傾向於關注功能。
對設計師來說,按鈕看起來很吸引人按就是按鈕,而對開發者來說,只要能按就是按鈕。

如果組件更複雜,設計師和開發人員之間的差距可能會進一步拉大。

Rethinking CSS in JS

5.2 設計者考慮

  • 視覺選項:外觀根據設定的選項而變化,如主要、重音、輪廓、純文字等
    Rethinking CSS in JS

  • 狀態選項:外觀會根據狀態和上下文而變化
    Rethinking CSS in JS

  • 設計決策:透過元件結構、視覺/狀態選項、視覺屬性(顏色、版式、圖示等)等來決定值。

5.3 開發者考慮因素

  • 選項:可設定的初始值。還包括視覺選項。例如)輪廓,尺寸
  • 狀態: 基於使用者互動的變更。例如)懸停、按下、聚焦、選擇(選取)
  • 事件: 觸發狀態變更的操作。例如)HoverEvent、PressEvent、FocusEvent、ClickEvent
  • 上下文: 從影響行為的程式碼注入的條件。例如)唯讀、停用

最終的形式是 Option、State 和 Context 的組合,從而導致上面提到的組合爆炸

其中,Option 符合設計者的觀點,而 State 和 Context 則不然。
考慮並行狀態、層級結構、守衛等進行狀態壓縮,以返回設計者的角度。
Rethinking CSS in JS

  • 啟用:停用關閉、按下關閉、懸停關閉、聚焦關閉
  • 懸停: 關閉、按下關閉、懸停開啟
  • 聚焦: 關閉、按下關閉、對焦開啟
  • 按:停用關閉,按開啟
  • 已停用: 停用開啟

6. 樣式是如何管理的?

正如您現在可能已經意識到的那樣,創建和維護高品質的 UI 是一項艱苦的工作。

所以狀態管理庫涵蓋了各種狀態,但是樣式是如何管理的呢?
雖然由於解決方案尚未建立,方法論、函式庫和框架不斷出現,但有三個主要範例
Rethinking CSS in JS

  1. 語意CSS:依元素的目的或意義分配類別。
  2. 原子 CSS: 為每個樣式(視覺)屬性建立一個類別。
  3. JS 中的 CSS: 用 Ja​​vaScript 編寫,並為每個元件單元隔離 CSS。

其中,JS 中的 CSS 感覺像是一種範式,使用完全不同的方法來表達和管理樣式。
這是因為JS中的CSS就像機制,而語意CSS和原子CSS就像策略
由於這種差異,JS 中的 CSS 需要與其他兩種方法分開解釋。 (圖片來源)

Rethinking CSS in JS

在討論 JS 機制中的 CSS 時,可能會想到 CSS 前/後處理器。
同樣,在談論政策時,可能會想到「CSS 方法」。

因此,我將按照以下順序介紹樣式管理方法:JS中的CSS、處理器、語義CSS和原子CSS以及其他樣式方法。

6.1 JS 中的 CSS

那麼,CSS在JS中的真實身分是什麼?
答案就在上面的定義中。

用 JavaScript 寫為每個元件單元隔離 CSS

  1. 用 JavaScript 寫的 CSS
  2. 元件層級的 CSS 隔離

其中,CSS隔離可以充分應用於現有CSS,解決全域命名空間和破壞隔離問題。
這是 CSS 模組。

Rethinking CSS in JS

根據上面提到的CSS in JS分析文章的鏈接,我將功能分類如下。
每個功能都有權衡,這些是在 JS 中創建 CSS 時重要的因素。

6.1.1 集成

特別值得注意的內容是SSR(伺服器端渲染)和RSC(React Server Component)
這些是代表前端的React和NEXT所追求的方向,它們很重要,因為它們對實現有重大影響。

  • IDE: 語法突出顯示和程式碼完成
  • TypeScript: 是否類型安全
  • 框架
    • 不可知論:獨立於框架,像 StyledComponent 這樣的函式庫是專門為 React 設計的。
    • SSR: 在伺服器上渲染時將樣式提取為字串並支援水合作用
    • RSC: RSC 僅在伺服器上運行,因此無法使用客戶端 API。

伺服器端渲染在伺服器上建立 HTML 並將其傳送到客戶端,因此需要將其提取為字串,並且需要對串流進行回應。與樣式組件的範例一樣,可能需要額外的設定。 (圖片來源)

Rethinking CSS in JS

  1. Server-side style extraction
    • Should be able to extract styles as strings when rendering on the server
    • Insert extracted styles inline into HTML or create separate stylesheets
  2. Unique class name generation
    • Need a mechanism to generate unique class names to prevent class name conflicts between server and client
  3. Hydration support
    • The client should be able to recognize and reuse styles generated on the server
  4. Asynchronous rendering support
    • Should be able to apply accurate styles even in asynchronous rendering situations due to data fetching, etc.

Server components have more limitations. [1, 2]
Server and client components are separated, and dynamic styling based on props, state, and context is not possible in server components.
It should be able to extract .css files as mentioned below.

Rethinking CSS in JS

  1. Static CSS generation
    • Should be able to generate static CSS at build time
    • Should be able to apply styles without executing JavaScript at runtime
  2. Server component compatibility
    • Should be able to define styles within server components
    • Should not depend on client-side APIs
  3. Style synchronization between client and server
    • Styles generated on the server must be accurately transmitted to the client

6.1.2 Style Writing

As these are widely known issues, I will not make any further mention of them.

  • Co-location: Styles within the same file as the component?
  • Theming: Design token feature supports
  • Definition: Plain CSS string vs Style Objects
  • Nesting
    • Contextual: Utilize parent selectors using &
    • Abitrary: Whether arbitrary deep nesting is possible

6.1.3 Style Output and Apply

The notable point in the CSS output is Atomic CSS.
Styles are split and output according to each CSS property.

Rethinking CSS in JS

  • Style Ouput
    • .css file: Extraction as CSS files