浅谈原子化 CSS

浅谈原子化 CSS

Tags
CSS
SaaS
Tailwind CSS
Published
June 14, 2024
Author
因为工作原因,笔者有着几年的开发维护展示类型网站的经历,这里和大家分享探讨下我对原子化 CSS 的理解和使用。

什么是原子化 CSS

网上相关的介绍非常的多,这里就简单带过下,简单来说原子化 CSS 是一种编写 CSS 的方法,每个类名只有一个明确的作用,从而避免重复和冗余的代码。这种方法提高了 CSS 代码的可重用性和可维护性,但同时也可能增加 HTML 结构内部类的使用。

原子化 CSS 的使用场景

在开发 Admin 或 Dashboard 这类后台项目时,一般需要一套完整的组件库,开发过程则是以交互功能为主,堆砌组件完成,一般不需要写很多的样式。但在开发官网等一些偏内容展示的网站时,往往更注重样式的还原和页面响应式兼容。
这类网站可能极少有通用的复杂控件,更多的是对样式的规范,风格的统一,并且以内容驱动,更新迭代速度很快,并需要长期的维护。
在这种场景下,原子化 CSS 就发挥了很大的作用。对于需要规范样式、持续更新迭代的展示类网站,原子化 CSS 使得每个样式都有唯一对应的一处定义,有利于控制和保持整体风格的一致性。同时还节省了重复定义样式的时间,有效提升开发和维护效率。

如何在 Scss 中使用原子化 CSS

在没有了解专门的原子化 CSS 库之前,我是通过 Scss 的变量和循环来实现一些常用的原子化 CSS。
这里我们以边距来举个例子:
$spacing-shortcuts: ( "margin": "m", "padding": "p", ) !default; $spacing-directions: ( "top": "t", "right": "r", "bottom": "b", "left": "l", ) !default; $spacing-horizontal: "x" !default; $spacing-vertical: "y" !default; $spacing-values: ( "0": 0, "1": 0.25rem, "2": 0.5rem, "3": 0.75rem, "4": 1rem, "5": 1.5rem, "6": 3rem, "auto": auto, ) !default; @each $property, $shortcut in $spacing-shortcuts { @each $name, $value in $spacing-values { // All directions .#{$shortcut}-#{$name} { #{$property}: $value !important; } // Cardinal directions @each $direction, $suffix in $spacing-directions { .#{$shortcut}#{$suffix}-#{$name} { #{$property}-#{$direction}: $value !important; } } // Horizontal axis @if $spacing-horizontal != null { .#{$shortcut}#{$spacing-horizontal}-#{$name} { #{$property}-left: $value !important; #{$property}-right: $value !important; } } // Vertical axis @if $spacing-vertical != null { .#{$shortcut}#{$spacing-vertical}-#{$name} { #{$property}-top: $value !important; #{$property}-bottom: $value !important; } } } }
Suffix
Value
*-0
0
*-1
0.25rem
*-2
0.5rem
*-3
0.75rem
*-4
1rem
*-5
1.5rem
*-6
3rem
还可以再举个 position 的例子:
$positions: absolute fixed relative static sticky; @each $position in $positions { .position-#{$position} { position: $position !important; } }
对于响应式网页,一般还需要媒体查询来为不同尺寸的设备写不同的样式,我们可以使用 Scss 的 mixin 功能实现一组断点来规范和统一管理媒体查询。
// Responsiveness // The container horizontal gap, which acts as the offset for breakpoints $gap: 32px; // 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16 $tablet: 769px; // 960px container + 4rem $desktop: 960px + (2 * $gap); // 1152px container + 4rem $widescreen: 1152px + (2 * $gap); // 1344px container + 4rem $fullhd: 1344px + (2 * $gap); @mixin from($device) { @media screen and (min-width: $device) { @content; } } @mixin until($device) { @media screen and (max-width: #{$device - 1px}) { @content; } } @mixin mobile { @media screen and (max-width: #{$tablet - 1px}) { @content; } } @mixin tablet { @media screen and (min-width: $tablet), print { @content; } } @mixin tablet-only { @media screen and (min-width: $tablet) and (max-width: #{$desktop - 1px}) { @content; } } @mixin touch { @media screen and (max-width: #{$desktop - 1px}) { @content; } } @mixin desktop { @media screen and (min-width: $desktop) { @content; } } @mixin desktop-only { @media screen and (min-width: $desktop) and (max-width: #{$widescreen - 1px}) { @content; } } @mixin until-widescreen { @media screen and (max-width: #{$widescreen - 1px}) { @content; } } @mixin widescreen { @media screen and (min-width: $widescreen) { @content; } } @mixin widescreen-only { @media screen and (min-width: $widescreen) and (max-width: #{$fullhd - 1px}) { @content; } } @mixin until-fullhd { @media screen and (max-width: #{$fullhd - 1px}) { @content; } } @mixin fullhd { @media screen and (min-width: $fullhd) { @content; } }
在全局引入后,我们即可在任意样式文件中使用,类似这样:
.card { padding: 32px; @include mobile { padding: 16px; } }
再搭配上 PurgeCSS,打包时自动删除没有使用到的样式,这样我们就能通过 Scss 实现一个简易的原子化 CSS 项目。
但手动去书写和管理这些样式还是不太方便,使用相关的原子化 CSS 库是更好的选择。

Tailwind CSS 介绍

Tailwind CSS 是目前最流行的原子化 CSS 框架,其旨在通过实用程序优先的方式来快速构建现代化的响应式网站。以下是对 Tailwind CSS 的简要介绍:
  • 实用程序优先
    • Tailwind CSS 提供了一组低级别的实用工具类,这些类可以直接在 HTML 标记中使用,而无需定义自定义 CSS 样式。这样可以快速、灵活地构建复杂的设计。
  • 高度可定制
    • Tailwind CSS 带有一个配置文件(tailwind.config.js),允许开发者根据项目需求进行深度定制。可以调整颜色、间距、字体等,使之完全符合设计规范。
  • 响应式设计
    • 内置响应式设计支持,通过类名前缀(如 sm:md:lg: 等)可以轻松地实现不同屏幕尺寸下的样式变化。
  • 与现有工具兼容
    • Tailwind CSS 可以与大多数前端构建工具(如 Webpack、PostCSS)无缝集成,也能与现代前端框架(如 React、Vue、Angular)一起使用。
  • 极小的打包大小
    • Tailwind CSS 配合 PurgeCSS 使用,可以在生产环境中移除未使用的 CSS 类,大大减少最终打包文件的大小。

Tailwind CSS 使用

如果你项目使用的是 Nuxt,则可以使用使用官方提供的插件快速配置。对于其他类型项目,Tailwind 的引入和配置方法可能会不太一样,具体请参考官方文档

常用样式和断点配置

Tailwind 作为一个原子化 CSS 框架,常用的样式都已经有着默认配置,并且可以自定义扩展。我们可以拿刚才 Scss 里举的例子,看看在 Tailwind 中如何实现一样的效果。
对于边距:
如果默认边距配置满足不了项目需求,简单配置即可扩展:
对于 position:
对于断点配置:
Tailwind 默认的断点配置和我们原本的配置不太一样,我们可能通过配置文件覆盖或者修改其默认配置,类似这样:
export default <Partial<Config>>{ theme: { screens: { 'mobile': { max: '767px' }, 'tablet': { min: '768px' }, 'tablet-only': { min: '768px', max: '1023px' }, 'touch': { max: '1023px' }, 'desktop': { min: '1024px' }, 'desktop-only': { min: '1024px', max: '1215px' }, 'until-widescreen': { max: '1215px' }, 'widescreen': { min: '1216px' }, 'widescreen-only': { min: '1216px', max: '1407px' }, 'until-fullhd': { max: '1407px' }, 'fullhd': { min: '1408px' }, }, }, }

组合或自定义样式

如果需要复用样式,或者在 Tailwind 的基础上搭建自己的组件库,可以通过扩展 Components 来实现,比如自定义一组按钮样式:
export default <Partial<Config>>{ plugins: [ plugin(({ addComponents }) => { addComponents({ '.btn': { '@apply select-none inline-flex gap-2 items-center justify-center shrink-0 flex-wrap text-base shadow-none h-12 px-4 text-center min-w-40 font-medium rounded-lg transition-all': {}, '&.btn-sm': { '@apply text-sm h-10 min-h-10 min-w-0': {}, }, '&:disabled, &.btn-disabled, &[disabled]': { '@apply pointer-events-none opacity-20': {}, }, '&.btn-loading': { '@apply pointer-events-none': {}, '&:before': { '@apply content-[\'\'] aspect-square w-6 bg-current [mask-image:url(\'/images/loading.svg\')] [mask-size:100%] [mask-repeat:no-repeat] [mask-position:center]': {}, }, }, '&.btn-primary': { '@apply text-white bg-primary hover:bg-primary/85': {}, }, '&.btn-secondary': { '@apply text-white bg-emphasis hover:bg-emphasis/85': {}, }, }, }) }), ], }
我们就能在项目中像使用普通 Tailwind class 一样的使用它
<button class="btn btn-primary"> submit </button>

使用小技巧

  • 对于一些不太方便使用 class 的样式,可以混合常规 CSS 使用。比如伪类的样式,我们可以在使用常规 CSS 的同时,搭配 apply、screen、theme 等来使用。
    • .hr { @apply overflow-visible relative my-11 w-full h-px border-none bg-[#dcdfe6]; &::after { content: ''; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); padding: 0 8px; font-size: 14px; color: #5b5b5b; background-color: #fff; } @screen touch { &::after { background-color: theme('colors.body'); } } }
  • 需要合理的区分全局样式和局部样式,一些仅在某些组件中使用的样式组合,样式应该在组件中定义,而不是配置到 tailwind.config 中,这样打包时能够合理的分块,仅在组件被使用时才加载对于的样式。
  • 一些比较复杂的 HTML 结构,直接将样式写到 HTML 中可能会比较杂乱,并且不方便后续维护,这种情况可以搭配 Vue 的 CSS module 功能,组合 Tailwind 样式,并且无须担心样式污染问题。
    • <script setup lang="ts"> const { t } = useI18n() const hrText = computed(() => `'${t('form.hr')}'`) </script> <template> <hr :class="$style.hr"> </template> <style module> .hr { @apply overflow-visible relative my-11 w-full h-px border-none bg-[#dcdfe6]; &::after { content: v-bind(hrText); position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); padding: 0 8px; font-size: 14px; color: #5b5b5b; background-color: #fff; } @screen touch { &::after { background-color: theme('colors.body'); } } } </style>

如何在现有项目中无痛引入 Tailwind CSS

现有的项目可能已经在使用着其他的组件或者 UI 库,在引入 Tailwind 上,我们需要关注很多问题,让我们来一一解决。

解决样式污染

对于样式污染问题,我们首先要了解 Tailwind 样式的组成部分,在初始配置时,我们通常会引入下列指令到全局样式中:
/** * This injects Tailwind's base styles and any base styles registered by * plugins. */ @tailwind base; /** * This injects Tailwind's component classes and any component classes * registered by plugins. */ @tailwind components; /** * This injects Tailwind's utility classes and any utility classes registered * by plugins. */ @tailwind utilities;
查看官方的注解,我们对于各个指令的作用也能略知一二。其中,最可能影响我们现有样式的是 base,因为一些基础样式的预设可能和我们原本使用的组件或 UI 库有冲突,导致实际显示效果上的偏差。此时我们可以选择禁用掉 Tailwind 的预设样式。
缺少预设样式可能导致我们在使用 Tailwind class 写一些样式时麻烦一些,比如 border 等效果,但仍然是可以使用的。
其次是 Tailwind class 的一些 class name 可能和项目现有的 class name 有重叠,也可能会导致样式污染,比如我们项目有使用 bulma 这类的 UI 库。我们可以通过配置 class 前缀来避免这个问题。

与预处理器一起使用

我们的项目可能使用着 Sass、Less 等预处理器,怎么使 Tailwind 能与其结合使用是需要考虑的问题。
由于 Tailwind 是一个 PostCSS 插件,因此没有什么可以阻止我们将它与 Sass、Less、Stylus 或其他预处理器一起使用,就像使用 Autoprefixer 等其他 PostCSS 插件一样。
针对不同的情况官方有给出使用说明:

打包构建

你可能会担心预设这么多的样式,引入 Tailwind 后项目打包大小会变的很大,正如我们前面介绍的一样,Tailwind CSS 在生产环境下是按需生成的,其能配合 PurgeCSS 使用,可以在生产环境中移除未使用的 CSS 类,大大减少最终打包文件的大小。

结语

早期的 UI 框架,如 Bootstrap 和 layui,都有着类似的设计理念。你会发现,这些众多的 UI 框架中,为了高效维护,一般都会有一套自己的逻辑去实现“工具”。在我看来,Tailwind CSS 不仅仅是一个原子化 CSS 框架,更重要的是你能够通过简单的配置构建出一套自己的“工具”,并且维护和修改起来也相对简单。
由于 Tailwind CSS 具有全局按需加载和局部组合定制的特性,即使项目不需要过多的自定义样式,也可以使用 Tailwind CSS。它就像一个便捷的工具,即使你不使用它,也不会对项目产生影响。