ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Element-UI项目高效集成iconfont:Symbol方案与SvgIcon组件封装实践

Element-UI项目高效集成iconfont:Symbol方案与SvgIcon组件封装实践 做管理后台的朋友应该都有同感Element-UI 自带的图标就那么二三十个业务一复杂产品给的设计稿上各种自定义图标就全靠外部资源了。阿里矢量图标库iconfont.cn是目前国内团队用得最多的图标管理平台把它的图标高效集成到 Element-UI 项目里基本是中后台项目的标配需求。这篇文章不是只丢给你一个“去 iconfont 复制代码”的捷径我会从方案选型、组件封装、工程化配置到高频踩坑把整套集成流程完整走一遍。适合正在用 Vue2 Element-UI 做后台项目、想彻底解决图标管理混乱问题的前端同学有一定 Vue 基础但没试过 Symbol 方式集成 iconfont 的读者跟着做基本能一次跑通。1. 放弃字体文件为什么Element-UI项目要优先用Symbol方式1.1 三种加载方式的核心差异对比阿里矢量图标库官方提供了三种使用方式Unicode、Font class 和 Symbol。很多刚接触的开发者习惯顺手复制 Font class因为看起来“最像 Element-UI 内置图标的用法”都是在模板里写个 class然后在 CSS 里设置 color 和 font-size。但从实际维护角度出发Font class 和 Unicode 本质上是同一套字体方案只是封装层级不同它们都把图标封装成了字体文件里的特殊字形通过font-family和字符码位渲染出来。字体方案的优点是兼容性好IE6 以上都能用写法也简单。缺点是图标永远只能是单色因为字体渲染时只有一个颜色维度即使你在 iconfont 里选了彩色图标下载成字体后也会自动变成单色。另外字体文件本身是一整包项目里哪怕只用了一个图标也得把整个字体文件下载下来。还有一个隐藏问题团队里如果有人忘了把新增的字体文件同步到代码库或者路径配错页面会显示成一个个方框排查起来相当痛苦。Symbol 方式是完全不同的思路。它本质上是 SVG Sprite也就是把所有图标的 path 统一收集到一个隐藏的 SVG 标签里页面上用use xlink:href#icon-xxx引用对应的 path。SVG 是矢量图形天然支持多色、无限缩放而且由于是内联在文档里的你可以用 CSS 灵活控制它的大小和颜色。它唯一的短板是兼容性不如字体方案IE 某些老版本对 SVG 内联的支持不好但后台管理项目基本都是内部系统浏览器环境可控这个短板几乎不影响选型。1.2 Symbol方案在Element-UI项目里的三个优势先说第一个优势多色图标支持。后台项目里不是只有简单的黑白图标侧边栏菜单、数据报表、状态提示经常需要用到带渐变或多种填充色的图标。如果你用 Font class设计稿上的彩色图标到了前端手里全变成单色视觉还原度直接打折扣沟通成本也会上升。Symbol 方案里每个图标就是一个独立的 SVG path原始的颜色信息完全保留。第二个优势是加载粒度可控。Font class 是整包字体文件Symbol 配合 webpack 的svg-sprite-loader可以做到只打包你实际用到的图标。图标库加进 300 个图标构建后的 sprite 文件里就只有那么十几个 path后面我会具体讲怎么配。这体验对我来说特别重要因为中后台项目维护久了图标库里的图标数量只会越来越多全量加载只会让首屏越来越臃肿。第三个优势是跟组件库协作顺畅。Element-UI 本身支持在按钮、菜单、输入框、下拉框这些组件里通过插槽或前缀插槽塞自定义内容Symbol 方式的svg标签本质上就是一个普通 DOM 节点你既可以给它绑事件、加类名也能直接塞进 Element-UI 的插槽。相比之下字体方案还得自己处理基线对齐、字体渲染偏移这些问题。用 Symbol 方式封装出的组件在 Element-UI 各个组件里的表现一致性会好很多。2. 完整接入流程从iconfont账号到全局SvgIcon组件2.1 在iconfont.cn上建项目、整理图标第一步是在 iconfont.cn 上注册并登录账号然后点击页面上方的“资源管理”创建一个自己的项目。这个项目不是代码项目更像是你的“图标分组”团队所有人都可以把这套项目加入收藏夹方便共享。创建项目时建议把项目名称改成业务模块名比如“admin-dashboard-icons”这样后期图标多了也能快速定位。接着把你需要的图标添进项目。有两种途径一种是直接搜索 iconfont 官方图标库里的现成图标点击购物车添加另一种是上传自己的 SVG设计资源是你们 UI 给的就通过“上传图标”功能提交。上传 SVG 之前有个关键步骤一定要做先检查 SVG 自身的边界。如果你直接拿设计稿导出的大画布 SVG 传上来图标周围会留一圈空白放到页面上不仅视觉上偏离尺寸计算也容易出问题。iconfont 提供了上传前的编辑界面我一般会先点“去除颜色”再点“自动吸附边缘”最后确认图标占满了整个画布再上传这样后面前端使用时会省很多调间距的精力。图标的命名从这一步就要规范。iconfont 的图标名称会直接影响到后端的 symbolId 和文件名建议一律使用小写中划线比如nav-home、order-list、user-avatar。不要用驼峰也不要用中文更不要用一串默认的icon-xxx_1这种 id后面找起来会怀疑人生。团队成员之间要互相约定好命名规则这是避免后期图标重复添加、难以引用的最有效手段。2.2 把下载的Symbol代码转成目录化的SVG文件在 iconfont 项目页里点击“下载至本地”选择 Symbol 方式会得到一个iconfont.js文件。这个文件打开后其实是一个自执行函数它会在页面 body 里创建一段隐藏的 SVG里面包含这个项目下所有图标的symbol定义。在开发调试阶段你可以在public/index.html里直接引入这个 JS 文件然后页面里就能用svguse xlink:href#icon-xxx/use/svg引用了。但我强烈不建议生产环境一直这么干原因有两个。第一这个文件是全局注入的项目里所有图标的 path 都会进入首屏 HTML相当于把所有业务图标一次性塞给了用户第二图标源文件没有纳入工程目录管理美术或产品改了一个图标的视觉前端得重新下载整个 JS 文件再替换版本差异很难追溯。正确的做法是把 symbol 转成一个个独立的.svg文件放到src/icons/svg目录下。怎么转你在 iconfont 项目页里点击每个图标的“编辑”然后选择“下载 SVG”把文件保存到本地目录。如果你图标数量比较大更快的路径是直接打开下载下来的iconfont.js把里面的symbol部分逐个抠出来用在线工具或者本地脚本批量转换成独立的 SVG 文件。每个文件的内容大概是这个结构svg xmlnshttp://www.w3.org/2000/svg symbol idicon-nav-home viewBox0 0 1024 1024 path d.../path /symbol /svg这里有一个很多人忽略的细节独立 SVG 文件的根节点里一定要带上xmlnshttp://www.w3.org/2000/svg命名空间否则在 webpack 和部分浏览器解析时可能会报错或渲染失败。symbol 标签上的id属性可以保留后面接入svg-sprite-loader时插件会根据文件名重新生成 id不会冲突。2.3 封装SvgIcon组件并全局接入Vue工程有了 SVG 目录后下一步是写一个全局图标组件这是整个集成的核心产物。我自己用的封装方式参考了开源的 vue-element-admin 思路但做了简化只保留了最常用的几个属性icon-class指定图标名class-name追加自定义类名size控制尺寸color控制颜色。template svg classsvg-icon :classclassName :stylesvgStyle aria-hiddentrue use :xlink:hreficonName/use /svg /template script export default { name: SvgIcon, props: { iconClass: { type: String, required: true }, className: { type: String, default: }, size: { type: Number, default: 16 }, color: { type: String, default: currentColor } }, computed: { iconName() { return #icon-${this.iconClass} }, svgStyle() { const style {} if (this.size) { style.fontSize ${this.size}px } if (this.color) { style.color this.color } return style } } } /script style scoped .svg-icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } /style组件里的vertical-align: -0.15em这个值是个经验值它可以让 SVG 图标跟旁边的文字基线对齐避免图标看起来“浮起来”或者偏低。width和height设置成1em目的就是让图标大小跟随父级font-size这样在按钮、菜单、表格操作列里使用时它会自然跟文字大小保持一致不需要每个地方单独写宽高。fill: currentColor是让图标默认继承父元素的color这样大部分单色场景可以直接靠外部文本颜色控制非常方便。写完组件后需要在入口文件里全局注册。同时这里要用require.context把src/icons/svg目录下的所有 SVG 文件自动加载进来这样每个图标都会进入构建流程被svg-sprite-loader收集成 sprite。注册代码我一般放在src/icons/index.jsimport Vue from vue import SvgIcon from /components/SvgIcon Vue.component(svg-icon, SvgIcon) const req require.context(./svg, false, /\.svg$/) const requireAll requireContext requireContext.keys().map(requireContext) requireAll(req)然后在main.js里import /icons就行了。业务代码里的用法就是el-menu-item index/dashboard svg-icon icon-classnav-home / span slottitle首页/span /el-menu-item el-button typeprimary sizesmall svg-icon icon-classedit / 编辑 /el-button组件名SvgIcon的S大写开头在模板里自动映射为svg-icon跟 Element-UI 的el-icon用法非常接近团队成员几乎零学习成本。3. 工程化进阶按需打包、命名空间与团队协作3.1 引入svg-sprite-loader让图标按需进入构建产物直接全局注册 SVG 文件后如果不做任何配置vue-cli 默认的 webpack 配置会把.svg文件当作静态资源处理要么生成一个文件路径要么被url-loader转成 base64反正不会生成你想要的 sprite。这正是需要svg-sprite-loader的原因它会把每个 SVG 文件的内容合并成一个symbol统一注入到页面里然后你用use引用。这样整个项目里只有实际用到的图标会进入sprite集合其余 SVG 文件根本不会被打包。具体配置放在vue.config.js的chainWebpack里const path require(path) module.exports { chainWebpack(config) { const svgRule config.module.rule(svg) svgRule.uses.clear() svgRule .use(svg-sprite-loader) .loader(svg-sprite-loader) .options({ symbolId: icon-[name] }) } }这段代码的意思是把 webpack 默认处理 SVG 的规则清空改由svg-sprite-loader处理。symbolId: icon-[name]决定了引用名称如果文件名是nav-home.svg那么生成后的 symbol id 就是icon-nav-home正好对应组件里计算的#icon-nav-home。有一点要格外注意chainWebpack里这个规则是全局生效的也就是说项目里除了src/icons/svg以外其他目录下的.svg文件也会被它接管。如果其他地方有需要当作普通图片引入的 SVG建议把 loader 的适用范围提前限定一下。可以用include指定只处理图标目录const svgRule config.module.rule(svg) svgRule.uses.clear() svgRule .include .add(path.resolve(__dirname, src/icons/svg)) .end() .use(svg-sprite-loader) .loader(svg-sprite-loader) .options({ symbolId: icon-[name] })3.2 图标命名规范与前端命名空间隔离随着项目变大你会发现图标管理真正难的不是技术接入而是规范不统一。图标目录src/icons/svg里的文件名直接对应业务里的icon-class所以命名一定要有可读性和分组逻辑。我比较习惯的规则是“模块-用途”两级结构比如menu-dashboard、order-export、user-reset。尽量别出现无意义的icon1.svg、111.svg也别用纯拼音缩写时间久了没人看得懂。这里顺带提一个跟 Element-UI 相关的小坑Element-UI 的样式命名空间是el-很多开发者在封装自己的组件时也习惯用el-前缀这在某些场景下会踩到样式覆盖或选择器冲突的坑。我给团队定的规矩是自定义组件统一用svg-icon、app-*这类前缀不要碰el-如果你要给图标加额外类名也应尽量避免覆盖el-开头的内部类名。图标改动会引起联调回归。iconfont 上的图标一旦被设计修改前端本地目录里的 SVG 文件不会自动更新。比较靠谱的流程是设计改完图标后在 iconfont 项目里发布新版本然后前端手动下载替换对应 SVG 文件。如果你的团队自动化意愿比较强也可以把整个src/icons/svg目录放进 Git 仓库每次版本变更通过 git diff 就能看出哪些图标路径变化了。前端不依赖 iconfont 的在线 CDN而是以代码库里的 SVG 为唯一标准这是我认为最稳妥的协作方式。3.3 新项目迁移到Vite时的快速替换方案如果你不是在维护老项目而是新起一个 Vite Vue 项目Element-UI 虽然主要面向 Vue2但很多团队会用兼容方案或者直接升级到 Element Plus。这套集成思路是通用的只是构建工具的配置方式变了。Vite 生态里对应的插件是vite-plugin-svg-icons配置更简单// vite.config.ts import { createSvgIconsPlugin } from vite-plugin-svg-icons import path from path export default defineConfig({ plugins: [ createSvgIconsPlugin({ iconDirs: [path.resolve(process.cwd(), src/icons/svg)], symbolId: icon-[dir]-[name] }) ] })然后在main.ts里引入一行import virtual:svg-icons-register这样SvgIcon组件本身不用改业务侧用法完全一致。这个插件的核心原理跟svg-sprite-loader是一样的都是把目录下的 SVG 整合成symbol只是注入方式从 webpack 的运行时拼接变成了 Vite 的虚拟模块机制。所以即便未来你们要把老项目从 Element-UI 升级到别的组件库图标层的SvgIcon组件基本可以原封不动地迁移过去这是整套方案带来的长期收益。4. 高频问题排查与避坑清单4.1 图标显示异常方块、多色变单色、颜色改不动先聊最让人头疼的“页面出现小方块”。这个问题的成因九成是use引用的 id 跟实际注入的 symbol id 对不上。排查时先看浏览器开发者工具查找生成的 SVG 标签确认use的xlink:href指向的#icon-xxx是否存在于svg styledisplay:none里的symbol集合中。如果看不到这个隐藏的 SVG说明图标文件根本没被注册检查require.context的目录和正则有没有覆盖到目标 SVG 文件顺便确认svg-sprite-loader的配置有没有生效。还有一个不经意的小坑在 Vue 模板里写:xlink:href时Vue2 会自动处理属性绑定但如果你手滑写成了:hreficonName大部分浏览器在 SVG use 标签上是不认的表现出来就是图标不渲染。再聊多色变单色的问题。你用 Symbol 方式引用发现图标所有颜色都变成了一个颜色这往往是图标本身的 path 里没有 fill 填充依赖外部 CSS 用currentColor统一染色。这其实是 SVG 的正常规则——fill是绘制属性它优先于 CSS 属性如果 path 上没有任何fill你设置 color 就生效。想要图标恢复多色要么在 iconfont 里下载原始彩色 SVG确保 path 里自带fill#xxx要么在 SVG 文件里手动给不同 path 加 fill 属性。反过来说如果图标怎么改 color 都不变颜色原因恰好相反path 里面写了fill#333之类的固定值它把 CSS 的color彻底盖住了。解决办法是把 path 上的fill删除让它继承组件里的fill: currentColor或者直接给这个 SVG 文件额外定义自己的 fill。4.2 在Select等Element-UI组件里图标不居中怎么办后台项目里经常要在一个弹窗中选择业务对象这时候需要给el-select加一个前缀图标来提示语义。不少人搜“element-ui select 选择器 高度怎么设置”结果折腾半天发现不是高度的问题而是图标在输入框里根本不居中要么偏上要么整个 input 被撑高了。我先说结论el-input的prefix和suffix插槽本身是 flex 布局容器如果你往里塞svg-icon先检查组件根元素的vertical-align和行高表现。一个比较稳的修复方式是在插入的图标外层包一个 span并且给它强制指定 flex 居中el-select v-modelvalue placeholder请选择 template #prefix span classselect-prefix-icon svg-icon icon-classsearch / /span /template /el-select.select-prefix-icon { display: inline-flex; align-items: center; height: 100%; }height: 100%在这里是关键因为 prefix 插槽的高度默认是跟随输入框高度的如果外层 span 没有 100% 高度flex 居中就无从谈起。同样的问题也会出现在el-button里不过按钮组件内部一般自己处理了对齐情况会好一些只需注意图标和文字之间加一个 margin 即可。4.3 打包体积与资源维护的补充建议最后聊打包体积。如果你沿用了我前面说的svg-sprite-loader方案构建后的产物里会有一个单独的 sprite 文件里面只有业务里实际用到的图标这个体积通常可以忽略。但如果你一直在用 iconfont 在线生成的 Font class 链接或全量注入的 symbol JS建议尽快改成本地目录管理。我见过一个后台项目图标库累计加了 500 多个图标全量注入首屏后多出了 300KB 左右的请求体积。300KB 在带宽有限的内网网络环境下加载时间可能从 1 秒变成 4 秒这是完全没必要的开销。维护上还有个小技巧如果产品经常增删图标建议在src/icons/svg目录里放一个README.md写明“图标新增流程”和“命名规范”。设计师改完图标后你只需要覆盖对应 SVG 文件构建后老地方引用自然生效。不要在一个项目里同时混用 Font class、Unicode、Symbol 三种方式视觉现象诡异且排查困难统一走SvgIcon组件是成本最低的路线。这套流程我在三四个后台项目里重复用过踩得最多的其实不是代码层面而是团队习惯大家总喜欢直接从 iconfont 在线地址复制 class 到代码里过几天图标改版线上就崩了。我自己的习惯是所有图标一律进src/icons/svg统一走svg-icon组件构建阶段按需切分。多花十分钟建目录和规范后面能省下好几天的排查时间。如果你也正在带这种项目从第一天就把规范立起来后面会轻松非常多。
返回列表