天天看點

設計了一個 CSS 終極解決方案!

文章目錄

    • 一、前言
    • 二、技術棧
    • 三、面臨的問題
    • 四、CSS 解決方案
      • 1、CSS 的艱難險阻 ——使用 CSS 所遇到的問題**
      • 2、CSS 的最新設計思想 —— Tailwind CSS
      • 3、終極 CSS 設計方案
      • 4、終極換膚方案
    • 五、小結

一、前言

随着産品的疊代,發現現有的産品架構上存在很多的問題,比如性能方面、業務方面、團隊協作方面等。作為一個老項目,已經無法再做進一步的優化和完善了,是以這次我們團隊打算對整個項目進行一次徹底的重構。

用了大約一個星期的時間,将有關的設計方案整理出來。本篇文章主要記錄了項目中頁面樣式 CSS 最終采用的解決方案,當然,要提前說一下,這些方案最終是根據實際的業務場景、客戶需求以及團隊協作來決定的,并非适用其他所有項目。

二、技術棧

  • 老項目技術棧: Vue2 + Less + Webpack +組建庫(内部開發)
  • 新項目技術棧: Vue3 + CSS + TypeScript + vite + Ant Design

三、面臨的問題

所有的解決方案都是根據目前項目面臨的實際問題來展開讨論的,如下:

1、 随着産品功能的不斷完善,樣式檔案的體積是呈現增量上升的,達到一定程度,會導緻打包速度和性能變慢;

2、 新需求涉及到線上換膚,老項目無法優雅的相容。(可以做,但成本比較大);

3、 團隊協作時,樣式檔案寫的比較亂,每個人都有自己的一套風格,導緻代碼備援,後期無法更好的維護。

四、CSS 解決方案

最終,我們決定對項目中用到的樣式資訊進行一個分層概念的處理。當然,你可能會有一系列的疑問,CSS 不就是寫 class 樣式麼,然後将 class 挂在到相對應的 HTML 結構當中就行了。

為什麼要分層?分層的意義何在?該如何分層?分層的依據又是什麼?

1、CSS 的艱難險阻 ——使用 CSS 所遇到的問題**

在解決上述問題之前,我們需要先來探究在我們日常項目中遇到的一些有關 CSS 的問題。

  • 問題一:不同的 HTML 結構,相同的 CSS 樣式
<!-- 元件一 -->
<div class="article-preview">
  <img class="article-preview__image" src="./image.png" alt="">
  <div class="article-preview__content">
    <h2 class="article-preview__title">文章标題:XXXXXXX</h2>
    <p class="article-preview__body"> 
      文章内容:XXXXXX XXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。
    </p>
  </div>
</div>
           

如上,我寫了一個有關文章預覽的元件,所有的 class 采用的是 Block Element Modifer 的寫法。但是由于需求的增加,需要寫一個與文章預覽 CSS 樣式相同元件,我們稱履歷元件吧。

div>
  <imgsrc="./header.png" alt="">
  <div>
    <h2>姓名:小鹿</h2>
    <p>
      個人簡介:熟練掌握 vue 開發,xxxxxxxxxxxxx
    </p>
  </div>
</div>
           

雖然上述的 HTML 的結構的内容是不同的,但是 CSS 樣式完全是相同的。由于文章預覽的元件 class 類名是語義化的(與有關文章預覽所對應),如果我們直接使用文章預覽的 class 到履歷元件,違反了語義化的原則。

是以我們不得不複制一份與文章預覽所有 class 相同的樣式,隻不過是将相對應的 class 名改成了與履歷有關的語義化類名。

<div class="author-bio">
  <img class="author-bio__image" src="./header.png" alt="">
  <div class="author-bio__content">
    <h2 class="author-bio__name">姓名:小鹿</h2>
    <p class="author-bio__body">
      個人簡介:熟練掌握 vue 開發,xxxxxxxxxxxxx
    </p>
  </div>
</div>
           

那麼問題來了,明明是相同的樣式,每次遇到相同的樣式都要去複制一份,導緻到代碼備援以及項目樣式檔案體積的增大。

到這,我們可能想到将 class 改成通用的語義化不就可以了,class 起名 media-card。

<div class="media-card">
  <img class="media-card__image" src="https://cdn-images-1.medium.com/max/1600/0*o3c1g40EXj65Fq9k." alt="">
  <div class="media-card__content">
    <h2 class="media-card__title">文章标題:XXXXXXX</h2>
    <p class="media-card__body">
      文章内容:XXXXXX XXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。
    </p>
  </div>
</div>

<div class="media-card">
  <img class="media-card__image" src="https://i.vimeocdn.com/video/585037904_1280x720.webp" alt="">
  <div class="media-card__content">
    <h2 class="media-card__title">姓名:小鹿</h2>
    <p class="media-card__body">
       個人簡介:熟練掌握 vue 開發,xxxxxxxxxxxxx
    </p>
  </div>
</div>
           

通過上述将 class 類名統一的形式,我們可以做到重用 CSS 樣式,而不是讓 CSS 樣式依賴于特定的 HTML 結構。

  • 問題二:相同的 HTML 結構,不同的 CSS 樣式

雖然上述重用了很多的 class 類,但是問題又來了,如果有一天,老闆讓我在文章預覽的邊框添加一個特殊的樣式,而履歷元件樣式不變,我們隻能使用内聯樣式(違反了 HTML 與 CSS 的低耦合原則)或者再聲明一個 class 類将其覆寫掉(靈活度下降,代碼備援)。

由于 HTML 結構太過于依賴 CSS ,雖然 class 可以進行重用,但是 HTML 不能靈活的進行特殊樣式的設計,那麼這種解決方案看起來并不靈活。能不能有一種方案既能夠重用 CSS 樣式,又能對 HTML 進行特殊的處理呢?

對于 CSS 的發展曆程,這裡參考了一下 Adam Wathan 大佬所寫的 CSS 個人使用經曆,以及 CSS 各個寫法的優缺點介紹。

英文連結:https://adamwathan.me/css-utility-classes-and-separation-of-concerns/

中文翻譯:https://tailwindchina.com/translations/css-utility-classes-and-separation-of-concerns.html

這篇文章也是大名鼎鼎的 Tailwind CSS 項目的作者 Adam Wathan 寫的。擷取更多有關 Tailwind 的資訊,可以戳官網(https://docs.tailwindchina.com/)。

2、CSS 的最新設計思想 —— Tailwind CSS

我們在探索解決方案時,正好發現了 Tailwind CSS 這個項目,根據它的設計思想,進而進一步完善了我們項目有關 CSS 的解決方案。

先看 Tailewind CSS 官方的一個例子。

<button class="py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-green-500 hover:bg-green-700">
  Click me
</button>

/** tailwind class 部分源碼 **/
.py-2 {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem
}

.px-4 {
  padding-left: 1rem;
  padding-right: 1rem
}

.font-semibold {
  font-weight: 600
}

.rounded-lg {
  border-radius: 0.5rem
}

.shadow-md {
  --tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)
}

.bg-green-500 {
  --tw-bg-opacity: 1;
  background-color: rgba(16, 185, 129, var(--tw-bg-opacity))
}
           
設計了一個 CSS 終極解決方案!

從使用上來看,同樣使用了 class 的樣式組合,表面上與我們的使用并沒有兩樣,但是仔細分析就會發現,它的 class 類中的屬性顆粒度更小,這樣做的原因在于保證 class 的「靈活性」和「可重用性」—— 一個 class 類中聲明的屬性越多,就越難以重用。

也就是每個 class 中相關的屬性盡可能的遵循最少原則,于此同時其不同的屬性組合又能與 class 類名所對應,保留了 CSS 的語義化的規範 。

根據上述對 CSS 語義化的深入了解,我總結了幾個有關語義化 CSS 的特性。

  • 語義化的 class —— 重用性
/***** font-weight *****/
.font-weight-normal {
  font-weight: normal;
}
.font-weight-bold {
  font-weight: bold;
}
.font-weight-100 {
  font-weight: 100;
}
.font-weight-200 {
  font-weight: 200;
}
.font-weight-300 {
  font-weight: 300;
}
.font-weight-400 {
  font-weight: 400;
}
......

/***** margin  *****/
.margin-auto {
  margin: auto;
}
.margin-0 {
  margin: 0;
}
.margin-5 {
  margin: 5px;
}
.margin-10 {
  margin: 10px;
}

......
           
  • 語義化的 class —— 局限性

上述我們提到過,語義化的 class 最大的問題就是在每個類中不要聲明太多的 CSS 屬性,那樣會造成 class 變的難以重用以及靈活度大大降低。

如果想用上述的設計思想,那麼就要保證這個原則 —— 一個 class 類中聲明的屬性越多,這個 class 就越難以重用。

3、終極 CSS 設計方案

最終我們并沒有直接用 Tailwind CSS 項目的原因在于項目中特殊的換膚需求,雖然可以用 Tailwind CSS 來實作換膚,但是不夠優雅和友善,下文會具體聊聊換膚方案。下面就具體介紹一下我們基于 Tailwind CSS 的設計思想以及加入靈活運用 CSS 變量的方式,決定的 CSS 終極解決方案。

在文章的開頭,我們提到過一個分層的概念,沒錯,我們要對 CSS 進行三個層級的分層,分别為變量原子層、變量應用層、class 應用層,我們接下來分析每一層的作用。

  • 變量原子層(atomicLayer)

我們主要在這一層聲明 CSS 的原子化的變量,也就是頁面中所有使用到的有關「大小」和「顔色」、「動畫」等的屬性值(顔色值、文字大小、邊框大小等),我們頁面用到的所有有關顔色、大小、動畫等的屬性值都應該在裡聲明。

/** 變量原子層 **/
<!-- atomicLayer.css -->
:root {
  /***** color *****/
  /* transparent */
  --color-transparent: transparent;

  /* black */
  --color-black: #000000;
  --color-black-1: rgba(0, 0, 0, 0.85);
  --color-black-2: rgba(0, 0, 0, 0.45);
  --color-black-3: rgba(0, 0, 0, 0.25);
  --color-black-4: rgba(0, 0, 0, 0.15);
  --color-black-5: rgba(0, 0, 0, 0.018);
  ....

  /* white */
  --color-white: #ffffff;
  --color-white-1: #f5f7fa;
  --color-white-2: #fafafa;
  ...

  /* blue */
  --color-blue-1: #1890ff;
  --color-blue-2: #40a9ff;
  --color-blue-3: #e6f7ff;
  --color-blue-4: rgba(24, 144, 255, 0.2);
  ...

  /* gray */
  --color-gray-1: #d9d9d9;
  ...

  /* red */
  --color-red-1: #ff4d4f;
  --color-red-2: rgba(255, 77, 79, 0.2);

  /***** font-size *****/
  --font-size-1: 12px;
  --font-size-2: 14px;
  --font-size-3: 16px;
  --font-size-4: 18px;

  /***** border-radius *****/
  --border-radius-1: 2px;
  --border-radius-2: 4px;
  --border-radius-3: 6px;
  --border-radius-4: 8px;

  /***** transition-duration *****/
  --transition-duration-1: 0.15s;
  --transition-duration-2: 0.3s;
  --transition-duration-3: 1s;

  /***** animation-duration *****/
  --animation-duration-1: 0.15s;
  --animation-duration-2: 0.3s;
  --animation-duration-3: 1s;
}
           
  • 變量應用層(applicationLayer)

這一層作為原則層的上一層應用,裡邊所有相關的值取自于變量原子層,主要将不帶有語義的變量原子層附上一層語義化。當然,這一層還有一個特殊的作用,就是後續的換膚方案,改變的是變量應用層的變量來達到換膚的效果。

/**
 * 原子應用層
 */

:root {
	/***** color *****/
	--primary-color: var(--color-blue-1);
	--secondary-color: var(--color-blue-2);
	--third-color: var(--color-blue-3);
	--error-color: var(--color-red-1);

	/* text */
	--text-color: var(--color-black-1);
	--text-hover-color: var(--secondary-color);
	--text-disabled-color: var(--color-black-3);
	--text-error-color: var(--color-red-1);

	/* button */
	--btn-primary-color: var(--color-white);
	--btn-primary-hover-color: var(--color-white);
	--btn-primary-disabled-color: var(--text-disabled-color);
	--btn-plain-color: var(--text-color);
	--btn-plain-hover-color: var(--text-hover-color);
	--btn-plain-disabled-color: var(--text-disabled-color);
	--btn-text-color: var(--text-color);
	--btn-text-hover-color: var(--text-hover-color);
	--btn-text-disabled-color: var(--text-disabled-color);
	--btn-link-color: var(--primary-color);
	--btn-link-hover-color: var(--secondary-color);
	--btn-link-disabled-color: var(--text-disabled-color);

	/* tab */
	--tab-color: var(--text-color);
	--tab-hover-color: var(--secondary-color);
	--tab-actived-color: var(--primary-color);
	--tab-disabled-color: var(--text-disabled-color);

	/* menu */
	--menu-color: var(--text-color);
	--menu-hover-color: var(--primary-color);
	--menu-actived-color: var(--primary-color);
	--menu-disabled-color: var(--text-disabled-color);


	/***** bg-color *****/
	/* body */
	--body-bg-color: var(--color-white-1);
	--common-bg-color: var(--color-white-1);

	/* btn */
	--btn-primary-bg-color: var(--primary-color);
	--btn-primary-hover-bg-color: var(--secondary-color);
	--btn-primary-disabled-bg-color: var(--color-white-1);
	--btn-plain-disabled-bg-color: var(--color-white-1);
	--btn-text-hover-bg-color: var(--color-black-5);
	--btn-text-disabled-bg-color: var(--color-transparent);
	--btn-link-bg-color: var(--color-transparent);
	--btn-link-hover-bg-color: var(--color-transparent);
	--btn-link-disabled-bg-color: var(--color-transparent);

	/* tab */
	--tab-card-bg-color: var(--color-white-2);
	--tab-card-actived-bg-color: var(--color-white);

	/* table */
	--table-header-bg-color: var(--color-white-2);
	--table-row-hover-bg-color: var(--third-color);
	--table-row-striped-bg-color: var(--color-white-2);

	/* select */
	--select-hover-bg-color: var(--color-white-2);
	--select-selected-bg-color: var(--third-color);

	/* menu */
	--menu-item-selected-bg-color: var(--third-color);

	/* dialog */
	--dialog-mask-bg-color: var(--color-black-4);


	/***** border-color *****/
	--border-color: var(--color-gray-1);
	--border-hover-color: var(--secondary-color);
	--border-disabled-color: var(--color-gray-1);
	--border-error-color: var(--error-color);

	/* btn */
	--btn-primary-border-color: var(--primary-color);
	--btn-primary-hover-border-color: var(--secondary-color);
	--btn-primary-disabled-border-color: var(--border-color);
	--btn-plain-border-color: var(--border-color);
	--btn-plain-hover-border-color: var(--secondary-color);
	--btn-plain-disabled-border-color: var(--border-color);
	--btn-text-border-color: var(--color-transparent);
	--btn-text-hover-border-color: var(--color-transparent);
	--btn-text-disabled-border-color: var(--color-transparent);
	--btn-link-border-color: var(--color-transparent);
	--btn-link-hover-border-color: var(--color-transparent);
	--btn-link-disabled-border-color: var(--color-transparent);


	/***** box-shadow-color *****/
	--box-shadow-color: var(--color-black-4);

	/* input */
	--input-focus-box-shadow-color: var(--color-blue-4);
	--input-error-focus-box-shadow-color: var(--color-red-2);


	/***** font-size *****/
	--font-size-mini: var(--font-size-1);
	--font-size-small: var(--font-size-2);
	--font-size-middle: var(--font-size-3);
	--font-size-large: var(--font-size-4);

	/* text */
	--text-font-size: var(--font-size-mini);

	/* title */
	--title-font-size: var(--font-size-large);


	/***** border-radius *****/
	--border-radius-mini: var(--border-radius-1);
	--border-radius-small: var(--border-radius-2);
	--border-radius-middle: var(--border-radius-3);
	--border-radius-large: var(--border-radius-4);

	--border-radius: var(--border-radius-small);


	/***** transition-duration *****/
	--transition-duration-fast: var(--transition-duration-1);
	--transition-duration-normal: var(--transition-duration-2);
	--transition-duration-slow: var(--transition-duration-3);

	--transition-duration: var(--transition-duration-fast);


	/***** animation-duration *****/
	--animation-duration-fast: var(--animation-duration-1);
	--animation-duration-normal: var(--animation-duration-2);
	--animation-duration-slow: var(--animation-duration-3);

	--animation-duration: var(--animation-duration-fast);
}

           
  • class 應用層(applicationLayerClasses)

在 class 應用層中我們具體的寫一些有關公共的 class(common.css)、元件相關class(component.css)、其他 class(other.css)等樣式,接下來會具體介紹這些檔案的内容和作用。

  • 通用樣式 —— common.css

在 common.css 中的 class 樣式,類似于Tailwind CSS 中聲明的最小顆粒度的樣式類,但是與 Tailwind CSS 不同的是,我們所有的變量值都是取自于「原子應用層」。

/***** color *****/
/* text */
.text-color {
  color: var(--text-color);
}
.hover:text-color:hover {
  --text-color: var(--text-hover-color);
}
.text-disabled-color,
.text-disabled-color:hover {
  --text-color: var(--text-disabled-color);
}
.text-error-color,
.text-error-color:hover {
  color: var(--text-error-color);
}

/***** border-color *****/
.border-color {
  border-color: var(--border-color);
}
.hover:border-color:hover {
  --border-color: var(--border-hover-color);
}
.border-disabled-color,
.border-disabled-color:hover {
  --border-color: var(--border-hover-color);
}
.border-error-color,
.border-error-color:hover {
  --border-color: var(--border-error-color);
}

/***** box-shadow *****/
.box-shadow {
  box-shadow: 0 2px 8px var(--box-shadow-color);
}

/***** font-size *****/
.font-size-mini {
  font-size: var(--font-size-mini);
}
.font-size-small {
  font-size: var(--font-size-small);
}
.font-size-middle {
  font-size: var(--font-size-middle);
}
.font-size-large {
  font-size: var(--font-size-large);
}
...
           

而 Tailwind CSS 源碼中的屬性值都是寫死的值,如下:

<!-- border color -->
.border-white {
  --tw-border-opacity: 1;
  border-color: rgba(255, 255, 255, var(--tw-border-opacity))
}

.border-gray-50 {
  --tw-border-opacity: 1;
  border-color: rgba(249, 250, 251, var(--tw-border-opacity))
}

.border-gray-100 {
  --tw-border-opacity: 1;
  border-color: rgba(243, 244, 246, var(--tw-border-opacity))
}

.border-gray-200 {
  --tw-border-opacity: 1;
  border-color: rgba(229, 231, 235, var(--tw-border-opacity))
}

.border-gray-300 {
  --tw-border-opacity: 1;
  border-color: rgba(209, 213, 219, var(--tw-border-opacity))
}

<!-- font-size -->

.text-xs {
  font-size: 0.75rem;
  line-height: 1rem
}

.text-sm {
  font-size: 0.875rem;
  line-height: 1.25rem
}

.text-base {
  font-size: 1rem;
  line-height: 1.5rem
}

.text-lg {
  font-size: 1.125rem;
  line-height: 1.75rem
}
...
           

我們之是以這樣設計的目的為了能夠在實作線上換膚的時候,通過覆寫原子應用層的變量值,直接進行換膚,後續換膚章節會具體講到。

  • 元件相關 —— component.css

由于我們使用的是 Ant Design 元件庫,考慮到可能後續的業務無法被現有的元件庫所支援,是以需要自己造輪子,那麼所有元件庫相關的應用層的 class 統一寫到這個檔案中。

  • 其他 class —— other.css

other.css 也是一些通用的 class,但是為什麼不寫到 common.css 中,原因在于 other.css 中的 class 類是由多個屬性組成的最小功能類。如下我們常用到的:

<!-- other.css -- >

/* 垂直居中 */
.flex-middle {
  display: flex;
  justify-content: center;
  align-items: center;
}

/* 多餘文本 ... */
.text-ellipsis {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
           

而 common.css 中是一些單個屬性的最小功能類,是以在這裡我們将其兩者做了一個區分存儲。

好了,到這裡我們的 CSS 分層結構分享完了,是不是非常的簡單和清晰,為了更加深入體驗到分層以及 Tailwind CSS 設計思想的,我們來做一個 Demo,體驗一下愉快的開發,順便分享一下最終的線上換膚方案。

4、終極換膚方案

起初,我們團隊中提出過很多的換膚方案,通過組内不斷讨論和交流,最終選擇我們統一了意見,選擇了最終這種換膚方案。

我們最終采用了這種換膚方案,不是說其他的換膚方案不好,而是我們團隊根據之前所遇到的問題以及現有的業務需求決定的,特定的項目環境以及産品需求,再加上人為因素的影響,決定了相同的問題,會出現不同的解決方案。

分别從以下三個方面是什麼、怎麼樣、為什麼來介紹采用的這個換膚方案。

  • 換膚原理 —— what

我們采用的用換膚原理是通過改變與頁面樣式相對應的 class 中的 CSS 的變量的值來實作換膚的功能。

注意,這裡改變的是原子應用層中的 CSS 變量,而不是原子層的變量,因為我們如果改變的原子層,也就是最底層的變量值,那麼頁面中很多不需要換膚的元素也被改變了,這也是我們設計原子應用層的原因所在。

是以我們在寫頁面涉及到換膚元素的時候,涉及到換膚的相關變量的 CSS 值應該去原子應用層中擷取。

有一個問題,我們用到的是第三方的元件庫 Ant Design,涉及到元件庫中的換膚如何實作?這也是我們換膚方案中的一個缺陷,應對方案是需要将元件庫中所有的元件的上層樣式做一層 class 覆寫,所覆寫的 class 中的屬性值用我們原子應用層的 CSS 變量來代替,進而實作元件庫的換膚。

雖然前期的工作量非常大,但是後續的維護和使用沒有了心智負擔,這也是我們團隊中對這種方案的取舍。

  • 效果如何 —— How

這裡我寫了一個 Demo,已經上傳至 Github,項目位址:https://github.com/luxiangqiang/peedingDemo

App.vue

<!--
 * @Author: xiaolu
 * @Date: 2021-08-22 14:34:41
 * @LastEditors: xiaolu
 * @LastEditTime: 2021-08-26 09:33:00
 * @Description:
-->
<template>
  <div class="height-100% bg-page">
    <a-button
      class="margin-left-25 margin-top-25 box-shadow font-size-middle border-radius-large"
      @click="changeTheme"
    >
      換膚按鈕
    </a-button>
    <div class="width-100% height-100%">
      <div>
        <div class="font-size-middle margin-left-25 margin-top-25">
          字型顔色:
        </div>
        <div class="width-100% flex-around margin-top-10">
          <div
            v-for="(item,index) in fontColors"
            :key="index"
            :class="['font-size-large',item.color]"
          >
            {{ item.name }}
          </div>
        </div>
        <div class="font-size-middle margin-left-25 margin-top-20">
          字型大小:
        </div>
        <div class="width-100% flex-around margin-top-10">
          <div
            v-for="(item,index) in fontSizes"
            :key="index"
            :class="[item.fontSize]"
          >
            {{ item.name }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>

import { ref,reactive } from 'vue'

let flag = true

const fontColors = reactive([
  // {
  //  name: 'body 字型顔色',
  //  color: 'text-color'
  // },
  {
    name: '禁用字型顔色',
    color: 'text-disabled-color'
  },
  {
    name: '錯誤字型顔色',
    color: 'text-error-color'
  }
]
)
const fontSizes = reactive([
  {
    name: 'mini 大小',
    fontSize: 'font-size-mini'
  },
  {
    name: 'small 字型',
    fontSize: 'font-size-small'
  },
  // {
  //  name: 'middle 字型',
  //  fontSize: 'font-size-middle'
  // },
  {
    name: 'large 字型',
    fontSize: 'font-size-large'
  }
]
)

function changeTheme(){
  if(flag){
    document.documentElement.setAttribute('data-theme', 'theme')
  }else{
    document.documentElement.setAttribute('data-theme', '')
  }
  flag = !flag
}
</script>
           

上述頁面中涉及到的換膚 calss 的屬性值,都是取自原子應用層的 CSS 變量。下面聲明新的換膚 CSS 變量(替換原子應用層)

theme.css

:root[data-theme="theme"]{
  --btn-plain-border-color: #f30606;
  --btn-plain-color: #00ff00;

  --btn-plain-hover-border-color: rgb(255, 115, 0);
  --btn-plain-hover-color: rgb(255, 115, 0);
  --body-bg-color: #cccccc;

  /* 字型顔色 */

  /* --text-color:rgb(35, 243, 8) ; */
  --text-disabled-color: rgb(23, 19, 231);
  --text-error-color: rgb(216, 21, 216);

  /* 字型大小 */
  --font-size-mini: 14px;
  --font-size-small: 16px;
  --font-size-large: 20px;
}
</script>
           

暴露 index.css 入口,引入頁面中:

/**
 * CSS 變量
 */

/* 原子層 */
@import url("./atomicLayer.css");
/* 原子應用層 */
@import url("./applicationLayer.css");

/* 換膚 css */
@import url("./theme.css");
           

啟動項目,如下頁面:

設計了一個 CSS 終極解決方案!

換膚之後效果:

設計了一個 CSS 終極解決方案!

上述 button 按鈕是用的第三方庫(Ant Design)的,也達到了換膚的效果,其他包括背景顔色,字型大小也實作了線上換膚,更多元件換膚體驗可以在 Github 下載下傳本項目的 Demo。

  • 使用原因 —— why

換膚方案有很多,為什麼不使用其他的換膚方案,這裡有兩個經典的問題,具體和大夥兒聊一下。

  • 為什麼不使用 class 實作換膚?

如果使用 Tailwind CSS 那麼換膚就要替換相對應的 class,我們之是以直接改變原子應用層 CSS 變量的值來達到效果的原因在于它相對于用 class 覆寫更為靈活和友善。

因為一個變量值可以被多個 class 所引用,而像 Tailwind 中的 class 對應一個屬性值,在覆寫 class 時,就需要寫更多的 class 覆寫類。

  • 為什麼不使用 less 實作換膚

less 的線上換膚的實作是通過切換不同的變量來實作,由于考慮到性能的原因,當項目非常大時,線上實時編譯 less 為 css 的性能會下降,考慮到這一點,是以 Less 的換膚方案并未采納。

五、小結

當然,每個方案都會有自己的優缺點,我們的方案也不例外。

通過團隊回報,使用 Tailwind CSS 的思想書寫 class 一開始有心智上的負擔,前期的使用成本比較大,可能搭建頁面效率低了點,這是使用這種方案最大的一個不足點,但是這種不足是可以通過後期慢慢的改變的,當熟悉了使用的 class 類,使用起來不會導緻效率低下,這個還需要後期團隊的使用回報情況而定。

這部分内容就分享到這,如果你有什麼好的問題或方案,歡迎下方留言讨論~