Skip to content

UnoCSS 原子化样式插件

介绍

UnoCSS 是一个即时按需的原子化 CSS 引擎,为 UniApp 项目提供快速、灵活的样式解决方案。通过 @uni-helper/unocss-preset-uni 预设,UnoCSS 完美适配 UniApp 的多端开发需求,支持 rpx 单位转换、属性化模式、图标系统等特性。

核心特性:

  • 即时按需生成 - 仅生成实际使用的 CSS,极致优化包体积
  • 原子化类名 - 使用简短的类名快速构建 UI,如 flexmt-4text-primary
  • UniApp 适配 - 支持 rpx 单位、多端兼容、条件编译
  • 属性化模式 - 支持 text="red" 这样的属性写法
  • 图标系统 - 集成 Iconify 图标库,按需加载
  • 自定义指令 - 支持 @apply@screen 等 CSS 指令
  • 分组语法 - 支持 hover:(bg-gray text-white) 分组写法

基本用法

插件配置

vite/plugins/unocss.ts 中配置:

typescript
/**
 * UnoCSS 插件配置
 * 原子化 CSS 引擎,提供快速、灵活的样式解决方案
 */
export default async () => {
  const UnoCSS = (await import('unocss/vite')).default
  return UnoCSS()
}

UnoCSS 配置文件

在项目根目录创建 uno.config.ts

typescript
import { presetUni } from '@uni-helper/unocss-preset-uni'
import {
  defineConfig,
  presetAttributify,
  presetIcons,
  transformerDirectives,
  transformerVariantGroup,
} from 'unocss'

export default defineConfig({
  presets: [
    // UniApp 预设
    presetUni({
      attributify: {
        prefixedOnly: true,
      },
    }),
    // 图标预设
    presetIcons({
      scale: 1.2,
      warn: true,
      extraProperties: {
        display: 'inline-block',
        'vertical-align': 'middle',
      },
    }),
    // 属性化预设
    presetAttributify(),
  ],
  transformers: [
    // 指令转换器
    transformerDirectives(),
    // 分组转换器
    transformerVariantGroup(),
  ],
  shortcuts: [
    {
      center: 'flex justify-center items-center',
    },
  ],
  safelist: [],
  rules: [
    ['p-safe', {
      padding: 'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)',
    }],
    ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }],
    ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }],
  ],
  theme: {
    colors: {
      primary: 'var(--wot-color-theme,#0957DE)',
    },
    fontSize: {
      '2xs': ['20rpx', '28rpx'],
      '3xs': ['18rpx', '26rpx'],
    },
  },
})

预设配置

presetUni

UniApp 专用预设,提供 rpx 单位支持和多端兼容:

typescript
presetUni({
  attributify: {
    prefixedOnly: true,  // 仅使用带前缀的属性
  },
})

功能说明:

功能说明
rpx 支持自动将数值转换为 rpx 单位
多端兼容适配 H5、小程序、App 等平台
响应式支持 UniApp 的响应式断点

presetIcons

图标预设,集成 Iconify 图标库:

typescript
presetIcons({
  scale: 1.2,        // 图标缩放比例
  warn: true,        // 未找到图标时警告
  extraProperties: {
    display: 'inline-block',
    'vertical-align': 'middle',
  },
})

使用图标:

vue
<template>
  <!-- Carbon 图标集 -->
  <view class="i-carbon-sun" />
  <view class="i-carbon-moon" />

  <!-- Material Design 图标集 -->
  <view class="i-mdi-home" />
  <view class="i-mdi-account" />

  <!-- 带颜色和尺寸 -->
  <view class="i-carbon-settings text-24 text-primary" />
</template>

presetAttributify

属性化模式预设,支持属性写法:

typescript
presetAttributify()

使用示例:

vue
<template>
  <!-- 传统类名写法 -->
  <view class="flex items-center justify-between p-4 bg-white">
    内容
  </view>

  <!-- 属性化写法 -->
  <view flex items-center justify-between p-4 bg-white>
    内容
  </view>

  <!-- 混合写法 -->
  <view class="container" flex items-center>
    内容
  </view>
</template>

转换器配置

transformerDirectives

支持 CSS 指令,如 @apply@screentheme()

typescript
transformerDirectives()

使用示例:

vue
<style lang="scss" scoped>
.button {
  @apply flex items-center justify-center;
  @apply px-4 py-2 rounded-lg;
  @apply bg-primary text-white;

  &:hover {
    @apply bg-primary/80;
  }
}

.container {
  padding: theme('spacing.4');
  color: theme('colors.primary');
}
</style>

transformerVariantGroup

支持分组语法,减少重复前缀:

typescript
transformerVariantGroup()

使用示例:

vue
<template>
  <!-- 不使用分组 -->
  <view class="hover:bg-gray-400 hover:font-medium hover:text-white">
    悬停效果
  </view>

  <!-- 使用分组 -->
  <view class="hover:(bg-gray-400 font-medium text-white)">
    悬停效果
  </view>

  <!-- 复杂分组 -->
  <view class="font-(light mono) text-(center lg)">
    组合样式
  </view>
</template>

快捷方式

内置快捷方式

typescript
shortcuts: [
  {
    center: 'flex justify-center items-center',
  },
],

使用示例:

vue
<template>
  <!-- 使用快捷方式 -->
  <view class="center h-100">
    居中内容
  </view>

  <!-- 等同于 -->
  <view class="flex justify-center items-center h-100">
    居中内容
  </view>
</template>

自定义快捷方式

typescript
shortcuts: [
  // 对象形式
  {
    center: 'flex justify-center items-center',
    btn: 'px-4 py-2 rounded-lg cursor-pointer',
    'btn-primary': 'btn bg-primary text-white',
    'btn-outline': 'btn border border-primary text-primary',
  },
  // 动态快捷方式
  [/^btn-(.*)$/, ([, c]) => `bg-${c} text-white px-4 py-2 rounded-lg`],
],

自定义规则

安全区域规则

项目预置了安全区域相关的规则:

typescript
rules: [
  // 全方向安全区域内边距
  ['p-safe', {
    padding: 'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)',
  }],
  // 顶部安全区域
  ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }],
  // 底部安全区域
  ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }],
],

使用示例:

vue
<template>
  <!-- 适配刘海屏顶部 -->
  <view class="pt-safe bg-primary">
    <text class="text-white">导航栏</text>
  </view>

  <!-- 适配底部安全区域 -->
  <view class="pb-safe bg-white">
    <button>提交</button>
  </view>
</template>

自定义规则示例

typescript
rules: [
  // 静态规则
  ['custom-shadow', { 'box-shadow': '0 4rpx 12rpx rgba(0, 0, 0, 0.1)' }],

  // 动态规则
  [/^text-(\d+)$/, ([, d]) => ({ 'font-size': `${d}rpx` })],
  [/^w-(\d+)$/, ([, d]) => ({ width: `${d}rpx` })],
  [/^h-(\d+)$/, ([, d]) => ({ height: `${d}rpx` })],

  // 带正则的复杂规则
  [/^m-(\d+)-(\d+)$/, ([, x, y]) => ({
    'margin-left': `${x}rpx`,
    'margin-right': `${x}rpx`,
    'margin-top': `${y}rpx`,
    'margin-bottom': `${y}rpx`,
  })],
],

主题配置

颜色主题

typescript
theme: {
  colors: {
    // 主题色(使用 CSS 变量支持动态切换)
    primary: 'var(--wot-color-theme,#0957DE)',

    // 自定义颜色
    success: '#07c160',
    warning: '#ff976a',
    danger: '#ee0a24',
    info: '#909399',

    // 灰度色阶
    gray: {
      50: '#fafafa',
      100: '#f5f5f5',
      200: '#eeeeee',
      300: '#e0e0e0',
      400: '#bdbdbd',
      500: '#9e9e9e',
      600: '#757575',
      700: '#616161',
      800: '#424242',
      900: '#212121',
    },
  },
},

使用示例:

vue
<template>
  <view class="text-primary">主题色文字</view>
  <view class="bg-success text-white">成功背景</view>
  <view class="border-danger">危险边框</view>
  <view class="text-gray-500">灰色文字</view>
</template>

字体尺寸

typescript
theme: {
  fontSize: {
    // 超小号字体
    '2xs': ['20rpx', '28rpx'],  // [字号, 行高]
    '3xs': ['18rpx', '26rpx'],

    // 标准字体
    xs: ['24rpx', '32rpx'],
    sm: ['28rpx', '36rpx'],
    base: ['32rpx', '44rpx'],
    lg: ['36rpx', '48rpx'],
    xl: ['40rpx', '52rpx'],
    '2xl': ['48rpx', '60rpx'],
  },
},

使用示例:

vue
<template>
  <text class="text-3xs">超小号文字</text>
  <text class="text-base">正常文字</text>
  <text class="text-2xl font-bold">大标题</text>
</template>

常用类名速查

布局

类名CSS 属性
flexdisplay: flex
inline-flexdisplay: inline-flex
blockdisplay: block
hiddendisplay: none
flex-rowflex-direction: row
flex-colflex-direction: column
flex-wrapflex-wrap: wrap
flex-1flex: 1 1 0%
items-centeralign-items: center
items-startalign-items: flex-start
items-endalign-items: flex-end
justify-centerjustify-content: center
justify-betweenjustify-content: space-between
justify-aroundjustify-content: space-around

间距

类名CSS 属性
p-4padding: 16rpx
px-4padding-left: 16rpx; padding-right: 16rpx
py-4padding-top: 16rpx; padding-bottom: 16rpx
pt-4padding-top: 16rpx
m-4margin: 16rpx
mx-automargin-left: auto; margin-right: auto
mt-4margin-top: 16rpx
gap-4gap: 16rpx

尺寸

类名CSS 属性
w-fullwidth: 100%
w-screenwidth: 100vw
w-100width: 100rpx
h-fullheight: 100%
h-screenheight: 100vh
h-100height: 100rpx
min-h-screenmin-height: 100vh
max-w-fullmax-width: 100%

文字

类名CSS 属性
text-centertext-align: center
text-lefttext-align: left
text-righttext-align: right
font-boldfont-weight: bold
font-mediumfont-weight: 500
text-smfont-size: 28rpx
text-basefont-size: 32rpx
text-lgfont-size: 36rpx
text-primarycolor: var(--wot-color-theme)
text-whitecolor: white
text-gray-500color: #9e9e9e

背景

类名CSS 属性
bg-whitebackground-color: white
bg-primarybackground-color: var(--wot-color-theme)
bg-gray-100background-color: #f5f5f5
bg-transparentbackground-color: transparent
bg-coverbackground-size: cover
bg-centerbackground-position: center

边框

类名CSS 属性
borderborder-width: 1px
border-2border-width: 2px
border-tborder-top-width: 1px
border-bborder-bottom-width: 1px
border-primaryborder-color: var(--wot-color-theme)
border-gray-200border-color: #eeeeee
roundedborder-radius: 8rpx
rounded-lgborder-radius: 16rpx
rounded-fullborder-radius: 9999px

定位

类名CSS 属性
relativeposition: relative
absoluteposition: absolute
fixedposition: fixed
stickyposition: sticky
top-0top: 0
right-0right: 0
bottom-0bottom: 0
left-0left: 0
inset-0top: 0; right: 0; bottom: 0; left: 0
z-10z-index: 10

其他

类名CSS 属性
overflow-hiddenoverflow: hidden
overflow-autooverflow: auto
opacity-50opacity: 0.5
cursor-pointercursor: pointer
select-noneuser-select: none
transitiontransition: all 0.3s

使用示例

卡片组件

vue
<template>
  <view class="bg-white rounded-lg p-4 shadow-sm">
    <view class="flex items-center gap-3">
      <image class="w-80 h-80 rounded-full" :src="avatar" />
      <view class="flex-1">
        <text class="text-lg font-medium">用户名</text>
        <text class="text-sm text-gray-500 mt-1">这是一段描述文字</text>
      </view>
      <view class="i-carbon-chevron-right text-gray-400" />
    </view>
  </view>
</template>

按钮样式

vue
<template>
  <view class="flex gap-3 p-4">
    <!-- 主要按钮 -->
    <button class="flex-1 center h-88 bg-primary text-white rounded-lg">
      主要按钮
    </button>

    <!-- 次要按钮 -->
    <button class="flex-1 center h-88 bg-gray-100 text-gray-700 rounded-lg">
      次要按钮
    </button>

    <!-- 边框按钮 -->
    <button class="flex-1 center h-88 border border-primary text-primary rounded-lg bg-transparent">
      边框按钮
    </button>
  </view>
</template>

列表布局

vue
<template>
  <view class="bg-gray-50 min-h-screen">
    <view class="bg-white">
      <view
        v-for="item in list"
        :key="item.id"
        class="flex items-center px-4 py-3 border-b border-gray-100"
      >
        <image class="w-100 h-100 rounded-lg" :src="item.image" />
        <view class="flex-1 ml-3">
          <text class="text-base font-medium">{{ item.title }}</text>
          <text class="text-sm text-gray-500 mt-1">{{ item.desc }}</text>
        </view>
        <text class="text-primary text-lg font-bold">¥{{ item.price }}</text>
      </view>
    </view>
  </view>
</template>

响应式布局

vue
<template>
  <view class="grid grid-cols-2 md:grid-cols-4 gap-4 p-4">
    <view
      v-for="i in 8"
      :key="i"
      class="aspect-square bg-gray-100 rounded-lg center"
    >
      {{ i }}
    </view>
  </view>
</template>

工作原理

┌─────────────────────────────────────────────────────────────┐
│                    UnoCSS 工作流程                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 读取配置文件 (uno.config.ts)                             │
│       ↓                                                     │
│  2. 加载预设和规则                                           │
│       ├─ presetUni (UniApp 适配)                            │
│       ├─ presetIcons (图标支持)                              │
│       └─ presetAttributify (属性化)                          │
│       ↓                                                     │
│  3. 扫描源文件                                               │
│       ├─ .vue 文件                                          │
│       ├─ .ts 文件                                           │
│       └─ .tsx 文件                                          │
│       ↓                                                     │
│  4. 提取类名和属性                                           │
│       ├─ class="flex items-center"                          │
│       ├─ flex items-center (属性化)                          │
│       └─ @apply flex (指令)                                  │
│       ↓                                                     │
│  5. 匹配规则生成 CSS                                         │
│       ├─ 内置规则                                            │
│       ├─ 自定义规则                                          │
│       └─ 快捷方式                                            │
│       ↓                                                     │
│  6. 输出优化后的 CSS                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

最佳实践

1. 合理使用快捷方式

typescript
// ✅ 推荐:常用组合定义为快捷方式
shortcuts: {
  'card': 'bg-white rounded-lg p-4 shadow-sm',
  'btn': 'center px-4 py-2 rounded-lg',
  'input-base': 'w-full h-88 px-3 border rounded-lg',
}

2. 利用 CSS 变量

typescript
theme: {
  colors: {
    // 使用 CSS 变量支持主题切换
    primary: 'var(--theme-primary, #0957DE)',
    secondary: 'var(--theme-secondary, #6c757d)',
  },
}

3. 避免过长的类名

vue
<!-- ❌ 不推荐:类名过多 -->
<view class="flex items-center justify-between px-4 py-3 bg-white border-b border-gray-100 rounded-lg shadow-sm">

<!-- ✅ 推荐:使用快捷方式或提取组件 -->
<view class="list-item">

4. 使用分组语法

vue
<!-- ✅ 推荐:使用分组减少重复 -->
<view class="hover:(bg-gray-100 text-primary) active:(scale-95)">
  可交互元素
</view>

常见问题

1. 样式未生效

问题原因:

  • 类名不在扫描范围内
  • 动态类名未被识别

解决方案:

typescript
// 将动态类名添加到 safelist
safelist: ['text-red', 'text-green', 'text-blue'],

或使用完整类名:

vue
<!-- ❌ 动态类名可能不被识别 -->
<view :class="`text-${color}`" />

<!-- ✅ 使用对象语法 -->
<view :class="{ 'text-red': color === 'red', 'text-blue': color === 'blue' }" />

2. rpx 单位问题

问题原因:

  • 部分平台不支持 rpx

解决方案:

使用 presetUni 预设,自动处理单位转换。

3. 图标不显示

问题原因:

  • 图标集未安装
  • 图标名称错误

解决方案:

bash
# 安装图标集
pnpm add -D @iconify-json/carbon @iconify-json/mdi

4. 属性化模式冲突

问题原因:

  • 与 HTML 原生属性冲突

解决方案:

typescript
presetUni({
  attributify: {
    prefixedOnly: true,  // 仅使用带前缀的属性
    prefix: 'un-',       // 添加前缀
  },
})
vue
<!-- 使用前缀 -->
<view un-flex un-items-center>
  内容
</view>