
简介Bootstrap Icons 1.11.3 图标库压缩包面向网页设计、前端开发与 UI 设计人员提供一套可直接用于 Bootstrap 项目、也可独立引入的开源矢量图标资源适合后台管理、企业官网、移动端页面等场景补齐通用界面符号。包内共 2000 个文件其中 1994 个 SVG 图标覆盖箭头、表单、标签、社交媒体与品牌标志等常用类别另含配套 CSS 样式、woff 与 woff2 字体、SCSS 源码及 JSON 清单压缩包约 1.58MB体积轻便分类与命名便于检索。图标采用 SVG 矢量格式缩放清晰不易失真可通过 HTML 类名或直接引用文件调用并能与 Vue、React、Angular 等前端框架及构建工具配合使用。读者可据此快速定位所需图标、复制矢量文件或复用样式类统一页面视觉语言减少额外图片与字体依赖也可查阅 SCSS 与 JSON 清单完成批量维护。已有 440 人学习下载。1. 本地包里到底装了什么从一次断网事故说起去年给某公司做内网演示会前一天晚上页面还好好的第二天进了隔离网段整站图标全变成方块按钮只剩一坨文字。查了半天不是代码问题——图标走的是外链 CSS网断了就什么都没了。回来之后我把 bootstrap-icons-1.11.3.zip 拖进仓库的 vendor 目录从此图标这块再没出过事。这个包本质上是两千多个图标的两种交付形态一套图标字体woff2/woff 类名 CSS一套独立的 SVG 源文件。它能解决的核心问题很朴素——让界面里那些箭头、勾、叉、齿轮有个统一来源不用东拼西凑找图也不用为了三个图标引一整个图标库的运行时。适合谁做后台管理系统、内部工具、文档站的前端尤其是需要离线部署或者对首屏体积敏感的项目。不适合谁需要多色插画、需要复杂路径动画的场景那得去用 SVG 源文件自己改字体这条路走不通。2. 解包与三条接入路线字体、sprite、单文件内联拿到压缩包别急着往项目里一扔就完事。这个包里同时塞了字体方案和 SVG 方案两套东西的文件路径、依赖关系、失效场景都不一样先花十分钟认清楚后面能省掉一堆图标怎么是方块的排查。2.1 解包后先认清每个文件的职责我习惯先建一个干净的 vendor 目录再解压避免文件散落在项目根上# 建目录、解压、看层级 mkdir -p vendor/bootstrap-icons unzip bootstrap-icons-1.11.3.zip -d vendor/bootstrap-icons find vendor/bootstrap-icons -maxdepth 2 -type d解压完你先看到的是顶层目录里面通常还有一个同名子目录真正的资源在第二层。find这条命令就是为了确认层级——很多人直接-d vendor/解压结果 CSS 里的相对路径全断页面上图标一个都不显示还以为是字体文件坏了。把里面的文件按职责归一下类心里就有数了路径作用什么场景该用它bootstrap-icons.css全量类名定义 font-face未压缩本地调试、需要读源码bootstrap-icons.min.css同上压缩版生产环境外链font/bootstrap-icons.css字体方案样式相对路径指向同级的fonts/字体和样式要放一起时font/fonts/*.woff2主字体文件现代浏览器优先加载这个font/fonts/*.woff回退字体老环境兜底icons/*.svg单个图标的 SVG 源文件按需取用、改色、改线宽icons.svg打包好的 sprite想一次引一批、又不想装字体font/bootstrap-icons.json图标名与字符码点的映射表生成子集、做图标检索、升级比对这里有个容易忽略的点bootstrap-icons.json不是给运行时用的它是构建期的账本。你在做图标选择器、做按需裁剪、做版本升级核对时全靠这张表对齐名字和码点。后面第 4 章和第 6 章都要用到它。2.2 字体方案一个 link 解决九成场景绝大多数项目用字体方案就够了。它的原理很直接——每个图标在 CSS 里被定义成一个::before伪元素的字符内容字体文件里对应的码位画的就是那个图形。所以你写i classbi bi-alarm/i浏览器其实是渲染了一个特殊字符只是这个字符长得像个闹钟。接入分两步。第一步把样式引进来!-- 放在 head 里路径按你实际的 vendor 目录改 -- link relstylesheet href/vendor/bootstrap-icons/bootstrap-icons.css第二步直接用类名类名规则是固定前缀bi加图标名两个类缺一不可!-- 正确bi 是基础类bi-alarm 是具体图标 -- i classbi bi-alarm/i span classbi bi-check-circle/span button typebutton classbtn i classbi bi-search aria-hiddentrue/i 搜索 /buttonbi这个基础类不能省。它干的事是设置font-family、display、line-height和vertical-align这一套基础样式。只写bi-alarm而漏掉bi图标会退化成一个方框或者一个乱码字符——这是最高频的翻车点我在别人的代码里见过不止一次。标签用i还是span无所谓语义上都只是个装饰容器关键是那两个类。路径这块要留个心眼。bootstrap-icons.css里的font-face有个src指向字体文件的位置。如果你把 CSS 单独抽出来放到/assets/css/下字体却还在/vendor/bootstrap-icons/里那src的相对路径就失效了控制台会报字体 404页面上一片方块。稳妥做法是保留font/目录的原始结构只移动整个目录不动内部相对关系。字体方案的好处是省事改大小改font-size改颜色改color跟调文字一模一样不用管fill也不用管stroke。代价是每个图标只能单色而且它是文字会被文本相关的样式影响——行高、字重、文本渲染模式都会作用到它身上。2.3 SVG 路线什么时候该放弃字体一旦出现下面这几种需求字体方案就到头了该转 SVG需要两种以上颜色比如带底色的状态徽标、需要单独调整某条路径的线宽、需要给图标加过渡动画、需要在打印样式里保证一定渲染出来。最省事的 SVG 用法是直接引 sprite!-- icons.svg 里的 symbol 通过 id 引用 -- svg classicon width16 height16 aria-hiddentrue use href/vendor/bootstrap-icons/icons.svg#alarm/use /svguse href里#后面跟的是 symbol 的 id。这个写法的坑在于如果你用构建工具把 SVG 当模块处理某些配置会把 id 重写或去掉结果就是use找不到目标页面上什么都没有控制台也不报错——这是典型的黑匣子问题只能靠人眼看 DOM 结构去查。单文件内联适合图标数量极少五个以内的情况直接把icons/目录下的 SVG 源码复制进组件!-- 直接内联颜色由 fill 决定可以随意改 -- svg xmlnshttp://www.w3.org/2000/svg width16 height16 fill#d9534f viewBox0 0 16 16 path d.../ /svg内联的好处是零请求、零依赖、能单独控制每一个属性坏处是没法缓存、代码体积涨得快、图标一多模板就变得很难读。我的分界线是同一页面用到超过十个图标就走 sprite只有三五个高频图标内联反而更划算。三条路线的选择本质上是在体积灵活性维护成本三角里选两个。内部工具、后台页面字体方案 少量内联兜底是我用得最多的组合。3. 尺寸、颜色与对齐三个真正影响观感的参数图标能显示只是及格线要对齐、要跟文字一个视觉重量、要在不同字号下都不糊靠的是参数调校。这一章讲的都是具体数值不涉及原理推导遇到观感问题直接对照改。3.1 尺寸只认两套开关别混用字体图标和 SVG 图标的尺寸控制完全是两套逻辑混用就会出现改了没反应。字体图标.bi的尺寸由font-size决定因为它本质是文字/* 字体图标font-size 是唯一入口 */ .icon-sm { font-size: 0.875rem; } /* 14px 基准下 */ .icon-md { font-size: 1rem; } .icon-lg { font-size: 1.5rem; } /* 注意给 .bi 设 width/height 在字体方案下是无效的 */SVG 图标svg或use反过来由width/height属性或者 CSS 的宽高决定font-size对它没有直接作用除非你用了em单位间接继承/* SVG 图标宽高 viewBox 决定缩放 */ .icon { width: 1em; height: 1em; fill: currentColor; }为什么推荐 SVG 用1em因为这样它会自动跟随父元素的字号缩放图标和文字的相对大小关系在任何字号下都保持一致不用为每个断点单独写一套尺寸。这是我踩过坑之后固定下来的写法——早些年用固定px结果移动端把根字号调小文字变小了图标没变一排按钮里图标全显得偏大。参数上的经验值正文文字14px时图标用1em就是14px视觉上略小通常在1.05em到1.15em之间微调最舒服具体取决于图标本身的留白。有些图标比如带外框的方形图标自带的留白多同样的1em看起来就比线条图标小一圈这时候不能统一改得给那一类单独加个类。3.2 颜色跟着 currentColor 走多色得另起炉灶字体图标的颜色天然继承color这个没什么好调的。SVG 图标要拿到同样的效果必须保证源文件的路径上写的是fillcurrentColor而不是写死的#000。/* 让 SVG 图标跟随文字颜色 */ .icon { fill: currentColor; color: inherit; } /* 语义色父级给颜色图标自动跟随 */ .status-ok { color: #198754; } .status-warn { color: #ffc107; } .status-error { color: #dc3545; }!-- 图标颜色由父级 class 决定不用单独给 SVG 设 fill -- span classstatus-oksvg classiconuse href#check/use/svg 已完成/span这里有个边界要说清楚currentColor只能给一个颜色。如果你的图标需要两个颜色——比如一个带填充背景的圆加一个白色的勾——字体方案和无脑currentColor都做不到必须回到单文件 SVG把两条路径分别设fill。这时候就该老老实实内联别硬用use因为use引用的 symbol 内部样式是封闭的外部 CSS 很难精准改到某一条路径。还有一个隐蔽的坑如果你在 SVG 源文件上设了fillcurrentColor同时又在 CSS 里写了fill: #333CSS 的优先级更高currentColor就失效了图标永远是深灰。表现是改父级颜色图标不变很多人查到半夜。我的习惯是 SVG 图标只写一处颜色来源要么全交给 CSS要么全写在属性上。3.3 垂直对齐与基线漂移这是最容易被忽略但观感影响最大的一项。字体图标本质是字符它坐在文本基线上而字符的字形高度和实际矩形边界往往不重合结果就是图标比文字高一点或者低一点一排按钮里看着像没对齐。/* 基础类里通常已经带了这一句自己覆写时别丢掉 */ .bi { display: inline-block; vertical-align: -0.125em; line-height: 1; }vertical-align: -0.125em的意思是往下挪一点让图标的视觉中心跟文字的小写字母中心对齐。这个值不是固定的——如果你把图标用在纯大写的按钮里或者用在line-height很大的容器里-0.125em可能就不够了。判断方法很简单拿一个口字和一个图标并排放眯眼看它们的重心是不是在同一条水平线上。调试的时候我会临时加个背景色/* 临时调试用确认对齐后删掉 */ .bi, .icon { background: rgba(255, 0, 0, 0.15); }红色块能直接暴露图标的实际占位范围和基线位置比盯着像素猜快得多。这个习惯帮我省过好几次为什么就是差一像素的纠结。还有一个跟对齐相关的问题line-height会撑高图标所在的容器。图标本身line-height: 1但如果它的父级是line-height: 1.8的段落整个段落的高度不会变图标却被垂直居中了。想让图标跟文字严格贴合父级要么给line-height: 1要么给图标加display: inline-flex; align-items: center;把对齐从基线模式换成 flex 模式。4. 按需裁剪把全量包压到只留自己用到的图标全量字体文件体积不小对一个只用三十个图标的后台来说把两千多个字形全下下来是浪费。这一章讲的是怎么把全量包裁成一个精简版以及为什么我建议把这套流程固化成脚本而不是手工复制。4.1 先盘点到底哪些图标真的在用裁剪的前提是知道用了什么。别凭印象列清单直接扫代码# 从模板、组件、脚本里扫出所有 bi-xxx 类名去重计数 grep -rhoE bi-[a-z0-9-] src/ \ | grep -v ^bi-$ \ | sort | uniq -c | sort -rn /tmp/icon-usage.txt # 看一眼前 20 个高频图标 head -20 /tmp/icon-usage.txt # 总数量 wc -l /tmp/icon-usage.txt-r递归、-h不显示文件名、-o只输出匹配部分这三个参数组合起来能把所有类名抽出来。uniq -c统计频次方便你判断哪些是核心图标、哪些只在一处用过。这份清单要留档后面升级版本时拿它当基线。扫出来的结果会有脏数据注释里写过的类名、已经删掉的组件残留、字符串拼接出来的动态类名比如bi- type。动态拼接的类名扫不到这是这个方法的固有盲区。我的应对是在代码里加一行显式声明把可能动态生成的图标名集中写在一个常量文件里扫描时把这个文件也纳入范围。4.2 用脚本生成子集 sprite有了清单就可以从icons/目录里挑出对应的 SVG拼成一个精简的 sprite。我一般写成 Python 脚本放进构建流程而不是手工复制粘贴——手工做过一次就知道漏一个图标导致线上空白排查成本远高于写这五十行脚本。#!/usr/bin/env python3 从全量图标包生成项目自用的 SVG sprite import re from pathlib import Path ICON_DIR Path(vendor/bootstrap-icons/icons) OUT_FILE Path(dist/icons-sprite.svg) # 从 4.1 的扫描结果里整理出的白名单放单独文件便于维护 WANTED [line.split()[-1].replace(bi-, , 1) for line in Path(config/icons.txt).read_text().splitlines() if line.strip()] parts [svg xmlnshttp://www.w3.org/2000/svg styledisplay:none] missing [] for name in sorted(set(WANTED)): src ICON_DIR / f{name}.svg if not src.exists(): missing.append(name) # 名字写错或版本里没有单独收集 continue raw src.read_text(encodingutf-8) # 从源文件里读 viewBox不要硬编码不同图标可能不一致 m re.search(rviewBox([^]), raw) viewbox m.group(1) if m else 0 0 16 16 # 剥掉外层 svg ...只留内部路径 inner re.sub(r^.*?svg[^]*, , raw, flagsre.S) inner re.sub(r/svg\s*$, , inner, flagsre.S).strip() parts.append(fsymbol idi-{name} viewBox{viewbox}{inner}/symbol) parts.append(/svg) OUT_FILE.parent.mkdir(parentsTrue, exist_okTrue) OUT_FILE.write_text(\n.join(parts), encodingutf-8) print(f生成 {len(parts) - 2} 个 symbol输出 {OUT_FILE.stat().st_size} 字节) if missing: print(以下图标未找到检查名字拼写, missing)几处关键逻辑值得单独说。viewBox从源文件里正则提取而不是写死是因为图标集内部偶尔有非标准尺寸的图形硬编码会导致某些图标被裁掉一截。missing列表必须有——白名单里出现一个不存在的名字如果脚本静默跳过你只会看到页面上少了个图标不会知道是哪一步出的问题。styledisplay:none挂在根svg上是为了让 sprite 本身不占位同时打散到symbol里的图形不会在页面上显示出来。跑完之后在页面顶层引一次 sprite然后任何地方都能用use href#i-xxx引用。这套流程在编译器里跑图标增删改都走白名单比手动维护字体文件可靠得多。如果项目确实需要字体方案比如老环境不支持use也可以用字体打包工具把这份白名单生成一个精简的 woff2思路一样只打包用到的字形。体积收益更明显但改图标的流程会重一点。4.3 封装成组件别在业务代码里裸写类名裁剪做得再好如果业务代码里到处是i classbi bi-xxx下次换图标方案就是一场灾难。我的做法是封一层薄组件让业务只关心语义名不关心底层是字体还是 SVG。// Icon.jsx —— 只暴露 name 和 size内部实现随时可换 const ICONS { search: i-search, check: i-check-circle, close: i-x-lg, }; export function Icon({ name, size 1em, className , ...rest }) { const symbolId ICONS[name]; if (!symbolId) { // 名字不在白名单里开发期直接抛错别让它在线上变成空白 if (process.env.NODE_ENV ! production) { throw new Error(未登记的图标: ${name}); } return null; } return ( svg className{icon ${className}} width{size} height{size} aria-hiddentrue focusablefalse {...rest} use href{#${symbolId}} / /svg ); }这个组件干了三件事把字符串名映射成 symbol id、给未知图标在开发期抛错、默认带上aria-hidden和focusablefalse。第三条是无障碍的基本要求——装饰性图标不能被读屏软件念出来也不能进 Tab 焦点链否则键盘用户每经过一个图标就要停一次。如果图标是唯一的表意元素比如只有一个叉的关闭按钮那就不能aria-hidden得给它配aria-label或者用可访问文字。组件化之后白名单和组件里的ICONS映射还能互相校验构建脚本比对两边对不上就报错。这套机制建立起来之后换图标库这种事从重写三个月变成改一个映射表。5. 排查手册五个反复出现的现象以下五条都是我或者同事真实撞过的按现象、原因、解决三段来写遇到问题直接对号入座。5.1 图标显示成方块或问号现象页面加载正常CSS 也加载了但图标位置显示成方块、问号或者一个带数字的六边形。原因九成是字体文件没加载成功。要么路径错了构建工具把 CSS 抽到别处相对路径失效要么服务端没有给.woff2配正确的 MIME 类型要么跨域被拦了。开发环境还有一种可能字体文件在但 CSS 里的font-face的font-family名字跟.bi类里写的不一致。解决打开网络面板筛 font看有没有 404 或 403。有 404 就顺着 CSS 里的url()路径核对实际文件位置有 200 但图标还是方块就在控制台执行getComputedStyle(document.querySelector(.bi)).fontFamily看实际生效的字体族是什么跟font-face声明的对不对得上。服务端记得给.woff2加font/woff2.woff加font/woff。5.2 升级后部分图标变成空白现象把包从旧版本换到 1.11.3大部分图标正常少数几个位置空着DOM 里节点还在但没有任何内容。原因图标集在版本迭代里会改名或者合并。旧名字在新版本里可能被拆成两个、也可能被重命名成更语义化的名字。改名的图标在新 CSS 里没有对应的类名定义::before的content为空于是渲染出一个零宽字符。解决拿新旧两份bootstrap-icons.json做差集把被删掉的名字全部列出来逐个映射到新名字。第 6 章给了具体脚本。这个坑我吃过一次当时是升级到新版本之后有四个图标凭空消失查了两个小时才发现是改名。5.3 首屏图标闪一下才出现现象字体加载的那几百毫秒里图标位置先显示成方块或者空白字体到位后才变成图形肉眼能看到一次闪烁。原因浏览器的字体加载策略默认有一段时间的阻塞期阻塞期结束后如果字体还没到就先渲染回退字形等字体到了再替换这个替换过程就是闪烁。解决给font-face加font-display: swap让回退字形立刻可见虽然还是方块但不会白屏更彻底的做法是预加载字体文件!-- 放在 head 里靠前的位置和样式表并列 -- link relpreload href/vendor/bootstrap-icons/font/fonts/bootstrap-icons.woff2 asfont typefont/woff2 crossorigincrossorigin不能省字体请求属于跨源请求缺了这个属性预加载会失败控制台会有一条警告但页面看起来正常很容易被忽略。如果连这一下闪烁都不能接受那就是该换 SVG sprite 的信号了——SVG 内联在 HTML 里不存在异步加载。5.4 打印预览里图标全没了现象屏幕上看得好好的一按打印预览所有图标消失只留下空白。原因浏览器打印时默认不加载网络字体尤其是用户没勾打印背景图形的时候。字体图标依赖字体文件字体没加载自然就没图形。解决走 SVG 路线就没这个问题。如果必须用字体就针对打印媒体写一套兜底样式把关键图标换成内联 SVG 或者用文字符号顶替media print { /* 打印时隐藏字体图标用生成的文字前缀代替 */ .bi { display: none; } .print-fallback::before { content: attr(data-label); } }这个坑最坑的地方在于它上线前很难发现——没人会在开发阶段反复按 CtrlP。5.5 构建产物里 CSS 路径错位现象本地开发一切正常构建打包部署之后图标全变方块本地复现不了。原因构建工具把 CSS 从vendor/bootstrap-icons/抽出来合并进了dist/css/app.css但 CSS 里的url(./fonts/xxx.woff2)是相对原位置的。新位置下这个相对路径指向了一个不存在的目录。解决两种思路。一是把整个font/目录原样拷进产物保持相对结构不变二是在构建配置里给字体文件设publicPath或者用url()重写插件把它改成绝对路径或 CDN 前缀。我一般选第一种因为改动最小、不依赖构建工具的版本行为。验证方法很简单构建后打开产物目录用find dist -name *.woff2确认文件在再对照 CSS 里的url()逐段核路径。6. 版本升级核对用 JSON 清单把改名和新增一次性对清楚升级图标包最怕的不是新增是悄悄改名。浏览器不会因为你写了个失效的类名就报错它只是安静地渲染一个空字符等到用户截图反馈这里怎么空了一块的时候你早就忘了改过什么。我现在的固定动作是拿到新版本的bootstrap-icons.json跟仓库里旧版本的做一次结构化比对把三类变动全部列出来——新增、删除、码点变动。名字不变的图标码点变了意味着字形被重画过视觉上可能跟旁边的图标不再协调这类也得看一眼。#!/usr/bin/env python3 比对两个版本的图标清单输出升级影响面 import json import sys from pathlib import Path def load(p): data json.loads(Path(p).read_text(encodingutf-8)) # 清单是 图标名 - 码点 的映射键统一成小写避免大小写差异干扰比对 return {str(k).lower(): v for k, v in data.items()} old_path, new_path sys.argv[1], sys.argv[2] old, new load(old_path), load(new_path) added sorted(set(new) - set(old)) removed sorted(set(old) - set(new)) recoded sorted(k for k in set(old) set(new) if old[k] ! new[k]) print(f新增 {len(added)} 个) for name in added[:20]: print(f {name}) print(f\n删除 {len(removed)} 个需要改名映射) for name in removed: print(f - {name}) print(f\n码点变动 {len(recoded)} 个字形被重画注意视觉一致性) for name in recoded: print(f ~ {name}: {old[name]} - {new[name]})用法很简单两个路径参数分别指旧版和新版的 JSONpython diff_icons.py old/bootstrap-icons.json \ vendor/bootstrap-icons/font/bootstrap-icons.json拿到结果之后重点看删除那一段。把每个被删掉的名字拿到新版清单里搜一搜找语义最接近的替代通常是加了方向后缀或者名词更具体的版本然后写一张映射表// 升级映射表配合第 4 章的组件一起用 const LEGACY_MAP { check-circle: check-circle-fill, // 示例按实际比对结果填 x: x-lg, };这张表的作用是让旧代码还能跑同时给新代码指引。等一次完整的迭代周期过去旧名字的引用清零把映射表删掉升级才算真正收尾。还有一个验证环节别省升级后把第 4.1 节扫出来的高频图标清单重新跑一遍用新版本逐个生成 SVG 渲染到一张对照页上肉眼过一遍。脚本能查出名字不存在但查不出图标还在、只是线条变细了这种视觉变更。上面那段码点比对就是为这件事准备的——码点变了就说明字形换过必须人工看。说个教训。有次我图省事直接替换了整个目录没做比对也没跑对照页。上线三天后有用户反馈侧边栏的折叠箭头方向反了——那个图标在新版本里被重画成了镜像版本类名完全没变。从那以后我每次升级图标包diff_icons.py和对照页这两步都是强制走一遍的不管版本号只跳了一个小版本。图标这种东西看起来最不起眼出问题时也最难被自动化测试覆盖。希望帮到你。本文还有配套的精品资源点击获取