知其所以然——深入理解icon font

现如今,icon font 已经是一个很基础的技术了。 《Iconfont-阿里巴巴矢量图标库》 (opens new window)用起来很简单方便,相关的教程很容易就能搜到。 作为一个甚至支持到 IE6 的技术,总是要解它的原理的。本文将通过一个用到了 icon font 本质原理的实际需求,深入分析 Iconfont。

# 一个小需求

最近接到了一个需求,某个产品的价格调整了,需要调整宣传页展示的价格。打开页面,查看对应的位置后发现,果不其然,是一张图:

页面样式

需要改图上的价格,那必然得让设计师出马咯。但在要图的时候,设计师提了意见:

能改成可配置的吗?如果都是一张张图,后期维护起来很困难。

说的很有道理,根本无法反驳。。而且和产品经理确认了一下,这个价格过一阵可能还得改,总这么一遍遍要图也很麻烦。那就改吧!于是,把这块重写成了这样:

初步修改

这里价格用的是默认字体,已经加粗了,但还是和设计稿差很多。把效果图发给设计师,得到的回答是这样不行。但设计师给了解决方案:

我把数字单独给你切出来,你来排如何?

咦,好像可以!但无论直接用 img 嵌在页面里还是写一堆 class = 1、2、3…… 的 div 都太 low 了。 有没有什么优雅的解决方案呢?iconfont! 或者说,直接生成一个只有数字 0-9 和 ¥ 符号的字体就可以了! 万能的搜索引擎指引我找到了icomoon (opens new window)这个网站。 于是找设计师要到了对应数字和字符的 svg 图,导出成字体并用 @font-face 引入,再把数字 div 设置成相应的字体,最终页面长这样:

最终样式

设计师表示很满意。注意右边的 dom 结构,这表示以后维护价格的时候只修改数字就可以了。不仅方便,还更符合直觉。

# icomoon使用教学

上面的需求最关键的点就是生成字体。有了字体文件后,就可以通过这样的 css 代码来实现上面的效果:

@font-face {
  font-family: price; /* 字体名称 */
  src: url('./font.eot'); /* 字体地址 */
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
.price-num {
  font-family: price;
}
1
2
3
4
5
6
7
8
9
10

接下来就详细介绍一下如何使用 icomoon 生成字体

打开icomoon (opens new window),页面如下:

icomoon页面

点击右上角的 Import Icons,选择想要上传的 icon。前面提到了,只支持上传 svg。上传成功后,可以在页面的顶部看到自己上传的 svg:

图片上传完毕

选中所有想导出的 svg,点击右下角的 Generate Font,跳转到编码页面:

调整编码页面

这里就需要插入本篇文章的主题了:icon font 的原理。 网页上显示的字,本质上就是一个个图形。 浏览器通过读取字符编码,寻找字库里对应编码的图形,把这个图形显示在页面上。 icon font 实际上就是生成一个包含 icon 的字体。 有了字体就需要有编码,这样浏览器才能通过编码找到对应的图形并渲染出来。 但这个编码值不能随便定义,如果使用了正常文字的编码区。 比如给 doge 图编码为 0,那么所有应用这个字体的元素,里面的 0 都会变成doge 图。 这样会影响页面的本义,所以 icon font 生成的字体一般使用 e000-f8ff 这段编码区。 这段区域是 unicode 里 BMP 的私用区(Private Use Area)。 在点击 Get Code 时,展示的就是如何让浏览器显示对应的编码:

查看代码

这里的 html 和 css 很容易懂,一个 class 为 icon-1 的 span,icon-1 有一个伪元素,包含一个编码为 e903 的字符。 这种使用方式是为了方便定义 icon 的大小。 但因为需求是替换掉本来字体的 0-9,所以这里需要把这里的编码改为 0-9。 修改对应右侧的空白符为 0-9,这样就可以了:

修改编码

这里的¥字符右下角标记了一个 Multicolor,无法改成一个单一的字符。点击 Get Code 就能发现小秘密:

多颜色字的编码

这里可以看出,这个图居然占用的三个字符。打开对应的 svg 文件,可以看到:

<svg width="22px" height="25px" viewBox="0 0 22 25">
    <!-- ... -->
      <g id="¥" fill-rule="nonzero">
          <use fill="#474747" xlink:href="#path-1"></use>
          <use fill="#FF6422" xlink:href="#path-1"></use>
      </g>
      <rect id="矩形" fill="#FF6422" x="1.609375" y="17.65625" width="18" height="4"></rect>
    <!-- ... -->
</svg>
1
2
3
4
5
6
7
8
9

这里用不同的颜色画了三条路径。 把 svg 里所有的颜色改为同一种颜色,再次上传就没问题了。 回头看上图的 html 代码,可以发现这里是把不同颜色的路径单独渲染成一个字符,通过不同字符的叠加来时间颜色的改变。 所以当想要使用多颜色的 icon 时,需要注意可能会占用超过一个字符这个特点。 点击右下角的下载,字体就生成好啦。 打开下载的压缩包,里面贴心的放了 demo.html 和 style.css,可以拿来参考。

但是打开压缩包里生成的 css,发现事情好像并没有这么简单:

@font-face {
  font-family: 'icomoon';
  src:  url('fonts/icomoon.eot?87qrfj');
  src:  url('fonts/icomoon.eot?87qrfj#iefix') format('embedded-opentype'),
    url('fonts/icomoon.ttf?87qrfj') format('truetype'),
    url('fonts/icomoon.woff?87qrfj') format('woff'),
    url('fonts/icomoon.svg?87qrfj#icomoon') format('svg');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}
1
2
3
4
5
6
7
8
9
10
11

@font-face 里的 src 有些复杂,都是什么意思呢?

两个 src 这里比较容易理解。 第一个 src 是为了保证在低版本浏览器中不支持多 src 属性值时的备用属性。 但因为没有查到多 src 属性值的兼容性,所以这个兼容究竟是锦上添花还是画蛇添足需要再斟酌。 第二个 src 就是为了兼容更多的浏览器,设置了四种字体。接下来简单介绍一下这四种字体格式:

  • eot: IE 专用字体。 在caniuse (opens new window)上可以看到,IE 6 - 8 只支持 eot 格式的字体。 但因为caniuse并不展示 IE 6 以下版本IE的兼容性,其实在 IE 4 中就可以用 eot 格式的字体设置 @font-face 了。
  • svg: iOS Safari 3.2 - 4.1 只支持 svg 格式的字体。 这是十年前的 iOS Safari 版本,属于古董级的向下兼容了。
  • ttf: Windows 和 Mac 中最常见的字体。 ttf 格式的字体无法压缩,所以体积会比较大。 与之相对的,这个字体的兼容性会更好一些。 除了上面提到的 IE 6 - 8iOS Safari 3.2 - 4.1 不支持,也就只有老旧的 Firefox 2 - 3Android Browser 2.1,及时代的眼泪 Oprea Mini。 另外需要指出的是,IE 9 - 11 需要添加 installable 属性才能支持这个字体格式。 参考链接 (opens new window)
  • woff: Web 字体中最佳格式。 woff 字体均经过 woff 的编码工具压缩,文件大小一般比 ttf 小 40%。 当然,兼容性就不如 ttf 了。 比起 ttf,不支持 woff 的浏览器除了 IE 6 - 8iOS Safari 3.2 - 4.3Firefox 2 - 3.5Oprea Mini,还有 chrome 4 以下Safari 3.1 - 5(Mac 系统的 Safari)、Oprea 10.1 以下Android Browser 2.1 - 4.3。 woff对 IE 9 - 11 的支持并不需要添加额外的设置。 woff 的兼容行确实比 ttf 差了些,但前面标注的浏览器也基本都是些 6、7 年前的浏览器了,市场占有率不超过 3%。 参考链接 (opens new window)

根据上面的分析,假如项目中不需要向下兼容到特别特别老的浏览器版本,其实可以把@font-face的src属性简化成:

@font-face {
  font-family: 'icomoon';
  src: url('fonts/icomoon.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}
1
2
3
4
5
6
7

这样其实已经可以兼容大部分的现代浏览器了。 当然,如果有需要,可以按照上面分析的结论,引入相应的字体文件进行兼容。

# 结尾

本文主要介绍了 icon font 的使用方法和原理。 如果还想了解更多可以看这篇SVG vs Image, SVG vs Iconfont (opens new window),可以帮助你选择更适合你的应用场景的方案。 如果你想深入了解@font-face规则,那么这篇真正了解CSS3背景下的@font face规则 (opens new window)或许能帮到你。