本文首发稀土掘金社区,未经本人允许,不可转载
本文的所有代码已上传至 zclzone/unocss-icon-demo (gitee.com),如果以下操作无法成功,可以对齐此仓库的源码。
上一篇文章介绍了 unocss 图标方案及基础使用,有不清楚基础用法的先看上一篇文章 unocss 图标方案,这可能是图标的最佳实践 - 掘金 (juejin.cn),本文不再重复赘述。
由于 Unocss 图标基于 iconify 生态,所以它可以直接使用一百多个图标集,二十多万个图标。虽然unocss提供的图标选择非常多,但有时候可能就是没有你想要的图标,或者UI设计师就是要你用他设计的奇奇怪怪的图标时就相当难受了。
于是,就有了自定义图标的需求。
在使用自定义图标之前,我们需要先明确自定义图标的来源,主要有以下几种情况
- 来源于设计稿
这种情况,一般是设计师提供的设计稿,里面有很多的图标,图标的格式一般是svg或者png。
如果是 png 格式,建议找 UI 换成 svg 格式的,如果不配合的话,也可以尝试自己转换,推荐这个网站 SVGcode,如果效果实在不满意那就直接用 png 吧,别纠结了,又不是不能用。
- 来源于 IconFont
IconFont 是支持 svg 格式的, 我们只需要把文件下载下来就行,当然也可以复制 svg 内容然后再贴到新建的 svg 文件中。
- 来源于自己设计的图标
类似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>
这是一个最简单的示例,我们将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>
然后可以看到页面上出现了两个掘金图标,其中一个设置成了掘金图标的颜色。
你以为这是最终方案了吗,如果你的自定义图标非常多,需要拆分成多个文件夹怎么办?每新增一个文件夹都要修改配置文件,还是有点麻烦。其实我们可以借助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 自定义图标方案就介绍完了,希望能帮到你,如果你有任何疑问,欢迎在评论区与我交流讨论。