Atomic CSS 理念分析,及相关框架 TailWindCSS 学习

1,298 阅读8分钟

CSS 发展趋势

比如 BEM、OOCSS、Css in js、Js in css、Utility first、Atomic css 等

  • BEM(Block-Element-Modifier):一种css class 命名方法,通过链接符号来区分含义。
  • OOCSS(Object-Oriented-CSS):面向对象的css,通过抽象的概念抽离css类,带来高复用。有点像 html 的表格标签,比如 table、caption、thead、tbody、tfoot等,形成一种以功能抽象的命名规范。
  • Css in js:通过 js 和 css 的耦合,更容易追溯,并且通过锁定作用域避免污染,更方便使用 js 的变量、模块、tree-shaking等。
  • Js in css:通过 CSS Houdini 实现 css 中使用 js 脚本。
  • Utility first:抽象大量的工具类,声明式理念以减少编写 css 为基础,以工具的方式管理使用 css,规范化、统一管理、高复用。
  • Atomic css:极端版 Utility first,除了提供工具类外,还会提供更低细粒度的累,这样可以解决 Utility first 无法覆盖场景的问题。

Atomic CSS 理念实质

通过大量的类和 css 做映射,把 css 的管理变为 css 类的管理,只需要操纵css类,就能得到相关的收益,比如 tree-sharking、css 预载、定制化、响应式设计等。

CSS 原子化相关框架及设计思维

ACSS

  • 基于原子化思想,提供大量类工具函数,下面是使用的几个特性demo

    // Color
    <div class="Bgc(#0280ae.5) C(#fff) P(20px)">
        Lorem ipsum
    </div>
    
    
    // Variables
    'custom': {
        'brandColor': '#0280ae',
        'columnWidth': '20px'
    }
    <div class="Pos(a) Bgc(brandColor) W(columnWidth) H(90px)"></div>
    <div class="C(brandColor) BdB Bdc(brandColor) Mstart(columnWidth) P(10px)">
         Lorem ipsum
    </div>
    
    
    // Responsive web design (RWD)
    'breakPoints': {
        'sm': '@media screen and (min-width:700px)'
    }   
    <div class="Bgc(#0280ae.5) H(90px) D(ib)--sm W(25%)--sm"></div><!--
    --><div class="Bgc(#0280ae) H(90px) D(ib)--sm W(25%)--sm"></div><!--
    --><div class="Bgc(#0280ae.5) H(90px) D(ib)--sm W(25%)--sm"></div><!--
    --><div class="Bgc(#0280ae) H(90px) D(ib)--sm W(25%)--sm"></div>
    

TailWind CSS(社区比较推荐,及个人比较推荐)

推荐理由
1. 设计想法基于工具类延伸,解决了工具理念的缺点
2. 整体框架设计,带来很好的拓展性
3. 主流编辑器都有补全插件,减少使用成本
简单看一些daemo
  • 响应式

    默认的配置的设备分辨率方案

    断点前缀最小宽度CSS
    sm640px@media (min-width: 640px) { ... }
    md768px@media (min-width: 768px) { ... }
    lg1024px@media (min-width: 1024px) { ... }
    xl1280px@media (min-width: 1280px) { ... }
    2xl1536px@media (min-width: 1536px) { ... }
    <!-- Width of 16 by default, 32 on medium screens, and 48 on large screens -->
    <img class="w-16 md:w-32 lg:w-48" src="...">
    
  • 主题色

    <div class="bg-white dark:bg-gray-800">
      <h1 class="text-gray-900 dark:text-white">Dark mode is here!</h1>
      <p class="text-gray-600 dark:text-gray-300">
        Lorem ipsum...
      </p>
    </div>
    
  • 交互状态

    常用的都覆盖了,比如 hover、focus、active 等

    <form>
      <input class="border border-transparent focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent ...">
      <button class="bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-600 focus:ring-opacity-50 ...">
        Sign up
      </button>
    </form>
    

    除了基本之外,还有一些内部支持的,比如 checked,开启后,会让一个单选或复选框应用样式

    <input type="checkbox" class="appearance-none checked:bg-blue-600 checked:border-transparent ...">
    
    
  • 基础样式

    可以自定义基础样式,满足视觉统一问题。

    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    @layer base {
      h1 {
        @apply text-2xl;
      }
      h2 {
        @apply text-xl;
      }
    }
    
  • 功能类

    这个其实和大多数框架差不多,都是保持“单一原则”、“组合应用”的几个点,设计功能类,就不过多介绍了,感兴趣可以参考社区开源框架。因为如果团队中落地,肯定是要重新设计过的。

    <!-- Using utilities -->
    <button class="py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-green-500 hover:bg-green-700">
      Click me
    </button>
    
    <!-- Extracting classes using @apply -->
    <button class="btn btn-green">
      Button
    </button>
    
    <style>
      .btn {
        @apply py-2 px-4 font-semibold rounded-lg shadow-md;
      }
      .btn-green {
        @apply text-white bg-green-500 hover:bg-green-700;
      }
    </style>
    
  • 函数与指令

    因为原子化理念,是种细化思想,所以会有大量的类,那么如果管理这些类,并且使开发者高效的使用。答案是建立一套函数和指令的形式,把相关操作的细节封装,提高开发者的效率和易用性。

    • @tailwind - 引入已封装的类
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    @tailwind screens;
    
    • @apply - 内联功能类,组合使用。
    .btn {
      @apply font-bold py-2 px-4 rounded;
    }
    .btn-blue {
      @apply bg-blue-500 hover:bg-blue-700 text-white;
    }
    
    • @layout - 设置封装的类
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    @layer base {
      h1 {
        @apply text-2xl;
      }
      h2 {
        @apply text-xl;
      }
    }
    
    @layer components {
      .btn-blue {
        @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
      }
    }
    
    • @variants - 变体声明
    @variants focus, hover {
      .rotate-0 {
        transform: rotate(0deg);
      }
      .rotate-90 {
        transform: rotate(90deg);
      }
    }
    
    /* output */
    .rotate-0 {
      transform: rotate(0deg);
    }
    .rotate-90 {
      transform: rotate(90deg);
    }
    
    .focus\:rotate-0:focus {
      transform: rotate(0deg);
    }
    .focus\:rotate-90:focus {
      transform: rotate(90deg);
    }
    
    .hover\:rotate-0:hover {
      transform: rotate(0deg);
    }
    .hover\:rotate-90:hover {
      transform: rotate(90deg);
    }
    
    • @responsive - 响应式声明
    @responsive {
      .bg-gradient-brand {
        background-image: linear-gradient(blue, green);
      }
    }
    
    /* output */
    .bg-gradient-brand {
      background-image: linear-gradient(blue, green);
    }
    
    /* ... */
    
    @media (min-width: 640px) {
      .sm\:bg-gradient-brand {
        background-image: linear-gradient(blue, green);
      }
      /* ... */
    }
    
    @media  (min-width: 768px) {
      .md\:bg-gradient-brand {
        background-image: linear-gradient(blue, green);
      }
      /* ... */
    }
    
    @media (min-width: 1024px) {
      .lg\:bg-gradient-brand {
        background-image: linear-gradient(blue, green);
      }
      /* ... */
    }
    
    @media (min-width: 1280px) {
      .xl\:bg-gradient-brand {
        background-image: linear-gradient(blue, green);
      }
      /* ... */
    }
    
    • @screen - 引用断点的媒体查询
    /* normal responsive screen css */
    @media (min-width: 640px) {
      /* ... */
    }
    
    @screen sm {
      /* ... */
    }
    
    • theme() - 获取主题配置值
    .content-area {
      height: calc(100vh - theme('spacing.12'));
    }
    .content-new-area {
      height: calc(100vh - theme('spacing[2.5]'));
    }
    .btn-blue {
      background-color: theme('colors.blue.500');
    }
    

CSS 原子化框架使用案例

原子化其实很早就提出了,大概在18年,但是缺少大公司实践背书,所以社区热度比较低。由于react先天性对css的缺乏设计,所以更多的css理念实践在react的项目中会有更多收益,比如 css in js、utility first等。

*2020.04.27 - Facebook 利用 Atomic CSS 重构案例

Facebook 团队基于 Atomic CSS 的理念融合 css-in-js,然后重构了 Facebook 的项目,将 css 体积减少了80%(413kb -> 74kb)。有了国际大厂的背书,使的 Atomic Css 再次进入人们的视野,如果有玩过 vite,会发现社区模板,有很多都是标配 TailwindCss。

基于这次案例,挑了些优化进行分析解读。

分析优化:

  1. 体积优化,减少 80%
    • 原因1:基于类的管理,使的css可以高度复用。通过对类的 tree-sharking 将未使用的css剔除。
    • 原因2::类与css的映射足够细,最优的情况是只要加载一个基础css。
  2. 响应式更可控
    • 基于 utility first 的工具统一理念,约束全局响应式规范。
  3. 多端复用
    • 对于移动端,一般都使用 rem 单位,通过内部只转换,可以使的一套css配置,适配多端。
  4. 主题色、暗黑模式
    • 通过直接修改类的映射,可以非常快速全局变化。
  5. 提高项目质量,降低失误风险
    • 因为是通过类设置样式,只要你删除或修改html模板,类会一起变化,避免修改html时,忘记修改css,导致视觉事故。

分析优缺点

特性优点缺点
基于工具化的理念基于工具化的理念,项目后期维护迭代,样式的增量都是逐步放缓的。入手成本较高,首先得知道有什么工具类,才能学会用
约束全局规范使得整体视觉设计一致,提供多端适配能力,后期维护成本减低。限制了个性化定制样式,花里胡哨的样式会增大样式冗余,强行抽离工具,反而增大开发成本。
css类的形式使用降低同时维护样式和模板的隐患,解决开发命名难的问题。类名增多,主观视觉上比较难看
清除无用样式减少打包体积无法使用动态拼接类名
提供了大量的基础类能通过组合的形式满足业务场景,避免编写大量工具类因为过度细分,导致Chrome检查样式,比较麻烦。(可以通过devtools可以解决)

TailwindCSS 基本实现思路

简述

  • 本质上 TailwindCSS 是一款 Postcss 的插件(Postcss 是一款用于css转换的工具)
  • 因为是 Postcss 插件,底层依赖 Postcss ,所以你项目能支持到多少版本的 Postcss 直接影响到你使用 tailwindcss 的大版本。截止2021年6月,目前有 v0、v1、v2 三个版本,个人推荐v2,原因是灵活的拓展性。

实现思路

围绕着几大特性,来看实现,避免枯燥,对特性进行简单提要,不进行源码解读。

  • 按需打包

    1. 借助 PurgeCSS 对字符串进行匹配,通过一个正则 /[^<>"'`\s]*[^<>"'`\s:]/g 进行筛查

    2. 移除自己生成未使用的工具类,和通过语法糖引入的样式,比如 @layout

    3. 大体上是借助 PurgeCSS 的能力,所以也想外直接暴露了 PurgeCSS,开发者可以通过学习 PurgeCSS 直接操纵匹配机制。

image.png

  • 自定义配置

    1. 源码目录中,有个stubs文件夹,有三个文件,其中有个分别是defaultConfig.stub.jsdefaultPostCssConfig.stub.jssimpleConfig.stub.js

      export const defaultConfigStubFile = path.resolve(__dirname, '../stubs/defaultConfig.stub.js')
      export const simpleConfigStubFile = path.resolve(__dirname, '../stubs/simpleConfig.stub.js')
      export const defaultPostCssConfigStubFile = path.resolve(
        __dirname,
        '../stubs/defaultPostCssConfig.stub.js'
      )
      
    2. 统一存储所有相关配置,然后完全暴露给调用方。

  • 功能类(utility-first)

    1. 根据样式功能性进行分类

    2. 然后利用自身的函数和指令能力,组合输出对应类映射

    3. 交由编译器做对应输出转换

    4. 源码目录 src/plugins

image.png

  • 指令和函数的语法糖

    这里编写了相关指令和函数的规则,然后通过 postcss 的编译,进行校验和处理。

image.png

其他

  • 如果对 Postcss 插件编写有兴趣的,可以看源码,整个目录结构非常清晰,并且入口文件的编写,都是非常标准的 Postcss 插件风格,学习过后可以提升一定的编码质量。
  • 对于看开源代码,主要还是有目的性的看,第一手段是通过 commit 和目录结构找到模块,然后通过分析依赖和相关函数命名,梳理大致的流程,再回顾问题,细化到对应细节实现。

总结

从宏到微的形式,逐步了解学习理念和框架,理念这种东西很玄学,没办法保证大家都认同,算是种拓展思考,也是不错的。如果有分析错误,或者有其他错误的地方,可以及时指出,Peace and Love,我向你敬礼。