UnoCSS preset-icons 深度指南:把任意 Iconify 图标变成 Pure CSS 图标
【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss
UnoCSS 的 preset-icons 预置让开发者无需图标字体、无需图标组件库,仅凭一个 class 名(如i-mdi-alarm)就能在页面中渲染出自 Iconify 生态的任意 SVG 图标。本文基于仓库中的官方文档 docs/presets/icons.md 展开,并结合 packages-presets/preset-icons/src/core.ts 等源码,讲清它的命名约定、安装配置、渲染模式控制、图标集加载策略(浏览器 / Node.js / CDN / 自定义 Loader)、三层定制机制(transform/customize/iconCustomizer)、icon()CSS 指令以及全部配置项,帮助你在实际项目中直接复制可用的配置。
一、Pure CSS 图标的原理:mask 与 background 两种渲染模式
preset-icons 生成的并不是图片文件,而是一段带 SVG Data URI 的 CSS。每个图标最终都是一个类选择器,样式由两种模式之一产生(源码见 core.ts):
- mask 模式:利用 CSS
mask属性,图标作为遮罩、颜色来自background-color: currentColor,因此图标可以随文字颜色自由变色,适合单色图标; - bg 模式:直接以 SVG Data URI 作为
background背景图,颜色是静态的(SVG 里画了什么颜色就是什么颜色),适合彩色图标。
在auto模式(默认)下,preset 会对解析出的 SVG 做判断:如果 SVG 源码中包含currentColor关键字则使用mask,否则使用bg(见 core.ts)。这一点解释了为什么彩色图标(如vscode-icons:file-type-light-pnpm)默认走bg模式。
两种模式生成的 CSS 形如(mask 模式,摘自源码):
.i-mdi-alarm { --un-icon: url("data:image/svg+xml;utf8,%3Csvg ...%3E"); -webkit-mask: var(--un-icon) no-repeat; mask: var(--un-icon) no-repeat; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; background-color: currentColor; color: inherit; /* 兼容 Safari */ width: 1em; height: 1em; }bg 模式则输出background: url(...) no-repeat; background-size: 100% 100%; background-color: transparent;。尺寸默认取${scale}${unit ?? 'em'}作为兜底(见 core.ts),因此图标会跟随font-size缩放——这也是text-3xl能让图标变大的原因。
二、命名约定:<collection>-<icon>与<collection>:<icon>
使用图标只需遵循以下两条命名约定:
<prefix><collection>-<icon><prefix><collection>:<icon>
示例(来自官方文档):
<!-- 来自 Phosphor 图标集的锚点图标 --> <div class="i-ph-anchor-simple-thin" /> <!-- 来自 Material Design Icons 的橙色闹钟 --> <div class="i-mdi-alarm text-orange-400" /> <!-- 大号 Vue Logo --> <div class="i-logos-vue text-3xl" /> <!-- 亮色模式显示太阳、暗色模式显示月亮(Carbon) --> <button class="i-carbon-sun dark:i-carbon-moon" /> <!-- 悬停时切换表情的 Twemoji --> <div class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy" />从源码看(core.ts),图标规则的正则是一条:
/^([\w:-]+)(?:\?(mask|bg|auto))?$/即主体允许: - ? 字母数字下划线,并可选携带?mask/?bg/?auto后缀(下一节详述)。解析逻辑在parseIconWithLoader(core.ts)中:
- 若主体包含
:,按collection:name直接切分; - 若不含
:,则按-切分,并从最长 3 段开始逐级缩短尝试匹配内置集合名(COLLECTION_NAME_PARTS_MAX = 3)。例如fa-solid是集合、ph是集合、mdi是集合,于是i-mdi-alarm会被解析为集合mdi+ 图标alarm。这就是为什么以i-为前缀的连字符写法无需冒号也能工作; - 内置的合法集合名列表由 collections.ts 提供,覆盖了
mdi、carbon、lucide、tabler、ph、logos、twemoji等数百个@iconify-json集合。新发布的、不在该列表中的集合需要配合iconifyCollectionsNames选项声明(见第七节)。
所有可用图标集可在线检索(Iconify / Icônes 站点),文档中也给出了完整列表的参考入口。
三、安装与基础配置
3.1 安装
除 preset 本体外,还需要按@iconify-json/*模式在devDependencies中安装对应的图标集,例如@iconify-json/mdi(Material Design Icons)、@iconify-json/tabler(Tabler):
# pnpm pnpm add -D @unocss/preset-icons @iconify-json/[the-collection-you-want] # yarn yarn add -D @unocss/preset-icons @iconify-json/[the-collection-you-want] # npm npm install -D @unocss/preset-icons @iconify-json/[the-collection-you-want] # bun bun add -D @unocss/preset-icons @iconify-json/[the-collection-you-want]如果想一次性安装 Iconify 上全部图标集(约 130MB):
pnpm add -D @iconify/json # 或 yarn / npm / bun install -D @iconify/json3.2 在 UnoCSS 配置中启用
// uno.config.ts import presetIcons from '@unocss/preset-icons' import { defineConfig } from 'unocss' export default defineConfig({ presets: [ presetIcons({ /* options */ }), // ...other presets ], })两个实用提示(与文档一致):
- 该 preset 已被打包进
unocss包,可以直接import { presetIcons } from 'unocss',无需单独安装; - 它也可以脱离 UnoCSS 体系单独使用,作为现有 UI 框架的补充来提供 Pure CSS 图标。
3.3 Node.js 环境的自动发现
在 Node.js 环境下无需手动注册任何集合:preset 会自动探测并加载node_modules中已安装的 iconify 数据集。源码上,这由 index.ts 中的createNodeLoader实现——它动态import('@iconify/utils/lib/loader/node-loader')得到loadNodeIcon,与 CDN Loader、loadIcon一起经combineLoaders链式组合(任一 Loader 命中即返回,见 core.ts)。
另外 preset 以enforce: 'pre'注册、并声明iconslayer(优先级-30),保证图标类规则在正确的层中输出(见 core.ts)。
3.4 Extra Properties:为图标注入默认 CSS
通过extraProperties可以给所有图标附加默认样式,例如让图标默认内联显示:
presetIcons({ extraProperties: { 'display': 'inline-block', 'vertical-align': 'middle', // ... }, })从源码看,extraProperties最终作为 Iconify 的additionalProps注入(core.ts),会合并进每条图标的 CSS 对象中。
四、渲染模式控制:mode选项与?bg/?mask覆盖
mode选项类型为'mask' | 'bg' | 'auto',默认'auto':
mask:单色图标用mask属性 + 背景色着色;bg:以背景图渲染,颜色静态;auto:按图标的currentColor特征逐图标智能判定。
当自动判定不符合预期时,可以在单个 class 上用后缀显式覆盖(文档中的例子是彩色 pnpm 文件图标):
?bg—— 强制渲染为背景图;?mask—— 强制渲染为 mask 图,从而绕过图标自带颜色。
<!-- 默认 bg 模式,显示彩色 --> <div class="i-vscode-icons:file-type-light-pnpm" /> <!-- 强制 mask 模式,跟随 text-red-300 变色 --> <div class="i-vscode-icons:file-type-light-pnpm?mask text-red-300" />仓库测试 test/preset-icons.test.ts 中也专门覆盖了i-carbon-sun?bg、dark:i-carbon-moon?auto这类变体,输出快照见 test/assets/output/preset-icons.css,可用于验证你本地生成的 CSS 与预期一致。
五、图标集的加载策略:Browser vs Node.js
collections选项的类型为Record<string, (() => Awaitable<IconifyJSON>) | undefined | CustomIconLoader | InlineCollection>。Node.js 下它通常无需配置(自动发现已安装的集),但在浏览器环境下它决定了“数据集从哪里来、怎么加载”。
5.1 浏览器 + Bundler:动态 import 按需加载
浏览器场景应安装@iconify-json/[collection]而不是完整的@iconify/json(后者文件巨大)。使用动态import()后,打包器会把每个集合拆成异步 chunk、按需加载:
import presetIcons from '@unocss/preset-icons/browser' export default defineConfig({ presets: [ presetIcons({ collections: { carbon: () => import('@iconify-json/carbon/icons.json').then(i => i.default), mdi: () => import('@iconify-json/mdi/icons.json').then(i => i.default), logos: () => import('@iconify-json/logos/icons.json').then(i => i.default), }, }), ], })注意此处入口是@unocss/preset-icons/browser。该构建入口(browser.ts)不做 Node 自动发现:若配置了cdn与customFetch则使用createCDNFetchLoader(fetcher, cdn);只有cdn则走 CDN Loader;否则退回到 Iconify 的loadIcon(即使用collections中注册的 Loader)。这与package.json中的 exports 映射(./browser→dist/browser.mjs,browser条件 → 同一文件)一致。
5.2 浏览器 + CDN:v0.32.10 起支持cdn选项
presetIcons({ cdn: 'https://esm.sh/' })要求 URL 以https://开头、以/结尾,官方推荐https://esm.sh/或https://cdn.skypack.dev/。其内部实现(core.ts)会:
- 拼接
${cdnBase}@iconify-json/<collection>/icons.json拉取整个集合,并用Map做进程级缓存; - 拉回后对图标名做归一化尝试——原始名、camelCase 转 kebab-case、字母后数字前插连字符三种变体依次检索;
- 默认 fetcher 是 ofetch),也可通过
customFetch替换。
注意:CDN 方式仅对内置集合列表中的名称生效(
fetchCollection会先校验icons.includes(name)),自定义集合名不会走 CDN 拉取。
5.3 浏览器 + 自定义集合:InlineCollection/CustomIconLoader
可以直接把 SVG 字符串内联为集合,也可以混用动态 import:
presetIcons({ collections: { custom: { circle: '<svg viewBox="0 0 120 120"><circle cx="60" cy="60" r="50"></circle></svg>', /* ... */ }, carbon: () => import('@iconify-json/carbon/icons.json').then(i => i.default as any), /* ... */ }, })之后即可在模板中写<span class="i-custom:circle"></span>。更复杂的场景可实现 Iconify 的CustomIconLoader接口。
5.4 Node.js:FileSystemIconLoader从文件系统加载
Node.js 下 preset 会自动搜索已安装的 iconify 数据集,无需注册。若要加载自有图标,需额外安装@iconify/utils(dev dependency),典型配置:
// unocss.config.ts import fs from 'node:fs/promises' // loader helpers import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders' import { defineConfig, presetIcons } from 'unocss' export default defineConfig({ presets: [ presetIcons({ collections: { // key as the collection name 'my-icons': { account: '<svg><!-- ... --></svg>', // load your custom icon lazily settings: () => fs.readFile('./path/to/my-icon.svg', 'utf-8'), /* ... */ }, 'my-other-icons': async (iconName) => { // your custom loader here. Do whatever you want. // for example, fetch from a remote server: return await fetch(`https://example.com/icons/${iconName}.svg`).then(res => res.text()) }, // a helper to load icons from the file system // files under `./assets/icons` with `.svg` extension will be loaded as it's file name // you can also provide a transform callback to change each icon (optional) 'my-yet-other-icons': FileSystemIconLoader( './assets/icons', svg => svg.replace(/#fff/, 'currentColor') ), }, }), ], })5.5 Node.js:createExternalPackageIconLoader加载第三方图标包
自@iconify/utils v2.1.20起,可用createExternalPackageIconLoader从其他作者发布的 npm 包中加载图标。前提是该包内包含IconifyJSON格式的icons.json文件(可用 Iconify Tools 导出):
import { createExternalPackageIconLoader } from '@iconify/utils/lib/loader/external-pkg' import { defineConfig, presetIcons } from 'unocss' export default defineConfig({ presets: [ presetIcons({ collections: createExternalPackageIconLoader('an-awesome-collection') }), ], })也可以与FileSystemIconLoader等其他 Loader 自由组合:
import { createExternalPackageIconLoader } from '@iconify/utils/lib/loader/external-pkg' import { defineConfig, presetIcons } from 'unocss' import { FileSystemIconLoader } from 'unplugin-icons/loaders' export default defineConfig({ presets: [ presetIcons({ collections: { ...createExternalPackageIconLoader('other-awesome-collection'), ...createExternalPackageIconLoader('@my-awesome-collections/some-collection'), ...createExternalPackageIconLoader('@my-awesome-collections/some-other-collection'), 'my-yet-other-icons': FileSystemIconLoader( './assets/icons', svg => svg.replace(/^<svg /, '<svg fill="currentColor" ') ), }, }), ], })六、图标定制:transform、customize与iconCustomizer
customizations选项(类型为Omit<IconCustomizations, 'additionalProps' | 'trimCustomSvg'>,完整定义见 types.ts)提供三层定制函数。对每个加载到的图标,按以下顺序应用:
- 若提供了
transform且当前使用的是自定义图标集,先对原始svg字符串执行transform(@iconify官方集合被排除在外,不会改写其 SVG); - 若提供了
customize,以其修改默认定制值; - 若提供了
iconCustomizer,在上一步结果之上继续修改。
6.1 全局 SVG 变换(仅自定义集合)
例如给自有图标补上currentColor:
presetIcons({ customizations: { transform(svg) { return svg.replace(/#fff/, 'currentColor') }, }, })自0.30.8版本起,transform还会收到collection与icon两个参数,可按集合/图标做条件处理:
presetIcons({ customizations: { transform(svg, collection, icon) { // do not apply fill to this icons on this collection if (collection === 'custom' && icon === 'my-icon') return svg return svg.replace(/#fff/, 'currentColor') }, }, })6.2 全局属性定制(作用于所有图标)
presetIcons({ customizations: { customize(props) { props.width = '2em' props.height = '2em' return props }, }, })6.3 按集合/图标粒度定制:iconCustomizer
iconCustomizer(collection, icon, props)优先于通用配置,且适用于任何来源的图标(自定义 Loader、内联集合或@iconify官方集合):
presetIcons({ customizations: { iconCustomizer(collection, icon, props) { // customize all icons in this collection if (collection === 'my-other-icons') { props.width = '4em' props.height = '4em' } // customize this icon in this collection if (collection === 'my-icons' && icon === 'account') { props.width = '6em' props.height = '6em' } // customize this @iconify icon in this collection if (collection === 'mdi' && icon === 'account') { props.width = '2em' props.height = '2em' } }, }, })测试用例 test/preset-icons.test.ts 验证了两个值得注意的细节:iconCustomizer可以把width/height设为var(--icon-size)这类 CSS 变量,甚至auto;而 preset 内置的unit逻辑只在props.width/height未设置时才回填${scale}${unit}(core.ts),因此显式定制的值不会被覆盖。
此外,自定义 SVG 会经过trimCustomSvg处理,测试 "svg prologue cleared"(test/preset-icons.test.ts)确认了 XML 声明、DOCTYPE 等 prologue 会从 Data URI 前缀中被清除,保证data:image/svg+xml;utf8,%3Csvg的干净输出。
七、icon()CSS 指令
你还可以在 CSS 中通过icon()指令获取图标的 Data URI:
.icon { background-image: icon('i-carbon-sun'); }注意icon()指令依赖@unocss/preset-icons并复用其配置,必须先引入该 preset。更完整的指令用法见 Directives 文档。
八、完整选项速查
以下选项汇总自官方文档,默认值与 types.ts 及 core.ts 中的实现一致:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
scale | number | 1 | 相对于当前字号(1em)的缩放倍数 |
mode | 'mask' \| 'bg' \| 'auto' | 'auto' | 生成 CSS 图标的渲染模式,auto按 SVG 是否含currentColor逐图标判定 |
prefix | string \| string[] | 'i-' | 匹配图标规则的类名前缀 |
extraProperties | Record<string, string> | {} | 附加到生成 CSS 上的额外属性(如display: inline-block) |
warn | boolean | false | 匹配到不存在的图标时发出警告(实现上会在 ESLint 环境下静默,见 core.ts) |
iconifyCollectionsNames | string[] | undefined | 补充声明未列入内置列表的新@iconify-json集合;注意外部自定义集合不能用它,应使用FileSystemIconLoader或createExternalPackageIconLoader |
collections | Record<string, (() => Awaitable<IconifyJSON>) \| undefined \| CustomIconLoader \| InlineCollection> | undefined | Node.js 下自动发现已安装数据集;浏览器下用其提供数据集与自定义加载机制 |
layer | string | 'icons' | 图标规则所在 layer |
customizations | Omit<IconCustomizations, 'additionalProps' \| 'trimCustomSvg'> | undefined | transform/customize/iconCustomizer三层定制 |
autoInstall | boolean | false | 检测到图标使用且缺少对应包时自动安装图标源包;仅 Node.js 环境有效,浏览器下被忽略 |
unit | string | 'em' | 图标尺寸单位(如'rem'),与scale组合决定默认宽高 |
cdn | string | undefined | 从 CDN 加载图标,须以https://开头、/结尾;推荐https://esm.sh/、https://cdn.skypack.dev/(v0.32.10 起支持) |
customFetch | (url: string) => Promise<any> | undefined | 自定义 fetch 函数替代默认的ofetch,用于提供图标数据 |
processor | (cssObject: CSSObject, meta: Required<IconMeta>) => void | undefined | 在 CSS 对象序列化前的钩子,可对cssObject做最后加工 |
其中IconMeta结构为:
interface IconMeta { collection: string icon: string svg: string mode?: IconsOptions['mode'] }processor的一个真实用例见 test/preset-icons.test.ts:在bg模式下删掉width/height,让图标尺寸完全交给外层容器控制;对应输出快照在 test/assets/output/preset-icons-propsProcessor.css。
九、自定义图标集清理与无障碍
9.1 自定义图标集的 Cleanup
使用自定义图标集时,建议参照 Iconify 对图标集做的清理流程(Iconify Tools 提供了完整工具链),例如统一替换#fff为currentColor、移除多余属性,使图标更适配 mask 着色模式;官方也维护了基于本 preset 的 Vue 3 演示项目可供参考(Iconify Tools 仓库中的 unocss 示例)。
9.2 Accessibility Concerns
图标对屏幕阅读器用户是不可见的,需要为它们提供替代文本:
<!-- 有意义的图标:提供 aria-label --> <a href="/profile" aria-label="Profile" class="i-ph:user-duotone"></a>纯装饰性图标则应从可访问性树中隐藏:
<a href="/profile"> <span aria-hidden="true" class="i-ph:user-duotone"></span> My Profile </a>CSS 图标在行为上类似 icon font,因此图标字体的无障碍技巧同样适用。若需要“视觉隐藏但对读屏可用”的元素,可参考 Wind3 preset 提供的sr-only工具类。
十、小结
preset-icons 的设计可以概括为三句话:命名即取用(i-<collection>-<icon>或i-<collection>:<icon>,解析规则见 core.ts)、模式自动判定、可按需覆盖(auto/?bg/?mask)、数据源可插拔(Node 自动发现、Bundler 动态 import、CDN、FileSystemIconLoader、createExternalPackageIconLoader与内联集合任意组合)。配合extraProperties、unit/scale与transform/customize/iconCustomizer三层定制,你可以在不引入任何图标组件的前提下,把 Iconify 生态的全部图标资产无缝接入 UnoCSS 流水线。
来源致谢:该 preset 的雏形来自社区对 unplugin-icons 的 issue 讨论,并基于相关 PR 的工作演化而来(详见 docs/presets/icons.md 的 Credits 部分)。
【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考