Icon 基础组件:Iconify 集成
为什么需要 Iconify 官方组件
UnoCSS 的 @iconify-json/* 预设集通过 presetIcons 提供图标支持,但其内置的 iconify-dataset 更新频率滞后于 Iconify 官方。例如 Material Symbols 图标集在 UnoCSS 中显示 11405 个图标,而 Iconify 官方已有 11538 个,部分新图标在 UnoCSS 的 preset 中无法找到。
直接使用 @iconify/vue 官方组件可以解决这个问题:
| 特性 | UnoCSS presetIcons | @iconify/vue 官方组件 |
|---|---|---|
| 图标完整性 | 依赖 @iconify-json/* 版本 | 实时从 API 拉取最新图标 |
| 构建方式 | 构建时内联 SVG | 运行时按需加载 |
| 包体积 | 图标数据打入 bundle | 按需请求,不增加 bundle |
| 离线支持 | 天然支持 | 需预加载或配置本地 Provider |
| 更新频率 | 跟随 npm 包发版 | 实时同步 Iconify API |
安装依赖
# 安装 @iconify/vue 组件库
pnpm add @iconify/vue
# 安装离线图标数据集(可选,用于离线场景)
pnpm add @iconify/json
bash
@iconify/json 包含了所有 Iconify 图标集的完整 JSON 数据(约 30MB+),适合需要完全离线的场景。
图标名称格式
Iconify 的图标名称遵循 {prefix}:{icon-name} 格式:
格式: [provider@]prefix:icon-name
示例: mdi:home
material-symbols:account-circle
carbon:add
text
| 部分 | 说明 | 示例 |
|---|---|---|
| provider | 图标来源,默认为空(Iconify API) | @local 表示本地 |
| prefix | 图标集名称 | mdi, carbon, bi |
| icon-name | 图标名称 | home, account-circle |
基础使用
<!-- src/components/IconDemo.vue -->
<script setup lang="ts">
import { Icon } from '@iconify/vue'
const handleClick = () => {
alert('Hello!')
}
</script>
<template>
<!-- 基础用法 -->
<Icon icon="mdi:home" />
<!-- 配合 CSS 框架设置大小与颜色 -->
<Icon icon="mdi:account-circle" class="text-sky-500 text-2xl" />
<!-- 绑定事件 -->
<Icon
icon="mdi:heart"
class="text-red-500 text-3xl cursor-pointer"
@click="handleClick"
/>
<!-- 使用 style 属性 -->
<Icon
icon="mdi:cog"
style="font-size: 24px; color: #666; cursor: pointer"
/>
</template>
vue
预加载图标:loadIcons
直接使用 <Icon> 组件时,图标会从 Iconify API 按需加载。在网络较慢时会出现图标闪烁问题。解决方案是使用 loadIcons 进行预加载:
<!-- src/components/IconPreloadDemo.vue -->
<script setup lang="ts">
import { ref, onBeforeMount } from 'vue'
import { Icon, loadIcons } from '@iconify/vue'
import mdiData from '@iconify/json/json/mdi.json'
// 提取所有图标名称
const iconNames = Object.keys(mdiData.icons).map(
(name) => `${mdiData.prefix}:${name}`
)
const currentIcon = ref('')
onBeforeMount(async () => {
// 预加载所有图标到内存缓存
await loadIcons(iconNames)
})
// 定时切换展示(示例)
onMounted(() => {
setInterval(() => {
const randomIndex = Math.floor(Math.random() * iconNames.length)
currentIcon.value = iconNames[randomIndex]
}, 1000)
})
</script>
<template>
<Icon v-if="currentIcon" :icon="currentIcon" class="text-4xl" />
</template>
vue
loadIcons 的核心行为:
- 异步加载:返回 Promise,所有图标加载完成后 resolve
- 网络请求:从
api.iconify.design拉取 SVG 数据 - 内存缓存:加载后缓存到浏览器内存,后续使用不再请求
- 按图标集批量加载:同一 prefix 的图标会合并为一次 API 请求
本地 Provider:addAPIProvider
在内网或离线环境中,无法访问 Iconify API。addAPIProvider 允许注册自定义的图标数据源:
<!-- src/components/IconLocalDemo.vue -->
<script setup lang="ts">
import { ref, onBeforeMount } from 'vue'
import { Icon, addAPIProvider } from '@iconify/vue'
// 注册本地 Provider
// 将 JSON 文件放到 public/icons/ 目录下
addAPIProvider('local', {
// 指向本地开发服务器或部署地址
resources: ['http://localhost:5173/icons'],
})
// 图标名称格式: @local:{prefix}:{icon-name}
// 实际请求: http://localhost:5173/icons/{prefix}.json?icons={icon-name}
const icons = ['bi:home', 'bi:gear', 'bi:person', 'bi:heart']
const currentIndex = ref(0)
onMounted(() => {
setInterval(() => {
currentIndex.value = (currentIndex.value + 1) % icons.length
}, 1000)
})
</script>
<template>
<Icon :icon="`@local:${icons[currentIndex]}`" class="text-4xl text-blue-500" />
</template>
vue
本地 JSON 文件准备
# 从 node_modules 中复制图标集 JSON 到 public
cp node_modules/@iconify/json/json/bi.json public/icons/
bash
JSON 文件结构:
{
"prefix": "bi",
"icons": {
"home": {
"body": "<path d=\"M8 2.404...\"/>",
"width": 16,
"height": 16
}
},
"lastModified": 1234567890,
"width": 16,
"height": 16
}
json
构建体积验证
使用 @iconify/vue 后的构建体积分析:
pnpm build
bash
输出结果中主文件仅约 31KB,因为 @iconify/vue 采用运行时加载策略:
- 核心库:极小,仅包含 SVG 渲染逻辑和 API 请求能力
- 图标数据:不打包进 bundle,运行时从 API 或本地 Provider 按需获取
- Tree-shaking 友好:未使用的功能不会进入最终产物
封装项目级 Icon 组件
// src/components/Icon/types.ts
export interface IconProps {
/** 图标名称,格式: prefix:icon-name */
icon: string
/** 图标大小 */
size?: number | string
/** 图标颜色 */
color?: string
/** 是否旋转 */
rotate?: number
/** 是否水平翻转 */
horizontalFlip?: boolean
/** 是否垂直翻转 */
verticalFlip?: boolean
/** 自定义 Provider */
provider?: string
/** 加载状态占位内容 */
placeholder?: string
}
typescript
<!-- src/components/Icon/Icon.vue -->
<script setup lang="ts">
import { Icon as IconifyIcon } from '@iconify/vue'
import type { IconProps } from './types'
const props = withDefaults(defineProps<IconProps>(), {
size: 16,
color: 'currentColor',
rotate: 0,
horizontalFlip: false,
verticalFlip: false,
})
defineEmits<{
click: [event: MouseEvent]
}>()
</script>
<template>
<IconifyIcon
:icon="icon"
:width="size"
:height="size"
:color="color"
:rotate="rotate"
:horizontal-flip="horizontalFlip"
:vertical-flip="verticalFlip"
:provider="provider"
class="inline-block align-middle"
@click="$emit('click', $event)"
/>
</template>
vue
两种离线方案对比
| 方案 | 适用场景 | 实现方式 | 优缺点 |
|---|---|---|---|
loadIcons 预加载 | 图标数量有限、可接受首次加载耗时 | 启动时批量从 API 加载到内存 | 优点:简单直接;缺点:仍依赖网络 |
addAPIProvider | 完全离线环境、内网部署 | 将 JSON 放到 public,配置本地 Provider | 优点:完全离线;缺点:需维护 JSON 文件 |
@iconify/json 全量 | 需要所有图标集 | 安装全量包,自行读取 | 优点:开箱即用;缺点:包体积约 30MB |
@iconify-json/{prefix} | 只需特定图标集 | 安装特定图标集的 JSON 包 | 优点:按需安装,体积可控 |
关键 API 速查
import {
Icon, // 图标渲染组件
loadIcons, // 预加载图标到内存
addAPIProvider, // 注册自定义 Provider
addIcon, // 手动添加单个图标数据
} from '@iconify/vue'
typescript
| API | 参数 | 说明 |
|---|---|---|
loadIcons(icons, callback?) | icons: string[] | 批量预加载图标,支持回调 |
addAPIProvider(name, config) | name: string, config: { resources: string[] } | 注册自定义数据源 |
addIcon(name, data) | name: string, data: IconifyIcon | 手动注册图标 SVG 数据 |
课后作业
仿照 Element Plus 官网的图标列表组件,实现以下功能:
- 图标网格展示(支持搜索过滤)
- 点击图标弹出操作菜单:
- Copy icon code -- 复制图标组件代码(如
<Icon icon="mdi:home" />) - Copy SVG content -- 复制图标原始 SVG 内容
- Copy icon code -- 复制图标组件代码(如
- 切换复制模式(组件代码 / SVG 内容)
参考 Element Plus Icon 页面: https://element-plus.org/en-US/component/icon.html
↑