【最佳实践】Unocss 自定义图标,试试这样用

1,289 阅读5分钟

本文首发稀土掘金社区,未经本人允许,不可转载

本文的所有代码已上传至 zclzone/unocss-icon-demo (gitee.com),如果以下操作无法成功,可以对齐此仓库的源码。

上一篇文章介绍了 unocss 图标方案及基础使用,有不清楚基础用法的先看上一篇文章 unocss 图标方案,这可能是图标的最佳实践 - 掘金 (juejin.cn),本文不再重复赘述。

由于 Unocss 图标基于 iconify 生态,所以它可以直接使用一百多个图标集,二十多万个图标。虽然unocss提供的图标选择非常多,但有时候可能就是没有你想要的图标,或者UI设计师就是要你用他设计的奇奇怪怪的图标时就相当难受了。

于是,就有了自定义图标的需求。

在使用自定义图标之前,我们需要先明确自定义图标的来源,主要有以下几种情况

  1. 来源于设计稿

这种情况,一般是设计师提供的设计稿,里面有很多的图标,图标的格式一般是svg或者png。

如果是 png 格式,建议找 UI 换成 svg 格式的,如果不配合的话,也可以尝试自己转换,推荐这个网站 SVGcode,如果效果实在不满意那就直接用 png 吧,别纠结了,又不是不能用。

  1. 来源于 IconFont

IconFont 是支持 svg 格式的, 我们只需要把文件下载下来就行,当然也可以复制 svg 内容然后再贴到新建的 svg 文件中。

  1. 来源于自己设计的图标

类似1...

拿到 svg 图标文件后,我们把文件丢到项目的某个文件夹下,这里假设是 src/assets/icons 文件夹。

然后我们修改 unocss 的配置文件,使其可以识别到到我们的 svg 图标,并将其转换成 css 代码。

// unocss.config.js
import { defineConfig, presetIcons, presetUno } from 'unocss'

export default defineConfig({
  presets: [
    presetUno(),
    presetIcons({
      warn: true,
      prefix: ['i-'],
      extraProperties: {
        display: 'inline-block',
      },
      collections: {
        custom: {
          circle: '<svg viewBox="0 0 120 120"><circle cx="60" cy="60" r="50"></circle></svg>',
        }
      }
    })
  ]
})
<!-- App.vue -->
<template>
  <i class="i-custom:circle" />
</template>

image.png

这是一个最简单的示例,我们将svg的内容直接写在了 collections 配置中,并使用 i-custom 前缀。

然后我们就可以以 i-custom:circle 的形式使用自定义的图标了,项目运行后,我们可以看到页面上出现了一个圆形图标。

当然,实际开发中我们基本是不会这样使用的,我们需要更加灵活的方式。

我们希望可以自动加载 src/assets/icons 文件夹下的所有图标,而不是自己一个个手动维护。

我们先在 src/assets/icons 文件夹下新建一个文件 juejin.svg,内容如下:

<svg
  t="1711867438390"
  class="icon"
  viewBox="0 0 1316 1024"
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  p-id="3164"
>
  <path
    d="M643.181714 247.698286l154.916572-123.172572L643.181714 0.256 643.072 0l-154.660571 124.269714 154.660571 123.245715 0.109714 0.182857z m0 388.461714h0.109715l399.579428-315.245714-108.361143-87.04-291.218285 229.888h-0.146286l-0.109714 0.146285L351.817143 234.093714l-108.251429 87.04 399.433143 315.136 0.146286-0.146285z m-0.146285 215.552l0.146285-0.146286 534.893715-422.034285 108.397714 87.04-243.309714 192L643.145143 1024 10.422857 525.056 0 516.754286l108.251429-86.893715L643.035429 851.748571z"
    fill="currentColor"
    p-id="3165"
  ></path>
</svg>

然后我们修改 unocss.config.js,将 collections 配置改成 FileSystemIconLoader 加载器,并传入 src/assets/icons 文件夹路径。这里需要先安装 @iconify/utils 依赖。

// unocss.config.js
import { defineConfig, presetIcons, presetUno } from 'unocss'
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'

export default defineConfig({
  presets: [
    presetUno(),
    presetIcons({
      warn: true,
      prefix: ['i-'],
      extraProperties: {
        display: 'inline-block',
      },
      collections: {
        custom: FileSystemIconLoader('src/assets/icons')
      }
    })
  ]
})

试一下有没有生效

<!-- App.vue -->
<template>
  <i class="i-custom:juejin" />
  <i class="i-custom:juejin text-#1E80FF" />
</template>

然后可以看到页面上出现了两个掘金图标,其中一个设置成了掘金图标的颜色。

image.png

你以为这是最终方案了吗,如果你的自定义图标非常多,需要拆分成多个文件夹怎么办?每新增一个文件夹都要修改配置文件,还是有点麻烦。其实我们可以借助nodejs的能力帮我们完成自动配置。

然后我们写一个工具方法,用来获取文件夹下的所有自定义图标,注意这个方法是运行在node环境下的。需要安装glob依赖。

import path from 'node:path'
import { globSync } from 'glob'

function getIcons() {
  const icons = {}
  const files = globSync('src/assets/icons/**/*.svg', { nodir: true, strict: true })
  files.forEach((filePath) => {
    const fileName = path.basename(filePath) // 获取文件名,包括后缀
    const fileNameWithoutExt = path.parse(fileName).name // 获取去除后缀的文件名
    const folderName = path.basename(path.dirname(filePath)) // 获取文件夹名
    if (!icons[folderName]) {
      icons[folderName] = []
    }
    icons[folderName].push(`i-${folderName}:${fileNameWithoutExt}`)
  })
  return icons
}

调用这个方法,就能拿到所有的 svg 图标,并按照文件夹分类。

为了满足这个自动化规则,我们需要先在 src/assets/icons 文件夹下新建一个 custom 文件夹,然后将自定义图标放到这个文件夹下。这个 custom 是我们举例的文件夹,它被用作图标集的名称,你可以将其换成你喜欢的名字。

然后我们再简单调整下 unocss.config.js ,为了方便理解,先将 getIcons 方法定义在 unocss.config.js 文件中。

// unocss.config.js
import { defineConfig, presetIcons, presetUno } from 'unocss'
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'
import path from 'node:path'
import { globSync } from 'glob'

function getIcons() {
  const icons = {}
  const files = globSync('src/assets/icons/**/*.svg', { nodir: true, strict: true })
  files.forEach((filePath) => {
    const fileName = path.basename(filePath) // 获取文件名,包括后缀
    const fileNameWithoutExt = path.parse(fileName).name // 获取去除后缀的文件名
    const folderName = path.basename(path.dirname(filePath)) // 获取文件夹名
    if (!icons[folderName]) {
      icons[folderName] = []
    }
    icons[folderName].push(`i-${folderName}:${fileNameWithoutExt}`)
  })
  return icons
}

const icons = getIcons()
const collections = Object.fromEntries(Object.keys(icons).map(item => [item, FileSystemIconLoader(`src/assets/icons/${item}`)]))
export default defineConfig({
  presets: [
    presetUno(),
    presetIcons({
      warn: true,
      prefix: ['i-'],
      extraProperties: {
        display: 'inline-block',
      },
      collections
    })
  ]
})

这样,我们就能自动加载 src/assets/icons 文件夹下的多个自定义图标集,而不需要手动维护配置文件。

注意: 图标文件的上一层文件夹名是图标集的名称,图标文件名是图标的名称,比如 src/assets/icons/custom/juejin.svg,它的名称是 juejin,它的图标集名称是 custom。要使用这个图标,我们需要在 class 中使用 i-custom:juejin 这样的格式。

此外,这个方法不可以在图标文件夹下嵌套文件夹,个人觉得一层文件夹就够用了,如果有你有多层嵌套文件夹的需求,可以自行修改 getIcons 方法。

最后,再填一下上篇文章的坑,上一篇文章中有提到 unocss 图标方案的不足, 也就是 无法运行时动态加载图标 ,针对这个问题,unocss 提供了一个配置项可以一定程度解决这个问题,那就是 safelist,它可以指定哪些图标可以固定被加载,哪怕你页面上没有显示使用这个图标,这使用图标动态记载成为可能,前提是你将图标对应的 class 添加到了 safelist 中。

还记得上面的 getIcons 方法吗,它不仅拿到了图标集名称,还拿到了所有图标的 class,我们可以将这些 class 都添加到 safelist 中,这样就能保证这些图标可以被动态加载。

// unocss.config.js

// ...省略代码同上

export default defineConfig({
  // ...省略代码同上
  safelist: Object.values(icons).flat()
})

我们模拟一下是否可以动态加载图标

<!-- App.vue -->
<script setup>
import { ref } from 'vue'
const name = 'custom'
const icon = ref('')

setTimeout(() => {
  icon.value = 'juejin'
}, 1000);
</script>

<template>
  <i :class="`i-${name}:${icon}`"  />
  <i :class="`i-${name}:${icon}`" class="text-#1E80FF" />
</template>

刷新页面可以看到,图标会在 1 秒后加载出来。

注意: safelist 方案并不完美,因为它会加大你打包后css的体积,加大多少取决于你 safelist 添加的图标数量,根据我的测试, 342 个图标大概会加大了 270k 大小,gzip 后 60k 不到,所以这不是一个值得去纠结的问题,而且实际开发中其实并没有太多的图标需要动态加载,尽可能按需添加到 safelist 即可。

至此,Unocss 自定义图标方案就介绍完了,希望能帮到你,如果你有任何疑问,欢迎在评论区与我交流讨论。