1. 前言
随着 Web 技术的发展与日益丰富的界面需求,图标逐渐成为前端开发中不可或缺的一部分,为此也诞生了各种各样的解决方案。文章总结及分析了目前常见的一些图标解决方案。
2. CSS 背景图片
2.1 background-image
图标本质上也是图片,所以一种最简单的方式就是利用background-image
将图片作为图标容器的背景,例如:
css
复制代码
.icon {
background-image: url("demo.jpg");
}
但是一个页面里往往会有很多图标,如果每个图标都去请求一次图片,会导致 HTTP 请求数量较多,我们知道 HTTP 并发请求数量是有上限的(例如 Chrome 最大是 6,HTTP/2 没有并发限制,此处暂且不论),请求太多图片会导致后面加载的图片被前面的图片阻塞,进而降低整体响应时间。为了解决这个问题,我们可以使用图像合并技术(Image Sprites)。
2.2 CSS Sprites
图像合并技术的原理是将多个图片合并成一个图片,使用时获取该图片的部分内容。这种做法有效减少了图片的请求数量,刚好适合图标的应用。
CSS Sprites(精灵图,雪碧图) 就是利用 CSS 背景图片实现图像合并的技术,图像合并可以使用其他方式,图像的部分获取主要是使用background-position
配合background-image
实现。例如,假设有一张 100px * 50px 的合并图片cat.png
:
展示左边的笑脸猫可以使用:
css
复制代码
/* 笑脸猫 */
.icon1 {
width: 50px;
height: 50px;
background-image: url("cat.png");
background-position: 0 0; /* 不偏移 */
}
展示右边的傲娇猫可以使用:
css
复制代码
/* 傲娇猫 */
.icon2 {
width: 50px;
height: 50px;
background-image: url("cat.png");
background-position: -50px 0; /* 向左偏移 50px */
}
实际使用时可以将公共的部分提取出来,不同的图标只需改变background-position
:
css
复制代码
.icon {
width: 50px;
height: 50px;
background-image: url("cat.png");
}
.icon1 {
background-position: 0 0; /* 不偏移 */
}
.icon2 {
background-position: -50px 0; /* 向左偏移 50px */
}
根据 CSS Sprites 的原理,容易得出与其相关的一些结论:
-
适用于固定容器尺寸:对于一个合并图像,每个图标的尺寸是固定的,如果一个图标要适应较多的容器,就会产生一些问题:
- 图标尺寸 = 容器尺寸:最佳状态;
- 图标尺寸 < 容器尺寸:图标可能失真;
- 图标尺寸 > 容器尺寸:配合
background-size
可以兼容所有情况,但是整个合并图像尺寸会比较大,导致请求速度较慢。
实际使用中可以设计多套不同尺寸的图标使用,但是维护成本也会相应增加。
-
适用于不需要频繁更改的场景:图片文件的维护成本相对较高,频繁新增,更改,甚至删除都会极大增加维护成本,如果是多套不同图标,维护会更困难。
-
图标颜色是固定的,不能随意更改。
-
CSS 计算图标位置时很容易出错,最好借助三方工具去设计,比如 CSS Sprites Generator。
3. 字体图标
3.1 字体图标介绍
字体图标是另一种常见的图标解决方案,其基本原理是将一组图标(例如 SVG 文件)打包成一种字体,将该字体加载到网页后,使用与某个图标对应的 Unicode 字符编码去显示该图标。例如在图标库Material Design Icons(mdi) 中,如果要显示一个 account 图标:
需要以下两步:
- 使用
@font-face
加载字体:
css
复制代码
@font-face {
font-family: "Material Design Icons";
src: url("materialdesignicons-webfont.woff2"); // 路径应替换成字体文件路径
}
- 引用字体:
根据 mdi 文档,account 图标对应的字符编码是U+F004
,在 HTML 中可以这样引用:
html
复制代码
<!--  是 U+F004 在 HTML 中的表示方法 -->
<span style="font-family: 'Material Design Icons'"></span>
实际中因为字符编码使用不便,一般会在 CSS 中定义一些类:
css
复制代码
/* 公共类,所有图标都依赖该类 */
.mdi {
font-family: 'Material Design Icons';
}
/* account 定制类,\F004 为 U+F004 在 CSS 中的表示方法 */
.mdi-account::before {
content: '\F004';
}
在 HTML 中,只需要使用<span class="mdi mdi-account"></span>
即可显示 account 图标。
3.2 字体图标分析
相比 CSS Sprites 来说,字体图标有不少优势,如:
- 可以适应不同容器尺寸。
- 可以自由更改图标颜色。
- 使用与维护要比 CSS Sprites 方便很多。
当然了,也会有一些劣势:
- 只能使用纯色图标,对彩色图标无能为力。
字体图标还有一个很大的问题,公共的图标库(如 mdi)为了满足各种各样的需求都会有很多图标,我们的项目并不会全部使用,这样在前端打包的时候就会浪费很多空间,导致页面首屏加载速度减慢。
为了解决这个问题,我们可以尝试在字体打包阶段介入(毕竟即使可以处理字体中的 CSS,也无法解决字体文件过大的问题)。字体打包实际上就是将一系列图标文件转化为了字体文件与字体样式,如果我们能够控制转换的过程以及转换的目标,这个问题就可以解决。仍以 mdi 图标为例:所有的 mdi 图标存放在@mdi/svg
包,mdi 打包过程可以使用@mdi/font-build
包实现,这个包可以指定需要转换的图标以及最终的字体格式。如果我们能够搜集到应用中所有使用到的图标(很不舒服的一点是这一步只能手动检索),那么我们就可以控制最终的字体文件大小。
另外,我们也可以控制最终生成的字体格式,实际上,现代浏览器很多已经支持了woff2
格式,因此如果不需要兼容很早的浏览器,完全可以只引用woff2
字体(woff2
字体具有更小的尺寸)。当然了,我们可以根据需求进行调整,浏览器对字体格式的支持程度如下:
woff2
:caniuse.com/?search=wof… 推荐,需要考虑兼容性。woff
:caniuse.com/?search=wof… 推荐,已经被绝大部分浏览器支持了。ttf/otf
:caniuse.com/?search=ttf 不推荐。eot
:caniuse.com/?search=eot 不推荐,只有 IE 支持。
4. SVG 图标
4.1 内嵌 SVG
SVG 能够绘制矢量图,在不同的容器尺寸下不会有失真的情况,将 SVG 直接嵌入在 HTML 中即可使用,例如:
html
复制代码
<div style="width: 50px; height: 50px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"/>
</svg>
</div>
即可画出:
相比字体图标来说,内嵌 SVG 可以渲染彩色图标,这是一个较大的优势,但是这种方案也有一个问题,就是如果同一个图标使用了很多次,需要重复很多相同的 SVG,从代码层面上我们可以使用变量去存储,但是 HTML 解析的时候还是会重复解析。为了解决这个问题,我们可以使用 SVG Sprites。
4.2 SVG Sprites
类似于 CSS Sprites,SVG Sprites 就是将多个 SVG 合并到一个 SVG 文件中,在引用的时候将需要的 SVG 内容取出,从而达到复用 SVG 的目的。其基本做法是利用 SVG <symbol>
元素定义图标,然后使用<use>
元素引用图标。例如,有一个 account 的 SVG 图像如下:
svg
复制代码
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" />
</svg>
我们可以使用<symbol>
元素将其定义在一个sprites.svg
的文件中:
svg
复制代码
<!-- sprites.svg -->
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="account" viewBox="0 0 24 24"> <!-- 使用 account id 标识该图标 -->
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"/>
</symbol>
</svg>
在 HTML 中可以这样使用:
html
复制代码
<div style="width: 50px; height: 50px;">
<svg viewBox="0 0 24 24">
<!-- 引用该图标(文件名#ID) -->
<use xlink:href="sprites.svg#account"></use>
</svg>
</div>
这样就可以正确绘制出 account 图标了。
结合以上的图标方案可以看出 SVG Sprites 几乎可以支持各种类型的图标,在其他方案解决不了的情况下可以考虑这种,如果图标不是很多,也可以直接嵌入 SVG。当然了,使用 SVG Sprites 的时候也需要关注下浏览器兼容性:
<symbol>
:caniuse.com/?search=svg…<use>
:caniuse.com/?search=svg…
5. CSS 绘制
图标的另一个解决方案是直接使用 CSS 绘制,这种方案利用 CSS border
,box-shadow
,transform
等属性结合伪元素before
,after
拼出图标,使用的时候直接引用相应的类名即可,具体做法可以参考 css.gg/ 。
这种做法的优势是使用方便,还能够使用animation
支持动态图标,劣势是图标库的设计较为复杂,只能使用一些简单的图标,如果过于复杂,实现起来会比较困难,即便可以实现也会有大量的 CSS 代码,使用的时候需要综合考虑。
6. 小结
本文总结了前端开发中常见的一些图标解决方案,并分析了不同解决方案的优劣势。目前比较主流的方案是字体图标和 SVG 图标,CSS Sprites 和 CSS 绘制可以在特定场景下使用。
学习更多前端知识请关注CRMEB