一、前言

随着产品的迭代,发现现有的产品架构上存在很多的问题,比如性能方面、业务方面、团队协作方面等。作为一个老项目,已经无法再做进一步的优化和完善了,所以这次我们团队打算对整个项目进行一次彻底的重构。

用了大约一个星期的时间,将有关的设计方案整理出来。本篇文章主要记录了项目中页面样式 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 各个写法的优缺点介绍。

英文链接:adamwathan.me/css-utility…

中文翻译:tailwindchina.com/translation…

这篇文章也是大名鼎鼎的 Tailwind CSS 项目的作者 Adam Wathan 写的。获取更多有关 Tailwind 的信息,可以戳官网(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))
}
 

企业微信截图_8711ef67-c0c3-48c2-8587-8f7431e9ec05.png

从使用上来看,同样使用了 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;
}