使用 UnoCSS 替换 icon-font,优化图标使用

背景

在有一定历史的项目中,使用 icon-font 来实现图标功能非常常见。但是随着项目规模的增长,icon-font 会有以下几个问题:

  • 图标体积过大,影响资源加载速度
    • 在我的项目中 379 个彩色图标(使用 svg symbol 引用),iconfont.js 体积为 494KB
    • 661 个线性图标(使用 font-class 引用),iconfont.ttf 体积为 170KB
  • 使用的时候必须在首屏加载,否则会有闪烁的问题,延长白屏时间
  • 如果只想使用几个图标,那么需要引入整个 iconfont.js 或者 iconfont.ttf,造成浪费

在我的场景中,我需要为一个已经开发了几年的 PC 端产品添加移动端版本,需要使用 PC 端的一部分图标。 如果直接使用 icon-font ,那么需要引入所有的图标,这个资源大小在移动端是不可接受的。 但是如果要 UI 重新为移动端新建一个 icon-font 项目,把所需图标放进去,又会造成两边可能不同步的问题。

那么有没有一种方法,可以只使用 icon-font 的部分图标呢?

解决方案

使用 UnoCSS 的 icons preset,就能实现图标的按需引入。

有关纯 CSS 图标的概念和原理可以参考 antfu.me/posts/icons... 这里我就不介绍了

核心是所有的图标都是用 svg 格式,然后通过 UnoCSS 的按需加载能力,做到图标按需引入。 同时配合脚本自动解析原有 icon-font 的图标,转换为 svg 格式。

1.将原有 icon-font 转换为 UnoCSS 需要的格式

UnoCSS 所需的图标需要一个 JSON 文件,内容是一个键值对,key 是图标名称,value 是图标 svg 内容。类似

json 复制代码
{
  "theright-line": "<svg viewBox=\"0 0 1024 1024\">....</svg>",
  "describe-line": "<svg viewBox=\"0 0 1024 1024\">....</svg>"
}

这里我写了一个 node 脚本,可以自动解析原有 icon-font 的图标,转换为 svg 格式。

js 复制代码
import { readFile, writeFile } from 'fs/promises';
import { readdir, rm, mkdir } from 'fs/promises';
import { join } from 'path';

// 需要从 iconfont 下载的部署文件,类似 font_3519475_tursfnn52si 这种,放在 iconfonts 目录下
const iconsDir = './iconfonts';

try {
  await rm('./dist', { recursive: true, force: true });
  await mkdir('./dist');

  const dirs = await readdir(iconsDir, { withFileTypes: true });
  const iconDirs = dirs.filter((dir) => dir.isDirectory());

  // 遍历每个 iconfont 项目文件夹
  for (const dir of iconDirs) {
    const dirPath = join(iconsDir, dir.name);
    const jsonPath = join(dirPath, 'iconfont.json');

    try {
      const jsonContent = await readFile(jsonPath, 'utf-8');
      const infoObject = JSON.parse(jsonContent);

      const glyphs = infoObject.glyphs;
      const setName = infoObject.name;

      const fullSvgFileContent = await readFile(
        join(dirPath, 'iconfont.js'),
        'utf-8'
      );

      // 提取 iconfont.js 中的 svg 内容
      // iconfont.js 中的所有图标都定义在一个 svg 下,使用 <symbol> 标签定义每个图标
      const svgContent =
        fullSvgFileContent.match(
          /window\._iconfont_svg_string_\d+='([^']+)'/
        )?.[1] || '';

      // 提取 svg 中的 <symbol> 标签
      const symbols = [];
      const symbolRegex = /<symbol[^>]*id="([^"]*)"[^>]*>([\s\S]*?)<\/symbol>/g;
      let match;

      while ((match = symbolRegex.exec(svgContent)) !== null) {
        const [svg, id, content] = match;
        // 提取 symbol 的 viewBox 属性
        const viewBox = svg.match(/viewBox="([^"]*)"/)?.[1] || '';
        symbols.push({
          id,
          content,
          viewBox,
        });
      }

      const iconSet = {};

      // 判断是否是彩色图标(这里需要根据你们项目的实际情况修改)
      const isColorIcon = setName.includes('color');

      symbols.forEach((symbol) => {
        // 这里的 du-icon 是我们项目中图标的前缀,需要根据实际情况修改
        // UnoCSS 图标在使用中需要指定图标集的名字,所以可以去掉
        iconSet[symbol.id.replace('du-icon-', '')] = `<svg viewBox="${
          symbol.viewBox
        }">${
          isColorIcon
            ? symbol.content
            : // 这里修改了纯色图标的 path 的 fill 属性,做到可以在 css 中使用 color 属性
              symbol.content.replaceAll('<path', '<path fill="currentColor"')
        }</svg>`;
      });

      writeFile(
        join('./dist', `${setName}.json`),
        JSON.stringify(iconSet, null, 2)
      );
    } catch (err) {
      console.error(`Error reading iconfont.json in ${dir.name}:`, err);
    }
  }
} catch (err) {
  console.error('Error reading icon directories:', err);
}

2.配置 uno.config.ts 来使用图标

ts 复制代码
import {

  presetIcons,
} from 'unocss';

// 我们使用了 monorepo 来管理项目,所以这么使用,只要能拿到这个 json 文件即可
import duiLinesIcon from '@some-package/unocss-icon/dist/dui-icon-lines-copy.json';
import duiColorIcon from '@some-package/unocss-icon/dist/dui-icon-color.json';

export default defineConfig({
  ...
  presets: [
    ...
    presetIcons({
      collections: {
        // 这里可以定义多个图标集,每个图标集可以有不同的前缀
        dui: duiLinesIcon,
        'dui-color': duiColorIcon,
      },
      customizations: {
        // 这里可以自定义图标的默认大小
        iconCustomizer(collection, icon, props) {
          props.width = '1rem';
          props.height = '1rem';
        },
      },
    }),
  ],
});

在项目中使用的时候就很简单了,直接使用图标集的名字即可。线性图标能直接修改颜色

vue 复制代码
<template>
  <div class="i-dui:theright-line text-primary"></div>
</template>
相关推荐
冴羽20 分钟前
SvelteKit 最新中文文档教程(2)—— 路由
前端·javascript·svelte
2401_8532757334 分钟前
ajax组件是什么
前端·javascript·ajax
若简38 分钟前
umi-request使用及原理解析
前端
mmmu39 分钟前
网页快速接入 Deepseek,是如此简单!分分钟带你搞定!
前端·deepseek
MariaH40 分钟前
一次搞定 Node 的文件操作
前端
知止定静行41 分钟前
Maven使用笔记
java·前端·maven
五号厂房43 分钟前
八、唠嗑一下 React Fiber工作原理
前端
爱趣五科技44 分钟前
无界云剪:企业级云剪辑私有化部署解决方案,安全可控的创作新体验
前端·安全·音视频
刺客_Andy44 分钟前
vue3第三十节(vue3 vite中使用sass)
前端·vue.js
岛风风1 小时前
一篇搞定CJS,AMD,CMD,ESM
前端