几乎是最优雅的图标方案 —— UnoCss的纯css方案

前言

我们大部分切图崽都用过iconfont的图标方案,就算你没用过也不要紧,他看起来像这样子:

html 复制代码
<i class="iconfont icon-hello" />

通过class名iconfont指出这是iconfont图标,再通过icon-hello图标名指示要渲染哪一个图标。你可以通过修改字体的属性来改变其大小或颜色:

html 复制代码
<!-- 特大号字体 -->
<i class="iconfont icon-hello text-xl" />

看起来很好用,是吧?要说缺点,那就是对图标处理很麻烦。可以在这里查看 官方文档,简单来说,iconfont的图标本质上是字体。为了使用iconfont,你首先需要把svg图标上传到iconfont,然后图标会被iconfont处理加工,最后导出一系列css、js、json、字体等资源,再放到你的项目中经过配置后使用。这不仅在处理图标时麻烦,还会占用比较大的资源(字体),甚至还有字体冲突的可能。长期以来,我们都接收了这种方案。毕竟无论怎么麻烦,都比手动在项目里调整图片方便得多。

那么有没有一种可能,能够像iconfont一样便利的修改图标大小与颜色,而且还不需要额外的资源消耗,不需要二次加工图标。新增一个图标时,只需要把图标放入项目中,就能拿来即用?

拿来!

UnoCss

首先贴上 unocss 的官网。大家或多或少都听说过 unocss,知道他是一个" 类似于tailwindcss的原子化css "。然而正如其官网大标题所说,unocss实际上是一个引擎,各种功能以类似插件的形式通过预设实现,原子化css只不过是官方的可选预设之一。对于本文所讲的图标预设,如果你不喜欢原子化css,你完全可以只使用图标预设,而不使用原子化css预设,这是不冲突的。本文要谈的是 unocss 的官方图标预设:Icons preset,他使用起来像这样:

(当然,你得先在presets里加上这个预设)

  1. 安装你喜欢的图标库,这里以unocss作者 @antfu 大神最喜欢用的carbon图标集合为例,你可以安装由iconify提供的图标库:
sh 复制代码
pnpm i -D @iconify-json/carbon
  1. 然后就能直接使用,请看代码:
html 复制代码
<div class="i-carbon-sun text-3xl c-yellow" />

后面的text-3xl表示3xl字号,c-yellow表示黄色字体。大功告成!这样就能渲染出一个太阳图标。

这里就介绍到antfu大佬的另一个开源库 icones ,这里面的图标都是能直接在npm下载然后使用的。使用前注意开源协议喔,建议只使用 MITApache 2.0 协议的图标集合。

我们可以查看 @iconify-json/carbon 这个库里有什么内容。很容易发现里面几乎全是svg ------ 请看里面的.json文件,svg的内容以字符串的形式被储存在json里。唯一的js代码只是将图标与其他一些信息导出。那么很容易联想到,如果我们以这种格式加入svg图标,是否也能像这个库的图标一样直接使用呢?

幸运的是,UnoCss已经提供了这种方案!

使用

让我们直接看 官方文档的这个章节,你会震惊的发现,原来文档里已经在一个不起眼的小角落里告诉你怎么使用自己的图标!(大嘘)

你首先需要安装 @iconify/utils 这个库(基于Node.js)

sh 复制代码
pnpm i -D @iconify/utils

他使用起来像这样:

js 复制代码
// uno.config.js
import { defineConfig, presetIcons } from 'unocss'

import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'

export default defineConfig({
  presets: [
    presetIcons({
      collections: {
        my: FileSystemIconLoader('./src/assets/svg')
      }
    })
  ]
})

这会读取 /src/assets/svg 文件夹下所有的.svg文件,并以my作为前缀,以文件名作为编号。假如我有一个名为sun.svg的文件,把他放入这个文件夹后,你可以像这样使用:

html 复制代码
<div i-my-sun />
<!-- 大小、颜色 -->
<div i-my-sun text-xl c-yellow />

哇袄!使用图标从未如此酸爽!当你需要新增一个图标时,只需要把这个svg放入项目的文件夹中,居然就能直接使用,简直就是魔法!

当然还有个问题,这个FileSystemIconLoader 工具是基于 Node.js 实现的,我知道在座的各位都是引领潮流的存在,也早就使用过 Bun 等其他运行时了。为了在其他运行时使用,我们翻看一下这个 函数的源码。其实源码非常简单,这是一个高阶函数,返回一个接受 name: string的函数,很容易看出这个就是图标的名字。然后在指定文件夹中搜索图标,简单处理后返回图标内容。如此,各位也可以在其他运行时中自行实现。

原理

大家可以阅读antfu的这篇中文文章 聊聊纯 CSS 图标 (antfu.me),这个文章中非常全面的讲了整个心路历程。值得一提的是,里面提到通过字体大小修改图标大小这个功能,是基于 em css单位实现的。记住这点,下面要考。

最佳实践

图标分类

在一个项目中可能会使用非常大量的图标,由于我们的方案是基于文件路径的,为了防止起名重复与维护困难,我们可以把他们分割在不同的文件夹中,并起不同的前缀,就像这样:

js 复制代码
export default defineConfig({
  presets: [
    presetIcons({
      collections: {
        user: FileSystemIconLoader('./src/assets/svg/user'),
        dashboard: FileSystemIconLoader('./src/assets/svg/dashboard'),
        login: FileSystemIconLoader('./src/assets/svg/login'),
        // ...
      }
    })
  ]
})

svg转换

我们有时候还会碰到修改字体大小,图标却没有变化的情况。这是因为美术给的svg宽高单位不对导致的。翻阅 icones 提供的svg图标,可以注意到其图标的宽高是统一的 1em

html 复制代码
<svg width="1em" height="1em" >
</svg>

正如上文所说,必须使用 em 单位才能实现通过修改字体大小,就可以修改图标大小的功能。FileSystemIconLoader实际上接受第二个可选的参数,用于对svg进行transform。既然如此,我们可以在这里凹一个正则来将所有的图标宽高改为1em:

js 复制代码
FileSystemIconLoader(
  './src/assets/svg',
  svg => svg
    .replace(/(<svg.*?width=)"(.*?)"/, '$1"1em"')
    .replace(/(<svg.*?height=)"(.*?)"/, '$1"1em"'),
)

不用担心正则替换的性能问题,因为在compile后所有图标都会变成base64。

我们也可能会碰到修改文字/背景颜色,图标颜色却不跟着变化的情况。这也在antfu的blog中提到,unocss对图标的渲染方式有两种:

  1. 背景图片 background-img
  2. 蒙版 mask-image

UnoCss利用蒙版实现了这个功能。而这两种方式是可以显式手动指定的:

html 复制代码
<!-- 指定为蒙版 -->
<div class="i-my-sun?mask" c-yellow />

<!-- 指定为背景 -->
<div class="i-my-moon?bg" />

svg中使用currentColor作为颜色的图标通常是单色图标(尽管你还能通过渐变色背景,实现渐变色的图标),因此通过UnoCss检查svg中是否存在currentColor来判断使用哪种方式渲染,如果存在currentColor,则默认以?mask渲染。

因此类似宽高,你也可以与美术约定单色图标使用指定颜色,然后通过正则把这个指定颜色改为 currentColor ,具体不再赘述。


报告完毕!愿月亮祝福你的前程,下次再会!

相关推荐
10年前端老司机2 小时前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
摸鱼仙人~2 小时前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客3 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2453 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇7 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖8 小时前
http的缓存问题
前端·javascript·http
小小小小宇8 小时前
请求竞态问题统一封装
前端
loriloy8 小时前
前端资源帖
前端
源码超级联盟8 小时前
display的block和inline-block有什么区别
前端
GISer_Jing8 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js