UnoCSS动态样式注入详解
2026-09-03 18:50:51
5 次阅读
UnoCSS 是一种按需生成 CSS 的原子化 CSS 引擎,与传统 CSS 框架提前准备大量样式类不同,它会根据项目实际使用到的工具类生成对应 CSS,因此具有体积小、响应速度快、配置灵活等特点。
实际开发中,静态 class 处理通常比较简单,例如 text-red-500、p-4、flex 等可以直接被 UnoCSS 扫描并生成样式。但当 class 名称来自变量、接口数据或用户操作时,动态样式注入就会成为一个容易踩坑的问题。
UnoCSS 动态样式注入是什么
所谓动态样式注入,通常是指程序运行过程中根据条件或数据动态决定使用哪些 UnoCSS 工具类,例如:
import { ref } from 'vue'
const color = ref('red')
const size = ref('lg')
动态样式内容
从开发者角度看,这段代码最终可能生成:
动态样式内容
但 UnoCSS 的核心机制并不是等到浏览器运行这段 JavaScript 后再分析最终字符串,而主要依赖构建阶段的源码扫描。因此,类似 text-${color}-500 这样的动态 class,可能无法被静态扫描器准确识别。
这也是 UnoCSS 动态样式注入最常见的问题来源。
为什么动态 class 可能无法生效
UnoCSS 的按需生成机制决定了它需要知道项目中可能出现哪些工具类。
例如下面这种写法:
工具可以直接识别 text-red-500。
但下面的代码:
实际 class 是运行时拼接出来的:
text-red-500
text-blue-500
text-green-500
构建工具在扫描源码时并不能始终可靠地推断 color 的所有可能取值。
因此,浏览器中可能出现:
但是最终 CSS 中并不存在:
.text-blue-500 {
--un-text-opacity: 1;
color: rgb(...);
}
结果就是 class 看起来已经添加成功,但页面样式没有变化。
这里需要明确一点:动态 class 绑定成功,不代表 UnoCSS 一定会生成对应 CSS。
推荐方案一:使用静态 class 映射
实际项目中,最推荐的做法通常不是直接拼接 UnoCSS class,而是建立一个明确的映射关系。
例如 Vue 项目可以这样处理:
import { computed, ref } from 'vue'
const status = ref('success')
const statusClass = computed(() => {
const classMap = {
success: 'text-green-500 bg-green-50',
warning: 'text-yellow-500 bg-yellow-50',
error: 'text-red-500 bg-red-50'
}
return classMap[status.value] || ''
})
当前状态
这种方式最大的优势是 class 名称完整存在于源码中:
text-green-500
bg-green-50
text-yellow-500
bg-yellow-50
text-red-500
bg-red-50
UnoCSS 可以正常扫描这些内容,并按需生成 CSS。
这种方案特别适合按钮状态、标签颜色、消息类型、权限状态等场景。
推荐方案二:使用 safelist 强制生成动态样式
如果项目确实需要使用动态 class,可以通过 UnoCSS 的 safelist 明确告诉 UnoCSS 哪些工具类必须生成。
配置文件通常为:
// uno.config.ts
import { defineConfig } from 'unocss'
export default defineConfig({
safelist: [
'text-red-500',
'text-green-500',
'text-blue-500',
'bg-red-500',
'bg-green-500',
'bg-blue-500'
]
})
这样,即使这些 class 没有直接出现在模板的静态文本中,UnoCSS 仍然会生成相应 CSS。
如果动态样式数量有限,这种方法非常实用。
例如:
const colors = ['red', 'green', 'blue']
{{ color }}
可以将可能使用的 class 放入 safelist。
需要注意的是,safelist 并不是越多越好。加入大量无实际使用场景的工具类,会削弱 UnoCSS 按需生成的优势。
推荐方案三:使用完整字符串集合
某些场景下,可以直接把可能使用的 class 保留为完整字符串集合。
例如:
const colorClasses = {
red: 'text-red-500',
blue: 'text-blue-500',
green: 'text-green-500'
}
然后:
[color]">
内容
这种方式比:
更加稳定。
原因在于前一种写法把实际的 UnoCSS 工具类完整暴露给扫描器,后者则依赖运行时字符串拼接。
对于业务系统而言,静态映射通常比任意字符串拼接更容易维护,也更容易进行类型约束。
推荐方案四:通过 CSS 变量处理真正动态的值
如果动态变化的是颜色、尺寸、间距等具体数值,而不是 UnoCSS 工具类本身,那么 CSS 变量通常是更合适的方案。
例如:
import { ref } from 'vue'
const color = ref('#409eff')
[var(--text-color)]"
:style="{ '--text-color': color }"
>
动态颜色
这里 UnoCSS 只负责生成固定的:
text-[var(--text-color)]
真正变化的是 CSS 自定义属性:
--text-color
这种方式特别适合后端返回颜色值、主题色、用户自定义颜色等场景。
例如:
[var(--theme-color)]"
:style="{ '--theme-color': themeColor }"
>
主题内容
相比生成大量类似:
bg-[#ff0000]
bg-[#00ff00]
bg-[#0000ff]
的动态 class,CSS 变量可以避免产生大量不可预测的工具类。
动态尺寸同样可以使用 CSS 变量
假设后端返回一个动态宽度:
const width = 73
不建议直接构造:
`w-${width}`
因为 UnoCSS 的 w-73 是否存在、是否符合预期,还受到规则和单位体系影响。
可以改成:
[var(--progress-width)]"
:style="{ '--progress-width': `${width}%` }"
>
这样动态值由浏览器处理,而 UnoCSS 只需要生成固定规则。
UnoCSS 中使用动态任意值的注意事项
UnoCSS 支持类似以下形式的任意值:
[#ff0000]">
[137px]">
[rgb(20,30,40)]">
这些写法对于固定值非常方便。
但是,如果把整个 class 都通过变量生成,例如:
[${color}]`">
仍然可能面临静态扫描无法识别的问题。
因此:
[var(--color)]" :style="{ '--color': color }">
通常比:
[${color}]`">
更加可靠。
UnoCSS 动态样式注入与运行时注入的区别
需要区分两个概念:构建阶段生成 CSS 和 浏览器运行时注入 CSS。
UnoCSS 默认强调按需生成。开发环境中,UnoCSS 可以根据源码变化快速生成或更新对应 CSS,让开发者感觉像是“动态注入样式”。
但这并不意味着所有运行时产生的字符串 class 都会自动生成 CSS。
例如:
const className = `text-${userColor}-500`
当 userColor 来自接口:
{
"userColor": "purple"
}
浏览器最终可能得到:
text-purple-500
如果构建阶段无法发现这个 class,UnoCSS 不一定会为它生成样式。
因此,UnoCSS 的“动态”主要体现在按需生成与开发体验,而不是无限制地支持任意运行时 class。
Vue 中动态样式的完整实践
一个常见的状态标签可以这样实现:
import { computed, ref } from 'vue'
const status = ref('success')
const statusClass = computed(() => {
const styles = {
success: 'text-green-600 bg-green-100',
warning: 'text-orange-600 bg-orange-100',
error: 'text-red-600 bg-red-100',
info: 'text-blue-600 bg-blue-100'
}
return styles[status.value] || styles.info
})
{{ status }}
这里将稳定的基础样式直接写在 class 中:
inline-flex rounded px-3 py-1 text-sm
把变化部分交给映射对象:
const styles = {
success: 'text-green-600 bg-green-100',
warning: 'text-orange-600 bg-orange-100',
error: 'text-red-600 bg-red-100',
info: 'text-blue-600 bg-blue-100'
}
这种结构既符合 UnoCSS 的工作方式,又能让业务逻辑保持清晰。
React 项目中的动态 UnoCSS 写法
React 中同样需要注意动态 class 的扫描问题。
不推荐:
function Button({ color }) {
return (
操作
)
}
可以改为:
function Button({ color }) {
const classMap = {
red: 'bg-red-500',
blue: 'bg-blue-500',
green: 'bg-green-500'
}
return (
[color] || 'bg-gray-500'}>
操作
)
}
如果动态值本身是任意颜色,则可以采用 CSS 变量:
function ColorBox({ color }) {
return (
[var(--box-color)]"
style={{ '--box-color': color }}
>
内容
)
}
这种设计可以将“样式结构”和“动态数据”分离。
使用规则生成动态样式
对于具有明确规律的业务场景,还可以通过 UnoCSS 的规则系统扩展自己的工具类。
例如:
import { defineConfig, presetUno } from 'unocss'
export default defineConfig({
presets: [
presetUno()
],
rules: [
[
/^grid-cols-(d+)$/,
([, d]) => ({
'grid-template-columns': `repeat(${d}, minmax(0, 1fr))`
})
]
]
})
之后就可以使用:
这种方式适合项目中存在统一、明确的自定义工具类规范。
不过,自定义规则解决的是工具类生成规则问题,并不能完全解决运行时未知字符串无法被扫描的问题。对于接口返回的数据,仍然应该优先考虑映射或 CSS 变量。
动态样式注入常见问题排查
class 已经添加,但页面没有样式
首先打开浏览器开发者工具,检查元素:
如果 class 存在,再搜索最终加载的 CSS 是否包含对应规则。
如果 CSS 中不存在 .text-purple-500,基本可以判断为 UnoCSS 没有生成该工具类。
此时可以检查:
class 是否完全动态拼接;
对应文件是否被 UnoCSS 扫描;
是否使用了 safelist;
工具类是否写法正确;
自定义规则是否正确匹配。
修改配置后样式仍然不生效
修改 uno.config.ts 后,建议重新启动开发服务器。
特别是新增:
safelist: [
'text-purple-500'
]
或者修改扫描范围、预设和自定义规则后,不要只依赖热更新结果判断配置是否正确。
动态 class 来源于接口数据
例如:
const className = data.className
如果接口直接返回:
text-red-500
也不意味着 UnoCSS 能在构建阶段知道它。
对于后端可控的 class 集合,建议建立白名单:
const allowedClasses = {
primary: 'text-blue-500',
danger: 'text-red-500',
success: 'text-green-500'
}
然后只根据业务字段选择:
const className = allowedClasses[data.type]
这种方式不仅解决 UnoCSS 扫描问题,也能避免让后端数据直接控制任意前端样式。
动态样式注入的选择建议
不同场景可以采用不同策略。
| 场景 | 推荐方案 |
|---|
| 状态、类型、按钮样式 | 静态 class 映射 |
| 少量动态工具类 | safelist |
| 动态颜色值 | CSS 变量 |
| 动态尺寸值 | CSS 变量 + 任意值 |
| 自定义工具类体系 | UnoCSS Rules |
| 后端返回样式类型 | 白名单映射 |
| 完全未知的运行时 CSS | 原生 CSS 或运行时 CSS 方案 |
核心原则是:能静态确定的 class 尽量静态确定,真正动态的数值交给 CSS 变量处理。
性能方面需要注意什么
UnoCSS 的优势之一就是按需生成,因此动态样式设计也应该尽量保持可预测。
例如项目中有几千种可能的颜色:
text-red-100
text-red-200
...
text-purple-900
...
如果全部加入 safelist,最终生成的 CSS 体积会明显增加。
相比之下,如果实际需求只是用户选择任意颜色:
[var(--color)]"
:style="{ '--color': userColor }"
>