☰
BewlyCat 组件图标体系深度解析:基于 Iconify 与 UnoCSS 的按需图标方案
2026/10/5 1:38:54 网站建设 项目流程
  • 前端

【免费下载链接】BewlyCat

BewlyCat——基于BewlyBewly开发的Bilibili拓展

项目地址:https://gitcode.com/gh_mirrors/be/BewlyCat
点击查看免费下载

BewlyCat 的组件目录中有一份仅三行的 README,却点明了整个扩展的图标基础设施:借助 [Iconify] 生态,几乎可以使用任何图标集,且构建时只会打包实际用到的图标。本文以此为骨架,结合仓库中 unocss.config.ts、Icon.vue、package.json 以及 Dock、SideBar、设置页等大量组件的真实用法,深入剖析 BewlyCat 是如何把"海量图标集 + 按需打包"落地的——读完你将掌握静态原子类、动态图标 safelist、本地图标组件与 B 站专属 SVG 符号四种图标使用姿势,以及它们各自的适用场景。

一、原文档说了什么:三条核心事实

src/components/README.md的内容可以归纳为三个要点,它们是理解 BewlyCat 图标体系的钥匙:

  1. 图标来源:通过 Iconify 的力量,几乎可以使用任何图标集(Iconify 聚合了数千个开源图标集,以集合名:图标名的形式寻址)。
  2. 按需打包:它只会把"你实际用到的图标"打进产物,而不是把整个图标集搬进浏览器。
  3. 工具链参考:文档提及 vite-plugin-icons 供进一步了解细节。

值得注意的一点是:当前仓库并未直接安装vite-plugin-icons或unplugin-icons(见 package.json 的 devDependencies),实际承担"按需打包"职责的是UnoCSS 的presetIcons预设,配套的@iconify/json(v2.2.376)作为图标数据源被声明在 devDependencies 中。这正体现了 README 的"示例性"定位——它是这套方案在 BewlyCat 中的真实实现(下文将逐一验证)。

二、基础设施:UnoCSS presetIcons 与图标数据源

2.1 依赖与数据源

package.json 中与图标体系直接相关的依赖:

"devDependencies": { "@iconify/json": "^2.2.376", "unocss": "^66.4.2" }
  • @iconify/json:Iconify 的 JSON 图标数据包,包含几乎所有主流开源图标集的矢量数据。它只存在于 devDependencies,因为运行时并不需要向 Iconify 服务器请求任何东西——所有图标数据都在构建期被编译进产物。
  • unocss:提供presetIcons,负责在扫描源码时发现图标类名、按需生成对应图标的 CSS。

2.2 presetIcons 的默认样式

unocss.config.ts 中配置了presetIcons并为每个图标注入统一的默认 CSS 属性:

presetIcons({ extraProperties: { 'display': 'inline-block', 'vertical-align': 'middle', 'width': '1.2em', 'height': '1.2em', }, }),

这意味着所有i-前缀的图标类默认都是行内块元素、垂直居中对齐、尺寸跟随字号(1.2em),天然适配文字混排场景;想放大缩小只需通过字号工具类(如text-xl)控制,无需单独指定宽高。例如 Dock.vue 中:

<div i-mingcute:settings-3-line text-xl group-hover:rotate-180 transition="transform duration-400 ease-out" />

i-mingcute:settings-3-line负责渲染齿轮图标,text-xl借助 1.2em 规则放大它,再配合transition/hover实现悬停旋转的动效——图标被当作一个普通的内联元素参与布局与动画。

2.3 扫描范围

unocss.config.ts的content.filesystem声明了扫描范围:**/*.{js,ts,vue,svelte,jsx,tsx,mdx,md,astro,elm,php,phtml,html},覆盖整个仓库源码。凡是出现在这些文件中的i-集合:图标名类名,都会触发按需生成;未出现的图标则完全不会进入产物——这就是"只打包你用的图标"的机制本质。

三、静态用法:原子类,哪里需要哪里贴

3.1 基本形式

静态图标最常见的写法是直接把i-<集合>:<图标名>当作 class 写在元素上,零 JS 成本。仓库中遍布这种用法,例如:

  • ArticleCard.vue:<div i-tabler:eye />(浏览量)、<div i-tabler:thumb-up />(点赞)、<div i-tabler:message />(评论数);
  • SearchBar.vue:<div i-tabler:search block align-middle />;
  • IframeDrawer.vue:i-mingcute:external-link-line(外链)、i-mingcute:close-line(关闭);
  • MomentCard 系列:i-mingcute:more-2-line、i-mingcute:play-circle-line、i-mingcute:up-line/down-line等展开收起图标;
  • MomentVideoStrip.vue:i-line-md:confirm与i-mingcute:carplay-line组合表达"已加入稍后再看"的状态切换。

3.2 状态驱动的静态图标

静态类名也可以被条件逻辑驱动,实现"同一个位置、不同状态下显示不同图标"。典型例子在 Dock.vue:

<Icon :icon="isLayoutEditing ? 'mingcute:check-line' : 'mingcute:edit-3-line'" />

以及 ContextMenu.vue:

<i v-if="option.checked !== undefined" class="item-check" :class="{ 'i-mingcute:check-line': option.checked }" aria-hidden="true" />

这类写法仍属于"编译期可静态分析"的范畴:类名字符串以字面量形式出现在模板中,UnoCSS 依然能在构建时收集到它们。

四、动态用法:BewLocalIcon 与 safelist 机制

当图标名来自运行时数据(如用户配置、设置项定义、接口返回值)时,UnoCSS 无法在构建期扫描到具体类名。BewlyCat 为此准备了本地图标组件 + safelist 的组合拳。

4.1 本地图标组件 Icon.vue

Icon.vue 是一个仅有 19 行的轻量组件,完整实现如下:

<script setup lang="ts"> defineOptions({ name: 'BewLocalIcon' }) defineProps<{ icon: string }>() </script> <template> <span class="bew-local-icon" :class="`i-${icon}`" /> </template> <style scoped> .bew-local-icon { display: inline-block; flex: none; vertical-align: middle; } </style>

它接收一个icon: stringprop,渲染时拼出i-${icon}类名;bew-local-icon样式保证图标在 flex 布局中不被压缩(flex: none)。该组件被 Dock.vue、SideBar.vue、MomentsPop.vue、DislikeDialog.vue、VideoCardCover.vue、opusDetailDrawerLayout.ts 等多个模块引用,是动态图标的统一入口。

4.2 动态图标名从哪来

动态图标名最典型的来源是设置页的分类导航。SettingsCategoryLayout.vue 定义了CategoryPage接口,每个页面携带icon与iconActivated两个图标名:

export interface CategoryPage { value: string titleKey: string descriptionKey?: string icon: string iconActivated: string component: Component groupKey?: string warning?: boolean badgeKey?: string }

模板中根据激活态切换图标(SettingsCategoryLayout.vue):

<span class="settings-category-icon" :class="activePage === page.value ? page.iconActivated : page.icon" />

同一机制也出现在 SettingsSectionHeading.vue:<span v-if="icon" class="settings-section-heading__icon" :class="icon" />,直接以运行时传入的图标类渲染标题图标。此外 Settings/types.ts、TopBar 的 MorePop.vue、DockAndSidebar.vue 等都以icon: string字段形式承载动态图标名。

4.3 safelist:把"可能用到"的图标钉进产物

由于上述图标名是运行时字符串,UnoCSS 扫描不到,BewlyCat 在 unocss.config.ts 中通过safelist显式声明了一组固定图标类,注释写得很直白:

Runtime Icon components resolve to these local UnoCSS icons. Keeping the finite list here prevents @iconify/vue from fetching icons after mount.

这句话揭示了两个设计意图:

  1. 动态图标组件最终都解析到这组本地 UnoCSS 图标上,由 safelist 保证它们始终存在于构建产物中;
  2. 这样可以避免组件挂载后再去运行时获取图标(对应 "@iconify/vue from fetching icons after mount" 的隐患),保证离线可用、无网络抖动。

safelist 里的条目涵盖了明快风格图标(mingcute)、深色模式下切换动画(line-md)、通用操作图标(tabler/mdi)等,例如:

  • 首页导航:i-mingcute:home-5-line/home-5-fill、i-mingcute:search-2-line/search-2-fill、i-mingcute:tv-2-line/tv-2-fill、i-mingcute:star-line/star-fill、i-mingcute:time-line/time-fill(对应 Dock 的分区入口);
  • 主题切换动画:i-line-md:sunny-outline-to-moon-loop-transition、i-line-md:moon-alt-to-sunny-outline-loop-transition等,被 Dock.vue 与 SideBar.vue 在isDark判断下动态使用;
  • 编辑器操作:i-mdi:undo-variant、i-mdi:redo-variant、i-mingcute:edit-3-line、i-mingcute:check-line;
  • 互动/内容:i-mingcute:thumb-up-2-line、i-mingcute:play-circle-line、i-mingcute:danmaku-line、i-mingcute:carplay-line等。

实践要点:凡是经由BewLocalIcon或:class绑定、图标名来自变量/数据的场景,都必须在 safelist 中登记对应类名,否则构建产物会缺失该图标。safelist 是有上限的"白名单",这也倒逼项目把动态图标收敛到有限集合,而不是放任任意字符串。

五、图标命名空间:仓库实际用到的图标集

从全仓库的i-前缀扫描结果看,BewlyCat 主要使用以下四个 Iconify 命名空间,各司其职:

命名空间典型图标主要用途
mingcute:home-5-line、settings-3-line、play-circle-line、danmaku-line、layout-grid-line导航、功能入口、内容操作,中性风格,占比最大
tabler:eye、eye-off、search、copy、brand-github、brand-bilibili通用操作图标与品牌标识
line-md:sunny-outline-to-moon-loop-transition、confirm、arrow-small-up带过渡动画的状态图标(尤其是明暗主题切换)
mdi:undo-variant、redo-variant少量补充型操作图标

命名空间前缀本身也是 UnoCSS presetIcons 的寻址规则:i-<集合名>:<图标名>。更换图标风格时只需替换集合名与图标名,类名结构与布局样式完全不变,这正体现了"几乎可以使用任何图标集"的灵活性。

六、B 站专属图形:本地 SVG symbol 的补充

Iconify 覆盖通用图标,但 B 站生态中大量平台专属图形(频道分区图标、顶栏入口、用户面板、播放器控件等)并不在开源图标集中。BewlyCat 在 svgIcons.ts 中维护了一个巨大的内联 SVG sprite 字符串,通过<symbol id="...">定义了一百多个专属图标,命名空间包括:

  • channel-*:频道分区图标,如channel-anime、channel-game、channel-dance、channel-guochuang(国创)、channel-vlog、channel-tuiguang(推广)等,涵盖动画、游戏、生活、知识、影视、直播等全部分区;
  • header-*:顶栏入口,如header-channel、header-search、header-history、header-hot、header-message、header-vip、header-login、header-creation等;
  • widget-*:卡片与小组件,如widget-favorite、widget-danmaku、widget-play-count、widget-watch-later、widget-up、widget-follow、widget-people等;
  • user-*、palette-*、creator-*、history-*、rate-*等:用户面板、设置调色、创作中心、历史记录、等级皇冠等细分场景。

这类 SVG 符号与i-原子类互补:前者承载品牌与平台语义(无法用通用图标替代、甚至自带品牌色),后者承载通用交互语义(可随主题色/字号自由缩放)。图标体系由此形成"Iconify 通用图标 + 本地 SVG 专属符号"的双层结构。

七、构建视角:按需打包是如何实现的

结合 vite.config.ts 与 unocss 配置可以还原整条构建链路:

  1. UnoCSS 依据content.filesystem扫描全仓库,收集i-*类名(静态类 + safelist 类);
  2. presetIcons从@iconify/json中提取对应图标矢量数据,仅对收集到的条目生成 CSS 规则;
  3. 未在源码中出现、也未登记进 safelist 的图标不会进入产物——这就是"只 bundle 你使用的图标"在 BewlyCat 中的落地形态;
  4. 平台专属图形以<symbol>形式随 svgIcons.ts 打包,通过<use href="#id">引用,同样不存在整包冗余问题。

这种方案相比在运行时引入@iconify/vue并依赖其按需 fetch 的做法,优势在于零运行时网络依赖:所有图标在构建期固化为本地 CSS/SVG,安装扩展后即可离线渲染,也避免了组件挂载后再取图标的闪烁与失败。

八、小结:四种用法速查

场景用法关键文件
模板内静态图标直接写i-集合:图标名类名ArticleCard.vue、IframeDrawer.vue
少量条件切换:class绑定静态字符串ContextMenu.vue
运行时数据驱动的图标BewLocalIcon组件 + safelist 登记Icon.vue、SettingsCategoryLayout.vue、Dock.vue
B 站平台专属图形内联 SVG<symbol>+<use>svgIcons.ts

从 README 的三行说明出发,BewlyCat 用unocss presetIcons + @iconify/json实现了"任何图标集 + 按需打包";用safelist与BewLocalIcon解决了动态图标名的构建期收集难题;再用内联 SVG symbol 补足了 Iconify 覆盖不到的平台专属图形。这套组合既保证了扩展体积的克制,也保证了运行时图标的零依赖、零闪烁,是浏览器扩展项目中图标体系的成熟范本。

  • 前端

【免费下载链接】BewlyCat

BewlyCat——基于BewlyBewly开发的Bilibili拓展

项目地址:https://gitcode.com/gh_mirrors/be/BewlyCat
点击查看免费下载

相关推荐

上一篇:XUnity.AutoTranslator支持的10大翻译服务对比:如何选择最适合你的游戏翻译方案
下一篇:sidekick.nvim多路复用功能详解:tmux和zellij会话持久化

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询