天天看点

设计了一个 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 类,使用起来不会导致效率低下,这个还需要后期团队的使用反馈情况而定。

这部分内容就分享到这,如果你有什么好的问题或方案,欢迎下方留言讨论~