Ray-D-Song's Blog

2022-10-10 07:51 南京

如何创建一个 UnoCSS icon 选择器

2024-08-08 3min

效果展示:

activity
add-cart
align-bottom
align-center
align-left
align-right
align-top
align-vertically
angry
app-menu
apron
arrow-down
arrow-left
arrow-right
arrow-up
artboard
audio-player
backward
bar
bar-chart
beer
beginner
bell
birthday-cake
bold
bolt
book
bookmark
bread
browser

UnoCSS Icon 的使用方法是在标签上添加i-开头的 icon 名, 例如
<div class="i-mdi-access-point" />
就会渲染

不需要在public或者assets目录引入额外的 icon 文件或者 js 文件, 还能用 class 来自由控制大小和颜色, 非常方便.

前端一个很常见的场景是让用户自己选择 icon, 由于UnoCSS 是一个作用于编译时的引擎, 它会根据这个 icon 名生成纯css icon, 不会将合集中所有的icon打包进去.

事实上<div class={i-mdi-$iconName}>这种写法也是无效的, 因为编译时无法确认iconName的值引擎就不会生成对应的样式.

要自己实现这个功能, 只需要以下几步:

获取合集中的所有 icon

Feather Icon为例, npm i -D @iconify-json/fe 安装iconify集合.

安装后可以通过import { icons } from '@iconify-json/fe/index.js'获取所有的 icon 数据, 结构如下:

{
  // icon 前缀
  prefix: 'fe',
  // icon 集合
  icons: {
    // icon 名称
    icon_name: {
      // 数据
      body: //...
    }
  }
}

注意, 导入的 icons 是包含 prefix 字段的对象, 而不是 icon 集合对象, 要访问 icon 集合对象需要icons.icons 接下来我们只需要根据icons.icons的key遍历渲染即可, 以Vue为例.

<script setup lang="ts">
import { icons } from '@iconify-json/fe/index.js'
</script>

<template>
  <div class="dark:bg-[#181818] bg-[#ffffff] w-full h-40 rounded-lg overflow-y-scroll">
    <div class="grid grid-cols-6 gap-4">
      <template v-for="icon in Object.keys(icons.icons)">
        <div class="flex flex-col items-center justify-center cursor-pointer p-4px dark:hover:bg-[#2A2A2A] hover:bg-[#EBEAEA]">
          <div :class="`i-${icons.prefix}-${icon}`" class="mb-2px" />
          <div class="text-xs text-center">{{ icon }}</div>
        </div>
      </template>
    </div>
  </div>
</template>

但这个时候 icon 是不可见的, 因为采用了i-${icons.prefix}-${icon}这样的动态写法.

添加白名单

uno.config.ts 中有safelist字段, 名单内的 class 无论是否使用都会被编译.
再一次遍历icons.icons的key, 将所有 icon 名添加到safelist中.

import { icons } from '@iconify-json/fe/index.js'

export default defineConfig({
  safelist: Object.keys(icons.icons).map(icon => `i-fe-${icon}`),
})

这样界面上就能看到所有 icon 了.

过滤

这里还有一个问题, 比如material-design-icons这个集合中有七千多个 icon, 但是我们只需要其中的一部分.
最简单的做法是加个filter, 例如只保留outline类型的 icon.

import { icons } from '@iconify-json/fe/index.js'

export default defineConfig({
  safelist: Object
    .keys(icons.icons)
    .filter(icon => icon.includes('outline'))
    .map(icon => `i-fe-${icon}`),
})