政采云技术团队.png

大海.png

> 这是第 163 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注我们吧~ 本文首发于政采云前端博客:小而美的 css 的原子化open in new window

什么是 CSS 原子化

引用 文章 Let’s Define Exactly What Atomic CSS isopen in new window 中定义:

> Atomic CSS is the approach to CSS architecture that favors small, single-purpose classes with names based on visual function.

译文:

> 原子化 CSS 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名。

.bg-blue { background-color: #357edd; } 
.f1 { font-size: 3rem; }
.m0 { margin: 0; }

通常情况下我们是怎么写 CSS 的

原子化 CSS 是一个 CSS 框架, 在没有对应的 CSS 框架的项目中,是如何写 CSS 的。我们引用 Challenging CSS Best Practicesopen in new window 文中例子来看看。

如图所示:我们要实现一个类似访问记录组件,右边是访问人的头像,左侧是访问的描述。

20220920162400

对应代码.

<div class="media">
  <a href="https://www.zcygov.cn/" class="img">
    <img width="40" src="logo.png" alt="zcy" />
  </a>
  <div class="bd">@小明 14 分钟之前</div>
</div>
<style>
.media {
  margin: 10px;
}
.media,
.bd {
  overflow: hidden;
  _overflow: visible;
  zoom: 1;
}
.media .img {
  float: left;
  margin-right: 10px;
}
.media .img img {
  display: block;
}
</style>

新增一个设计,需要把头像放在右侧,描述在左侧,如图所示

20220920162310

我们通过新增一个类名 imgExt,右浮动

代码:

<div class="media">
  <a href="https://www.zcygov.cn/" class="imgExt">
    <img width="40" src="https://sitecdn.zcycdn.com/f2e-assets/b37c37db-ce59-4bfe-a889-5c8615d008c8.png" alt="zcy" />
  </a>
  <div class="bd">@小明 14 分钟之前</div>
</div>
<style>
  ...
/* 图片在右侧 */
.media .imgExt {
  float: right;
  margin-left: 10px;
}
</style>

这时候有来一个新设计,要求组件在页面右侧栏中时候,字体变小

20220920162709

代码:

<div id="rightRail">
<div class="media">
  <a href="https://www.zcygov.cn/" class="imgExt">
    <img width="40" src="https://sitecdn.zcycdn.com/f2e-assets/b37c37db-ce59-4bfe-a889-5c8615d008c8.png" alt="zcy" />
  </a>
  <div class="bd">@小明 14 分钟之前</div>
</div>
<style>
</div>
<style>
  ...
/* 页面右侧容器中时,字体变小 */
#rightRail .bd {
    font-size: smaller;
}
</style> 

上诉的代码是在2013年发布的文章中引用的,CSS编码和命名的抽象是在组件层上,这样的抽象和命名存以下问题。

  • 一个简单的样式修改,都需要有新增一条样式规则。面向业务组件内CSS的抽象,复用的颗粒度是到组件的,比如例子中访问记录组件,是按照组件复用。组件中单个规则是很难单独抽离复用。
  • 在例子中的6个样式规则中,4个是基于上下文的
  • 新的需求与原有规则冲突,需要修改一个其中一个样式,是通过新增一个嵌套类覆盖原有的样式

9年过去了,前端技术飞速发展,但是看一眼现有项目中的CSS,相似的问题还是存在的。因为CSS组件化的思路没有变,还是按照组件维度去写。说明这些问题不是简单从CSS改用LESS就能解决的。而且随着业务复杂度变高,相关构建工具升级会引入更多的问题。

  • 相同功能在不同组件内部需要重新命名:比如字符过长省略的样式,CSS规则是一致的,比如一个订单详情字符缩略和发票信息的字符缩略都会各自独立实现,而不会相互引用,因为没人会在订单中使用发票的CSS类,CSS复用度问题。
.order-overflow-dot {
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
...
  .title-number {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
...  
.invoice-overflow-content {
    div {
     text-overflow: ellipsis;
     white-space: nowrap;
     overflow: hidden;
    }
 }
  • 类命名困难:缺失命名规范,不同人不同的风格,没有什么可读性,给类取名字一个十分痛苦的事情,大多数时候类名不伦不类,难以读懂。
.time-tip {
  i {
    margin-left: 10px;
    cursor: pointer;
  }
}
​
#service-charge-Bill {
  .zcy-search-panel .ant-form-item-label {
    min-width: 80px;
  }
}
​
.invoice-tpl-container {
  max-height: 360px;
  overflow-y: scroll;
}
​
.add-invoice-tpl-item {
  border: 1px dashed rgb(209, 215, 232);
  text-align: center;
  height: 160px;
  line-height: 160px;
  margin: 10px;
}
​
.invoice-tpl-item {
  border: 1px solid rgb(209, 215, 232);
  margin: 10px;
  height: 160px;
  position: relative;
​
  .invoice-content {
    overflow: hidden;
    padding: 20px;
    height: 119px;
​
    div {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
​
    .invoice-type-mark {
      display: inline-block;
      width: 20px;
      height: 20px;
      line-height: 20px;
      text-align: center;
      color: white;
      background-color: #3177fd;
    }
  }
​
  .operate {
    position: absolute;
    bottom: 0;
    width: 100%;
    border-top: 1px solid rgb(209, 215, 232);
    height: 40px;
    line-height: 40px;
    text-align: center;
  }
}
​
.invoice-tpl-item.active {
  border-color: #3177fd;
}
​
.default-mark {
  position: absolute;
  right: 0;
  top: 0;
  background-color: #bbb;
  color: white;
  padding: 0 5px;
}
  • 重复的 CSS 文件:现有项目大家都习惯将 CSS 文件创建在业务的目录中,我的项目是用 webpack 构建的,开发阶段 CSS 文件是按需加载的。这样就存在一个问题,如果一个页面也需要用另外页面的样式,开发同学第一反应是复制粘贴。很少发现同学用@import 的方式,导致大量重复的 CSS 文件。

    image-20220901103410149

  • 无意义的嵌套和使用&符号:无法搜索定位样式,且可读性极差

.catalogue {
  &-title {
    display: flex;
    &-btns {
      flex-grow: 1;
      align-items: center;
      text-align: right;
      .ant-btn {
        margin: 0 5px;
      }
    }
  }
  &-content {
    height: calc(100% - 60px);
    overflow: auto;
    padding: 18px;
    .cy-tree {
      width: 80%;
    }
    .ant-tree {
      overflow: auto;
    }
  }
  &-tree {
    height: calc(100% - 22px);
    .ant-input {
      width: 100%;
      &-search {
        width: 80%;
        flex-grow: 1;
      }
    }
  }
}
​

我所负责的项目都是后台管理的项目,团队也有成熟的组件库。这类的项目CSS的工作量是比较少的,所以项目开始之处,没有指定对应CSS规范和架构。大家都是按照组件的抽象思维,没有规范命名规程,日积月累,导致现有项目CSS代码状况较差,维护起来让人十分头大。如何解决这些问题呢?就需要我们改变现有CSS编码思路,改变现在业务组件抽象维度,制定适合命名规范。这些内容就是我们平时所说的CSS的架构,我们这边调研几个比较流行的CSS框架,以下是几个框架简要介绍。

CSS框架介绍

原子化 CSS

定义

> 原子化 CSS 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class。

原则

> - class 的命名按照功能 > - class 的功能单一

相关资料

> <https://css-tricks.com/lets-define-exactly-atomic-css/>

OOCSS (Object-Oriented CSS 面向对象 CSS)

定义

> OOCSS (Object-Oriented CSS 面向对象 CSS ) 是组织 CSS 的领先的模块化或基于组件的系统。它是 Nicole Sullivan 在 2008 年在 Web Directions North 大会上首次提出的,核心就是编写可复用和可维护的样式

原则

> - 分离结构(structure)和皮肤(skin)。 您应该在基础对象中保留结构和位置,并在扩展类中保留视觉特征(如 backgroundborder)。这样您就不必覆盖视觉属性。 > - 分离容器(container)和内容(content)。 永远不要在 CSS 中模仿 HTML 的结构。换句话说,不要在样式表中引用标签或 ID。相反,尝试创建和应用描述相关标签使用的类。并将嵌套类保持在最低限度。

相关资料

> <http://oocss.org/>

SMACSS (Scalable and Modular Architecture for CSS)

定义

> SMACSS(Scalable and Modular Architecture for CSS)编写模块化、结构化和可扩展的 CSS。

原则:SMACSS 认为 CSS 有 5 个类别,我们通过这 5 种类别来拼凑出完整的 class

> - Base 基础样式 > - Layout 布局样式 > - Module 模块样式 > - State 状态样式 > - Theme 主题样式

相关资料

> <http://smacss.com/>

BEM( block, element, modifier

定义

> 首先 BEM 是一个分层系统,它把我们的网站分为三层,这三层正好对应着 BEM 三个英文单词的简写 block, element, modifier,分为为 块层、元素层、修饰符层

原则

> - 使用__两个下划线将块名称与元素名称分开 > - 使用--两个破折号分隔元素名称及其修饰符 > - 一切样式都是一个类,不能嵌套

相关资料

> <https://getbem.com/>

ITCSS

定义

> 理智、可扩展、可管理CSS架构

原则:类似 SMACSS 对 CSS 元素进行了分层

> - Settings – 与预处理器一起使用,包含颜色、字体等定义 > - Tools – 工具与方法,比如 mixins,Settings 与 Tools 都不会产生任何 CSS 代码,仅仅是辅助函数与变量 > - Generic – 通用层,比如 reset htmlbody 的样式 > - Elements – 对通用元素的样式重置,比如  a p div 等元素的样式重置 Objects – 类似 OOCSS 中的对象,描述一些常用的基础状态 > - Components – 对组件样式的定义,一个 UI 元素基本由 Objects 与 Components 组成 > - Utilities – 工具类,比如 .hidden

相关资料

> <https://getbem.com/>

考虑到我们自身项目项目是一个后台管理类的项目,有成熟组件库。日常 CSS 开发工作量和复杂度都不高。我们也希望有一个快速可以落地的框架方案,且实现的成本较低。我们对比几个框架后,选择 CSS 原子化作为我们的 CSS 的架构方案。CSS 原子化提供现成的解决方案,几乎就是拿就用( tailwindcssopen in new windowwindicssopen in new window )。而且成体接入和改造成本也是最低的,不用自己再制定命名规则和使用规范。

CSS 原子化是如何解决这些问题的

首先我们看看前文中提到组件使用 CSS 原子化是如何实现的,项目中已引入 winidicss。

20220920162709

&lt;div class=&quot;overflow-hidden mr-1&quot;&gt;
  &lt;a href=&quot;https://www.zcygov.cn/&quot; class=&quot;float-left mr-1&quot;&gt;
    &lt;img
      width=&quot;30&quot;
      src=&quot;logo.png&quot;
      alt=&quot;zcy&quot;
    /&gt;
  &lt;/a&gt;
  &lt;div class=&quot;overflow-hidden text-sm&quot;&gt;@小明 14 分钟之前&lt;/div&gt;
&lt;/div&gt;

这边如果需要 头像在右侧的,只需要将 float-left 替换成 float-right 即可。

  • 没有了命名的烦恼,按照功能命名
  • 没有相关嵌套,整体可读性加强
  • 没有重复 CSS 类, 一个功能对应一个类名,一个类名一个功能。没有重复
  • 没有新增 CSS 文件了。所有类名都是有工具库提供,项目中无需新增类了。

基本上解决了项目现有的 CSS 问题,而且 CSS 的维护性有了很大的提高,CSS 编码的成本相对于以前有明显的降低。

使用体验

  • 接入简单:只选简单几步就能直接使用。参考官网的接入说明:<https://windicss.org/integrations/webpack.html>

  • 良好的编码提示:建议安装自动补全插件( <https://marketplace.visualstudio.com/items?itemName=voorjaar.windicss-intellisense> ),对于日常使用有很大的帮助,用起来的体验也很好。

    image-20221024195725661

  • 快速的功能和类目的映射:刚开始的时候对应功能的类名不熟悉,需要使用官网( <https://windicss.org/guide/> )查询。

    image-20221024195823993

  • design token 的设计规范约束:winidicss 工具提供了 design token 的设计规范实现,再推广 CSS 的原子化之前和设计师统一好此类的规范,形成工具配置

    import { defineConfig } from &#39;windicss/helpers&#39;;
    import colors from &#39;windicss/colors&#39;;
    
    export default defineConfig({
      theme: {
        extend: {
          transitionProperty: {
            width: &#39;width&#39;,
          },
          screens: {
            sm: &#39;640px&#39;,
            md: &#39;768px&#39;,
            lg: &#39;1024px&#39;,
            xl: &#39;1280px&#39;,
          },
          colors: {
            gray: colors.coolGray,
            blue: colors.sky,
            red: colors.rose,
            pink: colors.fuchsia,
          },
          fontSize: {
            xs: &#39;.75rem&#39;,
            sm: &#39;.875rem&#39;,
            tiny: &#39;.875rem&#39;,
            base: &#39;14px&#39;,
            lg: &#39;1.125rem&#39;,
            xl: &#39;1.25rem&#39;,
            &#39;2xl&#39;: &#39;1.5rem&#39;,
            &#39;3xl&#39;: &#39;1.875rem&#39;,
            &#39;4xl&#39;: &#39;2.25rem&#39;,
            &#39;5xl&#39;: &#39;3rem&#39;,
            &#39;6xl&#39;: &#39;4rem&#39;,
            &#39;7xl&#39;: &#39;5rem&#39;,
          },
        },
      },
    });
    

    使用过程中的问题

功能类优先的 原子化 CSS 框架,带来便利的同时,也是存在一些问题的。最突出的问题就是 html 上类过多,如果要实现一个相同功能,需要复制一个很长的字符串。这个情况下的可读性和复用性是比较差的。

比如一个简单按钮的例子:涉及到背景,字体,边距,边框的设置,类就很多

&lt;button
className=&quot;bg-blue-400 text-sm text-white font-mono font-light py-2 px-4 border-2 border-rounded border-blue-200&quot;
&gt;
  Button
&lt;/button&gt;

而且相同样式复用需要整块的复制。虽然windicss提供属性化(<https://windicss.org/features/attributify.html>)的模式,Shortcuts(<https://windicss.org/features/shortcuts.html>)和指令(<https://cn.windicss.org/features/directives.html>)来解决相同功能类集合过长,过多的问题。但是从他的实现思路上看,已经不在是原子化的范畴了。

当然你也可以通过设计变量的方式来解决这个问题,但这种做法和抽象成一个组件的CSS类没有什么区别了。我们发现,原子化框架自身是无法解决这些问题的。但是我们使用CSS框架初衷是为了使CSS的维护性和复用性变高,而不是墨守成规,不变通。

const myButtonClass = &quot;bg-blue-400 text-sm text-white font-mono font-light py-2 px-4 border-2 border-rounded border-blue-200&quot;

&lt;button
className={myButtonClass}
&gt;
  Button
&lt;/button&gt;

总结

在选定一个 CSS 框架作为项目的 CSS 规范的时候,需要考虑自身项目的现状,如果项目本身 CSS 复杂度较高,相对是偏向CSS工作量较多,原子化的 CSS 是不太适合的,建议去参考像ITCSS,其实ITCSS中的 Utilities – 工具类和原子化就很类似,但是他的分层更加的复杂。随着CSS的级联层open in new window浏览器支持程度越来越好,类似ITCSS分层框架会慢慢流行起来。

原子化的 CSS 可以帮助我们的项目解决 CSS 命名,代码重复,不断膨胀的问题,但它也不是一劳永逸的,实际使用中还是会有问题,关键在与开发者自身的取舍,权衡利弊。不要一个规则用到底,遇到问题也不变通。工具和思想发明其实也是为了解决你实际的问题。你会发现 windicssopen in new window 这类工具优先的css框架,也是添加蛮多额外的功能来弥补原子化 CSS 框架的缺陷,提升自身使用体验。

参考连接

推荐阅读

所见即所得 —— HTML转图片组件开发open in new window

探索组件在线预览和调试open in new window

规范升级 NPM 包open in new window

你想知道的前后端协作规范都在这了open in new window

IntersectionObserver 实现虚拟列表初探open in new window

开源作品

  • 政采云前端小报

开源地址 www.zoo.team/openweekly/open in new window (小报官网首页有微信交流群)

  • 商品选择 sku 插件

开源地址 https://github.com/zcy-inc/skuPathFinder-back/open in new window

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 90 余个前端小伙伴,平均年龄 27 岁,近 4 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com