基于 Unocss 的后台系统 SVG 图标方案实践

在后台管理系统中,图标通常并不是简单的装饰元素,而是菜单结构、功能语义和状态表达的一部分 。为了在保证灵活性的同时降低维护成本,我在项目中基于 UnoCSS 的 presetIcons 实现了一套本地 SVG 图标方案。本文主要记录该方案的实现方式及其背后的机制设计。

一:实现思路

  • 使用 本地 SVG 文件 作为唯一图标源

  • 借助 UnoCSS presetIcons 将 SVG 转换为 CSS 图标

  • 通过统一的预处理逻辑,使 SVG 图标:

    • 尺寸可控
    • 颜色可继承
    • 支持动态渲染
  • 解决 UnoCSS 按需生成机制下,动态图标类名无法被扫描的问题

二:UnoCSS presetIcons 与本地图标集合

配置:

php 复制代码
import { defineConfig, presetIcons } from "unocss";
import { FileSystemIconLoader } from "@iconify/utils/lib/loader/node-loaders";
​
const iconDir = "./src/assets/icons";
​
export default defineConfig({
  presets: [
    presetIcons({
      extraProperties: {
        display: "inline-block",
        width: "1em",
        height: "1em",
      },
      collections: {
        svg: FileSystemIconLoader(iconDir),
      },
    }),
  ],
});
  1. src/assets/icons 注册为一个图标集合
  2. 使用 svg 作为集合名,对应类名格式为:i-svg:icon-name
  3. 统一图标尺寸为 1em,使其行为与文字一致

三:SVG 预处理:注入fill="currentColor"

在实际使用中,SVG 图标最大的痛点之一是 颜色控制不统一

常见问题

  • 未声明 fill:浏览器默认渲染为黑色
  • 写死 fill="#000":无法通过 CSS 控制
  • 多状态(hover / active / disabled)需要多份 SVG

currentColor 的作用

currentColor 并不是 SVG 私有属性,而是 CSS 颜色关键字 ,表示当前元素的 color 值。

css 复制代码
color: red;
/* currentColor === red */

如果 SVG 的 fill 使用 currentColor,即:SVG 图标的颜色,完全由外层元素的 color 决定。

注入逻辑

javascript 复制代码
FileSystemIconLoader(iconDir, (svg) => {
  return svg.includes('fill="')
    ? svg
    : svg.replace(/^<svg /, '<svg fill="currentColor" ')
});

注入后,图标即可通过普通的文本颜色控制:

ini 复制代码
<i class="i-svg:user text-gray-400"></i>
<i class="i-svg:user text-blue-500 hover:text-blue-600"></i>
<i class="i-svg:user text-red-500"></i>

四:动态场景下的关键问题:UnoCSS 的按需生成机制

UnoCSS 是 按需生成(JIT) 的工具,它只会为 在构建阶段扫描到的类名 生成 CSS。

在后台系统中,一个非常典型的场景是:

ruby 复制代码
const iconClass = `i-svg:${menu.icon}`;
<i :class="iconClass"></i>

此时:

  • menu.icon 来自后端
  • 构建阶段无法确定具体类名
  • UnoCSS 无法扫描到真实的 i-svg:xxx

结果就是: 运行时 DOM 上有 class,但 CSS 不存在,图标无法显示。

五:safelist:解决动态图标的方案

ini 复制代码
import fs from "fs";
​
const iconDir = "./src/assets/icons";
​
const generateIconSafeList = () => {
  return fs
    .readdirSync(iconDir)
    .filter(file => file.endsWith(".svg"))
    .map(file => `i-svg:${file.replace(".svg", "")}`);
};  
arduino 复制代码
export default defineConfig({
  safelist: generateIconSafeList(),
});

动态菜单图标在生产环境也能稳定渲染

六:使用

less 复制代码
<i class="i-svg:home text-sm"></i>
<i :class="`i-svg:${menu.icon}`"></i>

完整代码示例:

javascript 复制代码
import { defineConfig } from "unocss";
​
import { FileSystemIconLoader } from "@iconify/utils/lib/loader/node-loaders";
import fs from "fs";
​
// 本地SVG图标目录
const iconDir = "./src/assets/icons";
​
// 读取本地 SVG 目录,自动生成 safelist
const generateIconSafeList = () => {
  try {
    return fs
      .readdirSync(iconDir)
      .filter((file) => file.endsWith(".svg"))
      .map((file) => `i-svg:${file.replace(".svg", "")}`);
  } catch (error) {
    console.error("无法读取图标:", error);
    return [];
  }
};
​
export default defineConfig({
  presets: [
    presetIcons({
      extraProperties: {
        display: "inline-block",
        width: "1em",
        height: "1em",
      },
      // 图标集合
      collections: {
        // svg 是图标集合名称,使用 `i-svg:图标名` 调用
        svg: FileSystemIconLoader(iconDir, (svg) => {
          // 如果 `fill` 没有定义,则添加 `fill="currentColor"`
          console.log(svg, "svgsvgsvsgvsgvsgvsg");
          return svg.includes('fill="') ? svg : svg.replace(/^<svg /, '<svg fill="currentColor" ');
        }),
      },
    }),
  ],
  safelist: generateIconSafeList(),
});
相关推荐
phltxy5 分钟前
Vue3入门指南:从环境搭建到数据响应式,开启高效前端开发之旅
前端·javascript·vue.js
小飞大王6666 分钟前
CSS基础知识
前端·css
Charlie_lll8 分钟前
学习Three.js–风车星系
前端·three.js
代码游侠8 分钟前
学习笔记——Linux内核与嵌入式开发1
linux·运维·前端·arm开发·单片机·嵌入式硬件·学习
玩电脑的辣条哥25 分钟前
幽灵回复AI已回复但前端不显示的排查与修复
前端·人工智能
石去皿38 分钟前
轻量级 Web 应用 —— 把一堆图片按指定频率直接拼成视频,零特效、零依赖、零命令行
前端·音视频
星夜落月1 小时前
Web-Check部署全攻略:打造个人网站监控与分析中心
运维·前端·网络
冰暮流星1 小时前
javascript之双重循环
开发语言·前端·javascript
爱敲点代码的小哥1 小时前
C#视觉模板匹配与动态绘制实战(绘制和保存,加载tb块,处理vpp脚本的方式)
前端·javascript·信息可视化
南风知我意9572 小时前
【前端面试3】初中级难度
前端·javascript·面试