教你优雅使用“原子化CSS”

小伙伴们大家好,今天给大家带来一篇原子化CSS的分享,带大家了解它的编程思路以及目前比较流行的一些框架。

1. 什么是原子化CSS

1.1 基本概念

原子化CSS(Atomic CSS) 近年来热度逐渐增加,与原子化CSS相关的库在Github上也收获上万的Star。那么什么是原子化CSS呢,引用文章 Let's Define Exactly What Atomic CSS is 中定义:"原子化CSS是一种CSS架构方式,其支持小型、单一用途的类,其名称基于视觉功能。"

更加通俗的来讲,原子化CSS是一种新的CSS编程思路,它倾向于创建小巧且单一用途的class,并且以视觉效果进行命名。举个简单的例子:


<!-- 原子化类定义 -->

<style>

  .text-white { color: white; }

  .bg-black { background-color: black; }

  .text-center { text-align: center; }

</style>

<!-- 原子化类使用 -->

<div class="text-white bg-black text-center">hello Atomic CSS</div>

1.2 VS 行内样式

看到以上的示例,你可能很快就想到,直接使用行内样式不是更好吗,还省去了原子类的定义。这个问题可以从样式编写、一致性、功能、和缓存四个方面来回答。

在样式编写层面,CSS预处理和后处理器很大程度上依赖单独的样式表,原子化CSS可以充分利用Sass、Less等CSS预处理器功能进行样式的编写,同时可以借助PostCSS进一步增强CSS的功能。而对于行内样式,虽然在技术上支持使用预处理和后处理器对其进行处理,但很少有成熟的工具对此提供支持和维护。

在一致性层面,原子化CSS框架一般有预定义的设计系统,开发者仅能在设计系统中选择要设置的值。而对于行内样式或者传统CSS类定义来说,可设置的值是没有任何限制的。对于行内样式或者传统的CSS类设置来说,一个标签的字体大小可能是14px0.875rem,当产品(or 客户)说需要调小一点时,开发者A可能调整为13px,开发者B可能调整为12px。但对于原子化CSS框架来说,调小一点意味着设置的类从text-sm变为text-xs

以下为部分采用传统CSS类编写的网站样式统计数据(统计来源):

  • 掘金官网:283种背景颜色 471种字体颜色 264种字体大小
  • GitLab:1199种背景颜色 1351种字体颜色 450种字体大小
  • CSDN:585种背景颜色 1190种字体颜色 504种字体大小

在功能方面,原子化CSS本质上还是CSS类,因此支持媒体查询功能,也支持对元素的悬停、聚焦等状态进行处理,而内联样式缺少这部分的能力。

在打包方面,内联样式包含在JS文件中,样式的修改会导致整个bundle的改变,原子化CSS样式定义和JS逻辑分离,修改元素的class属性可能并不影响(在没有新CSS类的情况下)最终打包输出样式文件。

2. 谁在使用原子化CSS

截至目前已经有部分网站借鉴或者使用原子化CSS的思想重构了自己的web网站,比较知名的网站有FacebookTwitterGithubswipperjs等。根据网上公开的信息,Facebook在使用原子化CSS思想重构之后,仅登录页面的413KB样式文件,减少为整个站点的74KB

  • Facebook
  • Twitter
  • Github
  • swiperjs

3. 流行的原子化CSS框架

3.1 Tailwind CSS

3.1.1 简介

Tailwind CSS 是一个功能优先的CSS框架,它继承了诸如flex、pt-4、text-center和rotate-90这样的类,它们能直接在脚本标记语言中组合起来,构建任何的设计。截止到目前该框架在Github上拥有62.1k的Star,在2021年最受欢迎的项目中位列第六。上文中提到的 swipperjs 网站就是使用了该框架。

3.1.2 安装

本文以Create React App创建的应用结合Tailwind CSS为例,更多安装可以查看此链接。

  1. 创建项目

npx create-react-app tailwind-demo

cd tailwind-demo

  1. 安装tailwindcss相关依赖并初始化

npm install -D tailwindcss postcss autoprefixer

npx tailwindcss init -p
  1. 在根目录新建tailwind.config.js文件,并添加以下配置

module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
  1. ./src/index.css 文件顶部添加如下代码

@tailwind base;
@tailwind components;
@tailwind utilities;
  1. 现在可以在组件中使用原子类

// app.js
function App({
  return (
  <div className="w-screen h-screen flex items-center justify-center">
    <span className="text-3xl">hello tailwindcss</span>
  </div>

  );
}

export default App;
  1. 建议安装VS Code插件Tailwind CSS IntelliSense,从而在编写代码时获得语法提示。

3.1.3 功能

Tailwind CSS已经内置了较为完善的原子类和设计系统,可以直接在HTML中使用,这也是原子化CSS的核心功能。以下介绍该CSS框架的其它常见功能:

  • 悬停、焦点或者其他状态的处理。Tailwind CSS包含几乎所有的伪类和伪元素修饰符,可以与工具类随意组合使用。

<!-- 
  1. 输入框不可用时会显示浅灰的背景颜色
  2. "用户名称"后会显示红色*号
  3. 输入框聚焦时会显示不同颜色的边框
  4. 可以修改placeholder的样式
  5. 按钮悬浮时颜色变化 激活时透明度变化
-->

<form class="...">
  <div>
    <label class="...">用户编号</label>
    <input
      class="disabled:bg-gray-100 ..."
      disabled
      value="1234"
    />

  </div>
  <div class="mt-4">
    <label class="block mb-2 after:content-['*'] after:text-red-600">用户名称</label
    >

    <input
      class="border-gray-300 focus:border-blue-300 placeholder:text-gray-300 ..."
      placeholder="请输入用户名称"
    />

  </div>

  <button class="bg-gray-100 hover:bg-blue-200 active:opacity-70 ...">
    点击保存
  </button>
</form>

<!-- 6. 偶数行背景使用浅灰色,第一行使用蓝色字体,最后一行使用红色字体 -->
<ul class="...">
  <li class="even:bg-gray-50 first:text-blue-600 last:text-red-600 ...">
    111111
  </li>
  <li class="even:bg-gray-50 first:text-blue-600 last:text-red-600 ...">
    222222
  </li>
  <li class="even:bg-gray-50 first:text-blue-600 last:text-red-600 ...">
    333333
  </li>
  <li class="even:bg-gray-50 first:text-blue-600 last:text-red-600 ...">
    444444
  </li>
</ul>

<!-- 7. 首字母变大,首行加粗 -->
<p class="first-letter:text-4xl first-line:font-bold ...">
  Hello Tailwind Hello Tailwind Hello Tailwind Hello Tailwind Hello
  Tailwind Hello Tailwind Hello Tailwind Hello Tailwind
</p>
  • 响应式设计。 Tailwind CSS按照屏幕的宽度默认提供了5个断点:sm(<=640px)md(<=768px)lg(<=1024px)xl(<=1280px)2xl(1536px)Tailwind CSS中的每个原子类都可以有条件的应用于不同的断点,从而快速的构建复杂的响应式界面。
<!-- 在小屏设备使用大圆角,在大屏设备使用小圆角 -->
<div class="rounded-2xl lg:rounded-md ..."></div>
  • 深色模式。
<!-- 正常模式下背景白色,深色模式下,背景黑色 -->
<div class="bg-white dark:bg-black ..."></div>
  • 自定义主题。 Tailwind CSS内置了默认主题来帮助开发者快速的入门,但该框架支持并鼓励开发者自定义主题来构建自己的设计系统。通过修改根目录下的tailwind.config.js文件,开发者可以自定义项目的屏幕断点、调色板、间距、边框圆角等属性。

module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    // 自定义屏幕断点
    screens: {
      'sm''480px',
      'md''768px',
      'lg''976px',
      'xl''1440px',
      '2xl''1860px'
    },
    // 自定义调色板,设置之后覆盖默认调色板,可以在背景、字体、边框颜色设置中使用。
    // 原有如bg-blue-50、text-gray-50等值不可用
    colors: {
      'transparent''transparent',
      'black''#000',
      'white''#fff',
      'gray': {
        100'#f7fafc',
        900'#1a202c',
      },
    },
    // 自定义边界圆弧,设置之后覆盖默认边界圆弧。
    borderRadius: {
      DEFAULT'12px'// 默认值,使用rounded即可
      'none''0',
      'sm''4px',
    },
    // 扩展Tailwind CSS内置主题
    extend: {
      // 扩展屏幕断点的值,增加3xl大小屏幕,可以使用3xl:xxx的写法。
      screens: {
        '3xl''2560px'
      },
      // 扩展间距的值,增加128和144的大小,可以使用p-128、m-128的写法。
      // 默认情况下,该设置将影响padding、margin、width、height、maxHeight、flex-basis等值的设置。
      spacing: {
        '128''32rem',
        '144''36rem',
      },
    }
  },
  plugins: [],
}
  • 其它。
<!-- 使用任意值 -->
<div class="pt-[26px] lg:pt-[36px] color-[#eee]"></div>
<div class="bg-[url('/assets/mi.svg')]"></div>

<!-- !前缀提高属性优先级  -->
<div class="!pt-1"></div>
<button class="btn-primary">
  Save changes
</button>

/* @apply 抽取公共样式,应尽量避免使用,需要起类名并且会导致CSS生产包变大 */
.btn-primary {
    @apply py-2 px-4 bg-blue-500;
}

3.2 Windi CSS

3.2.1 简介

Windi 号称下一代功能优先的CSS框架。可以把Windi CSS看作是按需供应的Tailwind替代方案,它的出现是为了解决Tailwind v2.0随着项目变大初始化编译和热更新慢的问题。

Windi CSS完美兼容Tailwind v2.0并且拥有很多额外的炫酷功能。

该框架在Github上拥有5.8k的Star。

但需要注意的是,Tailwind v3 版本默认开启了即时引擎(JIT),实现了和Windi类似的按需加载的功能,构建速度得到了极大的提升。可能也是因为这个原因,截止目前Windi的Github仓库已经半年没有提交过代码。

3.2.2 安装

本文以Create React App创建的应用集成Windi CSS为例,更多安装可以查看此链接。

  1. 创建项目
npx create-react-app windi-demo
cd windi-demo
  1. 安装webpack扩展工具和插件
npm i react-app-rewired windicss-webpack-plugin
  1. 在根目录新建config-overrides.js,引用windi插件。该步骤本质是配置windicss-webpack-plugin插件,其它方式扩展crawebpack配置也可以。

const WindiCSSWebpackPlugin = require("windicss-webpack-plugin");

module.exports = function override(config{
  config.plugins.push(new WindiCSSWebpackPlugin({ virtualModulePath"src" }));
  return config;
};
  1. 修改package.json中的scripts命令
"scripts": {
  "start""react-app-rewired start",
  "build""react-app-rewired build",
  "test""react-app-rewired test",
  "eject""react-app-rewired eject"
}
  1. 在根目录新建windi.config.js文件,添加如下配置

import { defineConfig } from 'windicss/helpers'

export default defineConfig({
  extract: {
    include: ['**/*.{jsx,js,css,html}'],
    exclude: ['node_modules''.git'],
  },
})
  1. index.js中引入windi.css
import './windi.css' // 如果报错可以尝试 import './virtual:windi.css'
  1. 现在可以在组件中使用原子类

// app.js
function App({
  return (
  <div className="w-screen h-screen flex items-center justify-center">
    <span className="text-3xl">hello windicss</span>
  </div>

  );
}
export default App;

3.2.3 功能

Windi CSS完美兼容Tailwind v2.0,包含了其所有的工具类和几乎所有的功能。同时还新增了许多额外的特性。

  • 自动值推导。Tailwind的任意值功能类似,不过其书写更加的方便。
<!-- 可以不用加中括号 -->
<div class="text-20px text-hex-1e1e1e">自动值推导</div>
  • 修饰组。
<!-- hover之后,字体颜色加深并且字体加粗 -->
<div className="text-blue-700 hover:(text-blue-900 font-bold)">修饰组</div>
  • 属性化模式。 基于这个特性,可以像在html属性中编写windi类。
<!-- 修改windi.config.js文件,添加attributify: true配置 -->
<button 
  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="light"
  p="y-2 x-4"
  border="2 rounded blue-200"
  >

  Button
</button>

<!-- 为了避免属性冲突,也可以自定义前缀,修改attributify属性值为{ prefix: 'mi-' } -->
<button 
  mi-bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  mi-text="sm white"
  mi-font="light"
  mi-p="y-2 x-4"
  mi-border="2 rounded blue-200"
>

  Button
</button>
  • 可视化分析器。 在项目根目录下执行npx windicss-analysis可以生成项目的分析报告。

3.3 UnoCSS

3.3.1 简介

UnoCSS 是具有高性能且极具灵活性的即时原子化CSS引擎。它是一个引擎而非框架,因为它并没有提供核心工具类,所有功能可以通过预设和内联配置提供。UnoCSS的主要目标是直观性和可定制性,它可以开发者在极短时间内定义自己的CSS工具类。尽管UnoCSS目前还处于beta阶段,但其在 Github 上的 Star数已经达到 7.5k

3.3.2 安装

由于UnoCSS对于Vite有更好的支持,本文将以Vite初始化React项目并集成UnoCSS为例,更多安装可以查看此链接。

  1. 创建项目
npm create vite@latest unocss-demo -- --template react
cd unocss-demo
npm i   
  1. 安装UnoCSS插件
npm i unocss
  1. vite.config.js中引用插件
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import Unocss from "unocss/vite";

export default defineConfig({
  plugins: [
    react(),
    Unocss({
      plugins: [],
    }),
  ],
});
  1. src/main.jsx中添加如下引用
import 'uno.css'
  1. 现在可以在组件中使用原子类
// app.js
function App() {
  return (
  <div className="w-screen h-screen flex items-center justify-center">
    <span className="text-3xl">hello tailwindcss</span>
  </div>
  );
}

export default App;

3.3.3 功能

在了解该框架功能之前,建议读者先阅读文章重新构想原子化 CSS,了解作者对原子化CSS的认识和编写该框架的心路历程。

UnoCSS本身不具有原子类和设计系统,所有的功能都是通过预设来实现。其默认的预设包含了上述两种框架的原子类和设计系统,因此在使用方面的功能与上述两种框架类似。其主要特点是可以快速的构建自己的原子类和设计系统。由于笔者没有深入的体验该框架,因此仅作简单介绍,更多的规则预设可以查看官方文档。

// vite.config.js
export default defineConfig({
  plugins: [
    react(),
    Unocss({
      rules: [
        // m-1 => margin: 1px; m-100 => margin: 100px
        [/^m-(\d+)$/, ([, d]) => ({ margin`${d}px` })],
        // p-1 => padding: 1px; m-100 => padding: 100px
        [/^p-(\d+)$/, match => ({ padding`${match[1]}px` })],
      ],
    }),
  ],
});

总结

4.1 问题及思考

如何实现按页面分包?在使用原子化CSS框架之后,整个项目的CSS并不会很大,即便是Facebook重构完成后,也只需要70kb,因此分包意义不大。

JSX中大量的类名增加了JS文件的体积?相比于自定义CSS类名,使用原子化CSS类名确实会让class字符串变长,但由于都是重复的字符串,在经过GZip后影响微乎其微。

多主题切换是否支持?借助CSS变量和CSS原子化框架提供的主题配置,可以实现多主题的切换。详细实现思路可以参考文章使用 Tailwind 实现网页多主题。

浏览器兼容性?以上三种框架中的大部分功能都适用于所有现代浏览器,仅有部分未被所有浏览器支持的前沿功能的API存在兼容性问题,例如:focus-visible伪类和backdrop-filter

自定义样式破坏约束?以上三种框架都支持自定义样式,这一定程度上破坏了约束,但确极大的提高了框架的实用性,因此得到了各个框架的支持。

4.2 使用感受

为了深度的体验原子化CSS框架,我使用windi框架重构了个人的博客项目,包括展示页面(Next.js)和后台管理系统(vue3),整体的开发体验十分的良好。对于不需要再起类名这一点真的很香。而且由于基本不需要自己写CSS类,几乎所有的样式文件都可以删除,项目干净了很多。

另外也有两点不太习惯,一是windi的设计系统的字体大小、行高、边距都是以rem为单位,而原项目中一般都是以px为单位。二是定位问题时,即便在网页上找到了问题节点,也无法通过类名直接在项目中全局搜索。

4.3 优缺点及适用场景

深度体验之后,个人觉得原子化CSS主要优势有:

  • 不用浪费精力起类名;
  • CSS文件将不会无限增长,相比于以往的方式CSS打包结果更小;
  • 可以很好的避免历史样式的堆积,不存在历史样式类不敢删除的问题;
  • 天然的支持组件间的样式隔离,没有自定义的class也就无需担心组件之间样式的影响。

其主要缺点有:

  • 前期有一定的学习成本,需要不断的翻看样式对应的类名;
  • 无法通过类名在项目中直接定位源码位置;
  • 类名横向平铺无法快速定位要修改的内容。

基于以上原子化CSS的优缺点,个人觉得原子化CSS的适用场景有:

  • 个人项目,对样式没有太高的要求,借助设计系统,可以提升页面显示效果,毕竟不用起类名真的很香。

  • 大型项目,有较为完整且稳定的视觉规范,能够很好的搭建设计系统。可以很好的解决历史样式的堆积问题并且有效的减少CSS的体积。

最后

觉得本文有用的小伙伴,可以帮忙点个“在看”,让更多的朋友看到咱们的文章。

再给“前端面试题宝典”的辅导服务打下广告,目前有面试全流程辅导简历指导模拟面试零基础辅导付费咨询等增值服务,感兴趣的伙伴可以联系小助手(微信号:interview-fe)了解详情哦~