iconfont的原理与实践

970 阅读6分钟

icon font 图标字体,也成为字体图标,顾名思义就是使用字体做的图标,受到近些年来 扁平化设计 的影响,越来越多的图标开始使用 iconfont。

由于它本质上是特殊的字体库webFont,可以使用 font-size 和 color CSS属性控制大小和颜色,因此相对于传统img图标,具有以下优点:

  • 自由变化大小,不会模糊
  • 加载快,和 CSS spirit 异曲同工的作用
  • 可以自定义调整颜色

缺点也比较明显:

  • 字体库维护比较复杂
    例如你寻找设计同学给你提供个图片相对容易,但是如果让其提供字体库就比较困难了,调整图片需要专业的软件和工具
  • 既然是字体,那么你做的图标效果就比较有限了
    例如只支持单色和CSS3的渐变色
  • 在无障碍阅读场景下,需要做额外的工作
    对HTML来看它也就是span::before增加了一个文字,无法传达它的真实含义

作为一名前端开发者,如何从开发角度去维护和开发实践iconfont呢?接下来我将从自己的实践出发给大家介绍下流程。

传统WebFont

在网页开发中常用的字体有 微软雅黑、宋体、Aria 等,可以在 CSS 的 font-family 属性进行指定;

font-family: PingFang SC;

font-style: normal;

font-weight: normal;

如果是指定特殊字体,例如iconfont 则需要使用 @font-face 来引入特殊字体。

@font-face {
  [ font-family: <family-name>; ] ||
  src: <source> [<format>][,<source> [<format>]]*;
  [ unicode-range: <unicode-range>; ] ||
  [ font-variant: <font-variant>; ] ||
  [ font-feature-settings: <font-feature-settings>; ] ||
  [ font-variation-settings: <font-variation-settings>; ] ||
  [ font-stretch: <font-stretch>; ] ||
  [ font-weight: <font-weight>; ] ||
  [ font-style: <font-style>; ] ||
  [ size-adjust: <size-adjust>; ] ||
  [ ascent-override: <ascent-override>; ] ||
  [ descent-override: <descent-override>; ] ||
  [ line-gap-override: <line-gap-override>; ]
}

其中需要关注的是:

  • src 属性可以通过 url() 加载远程字体,也可以通过 local() 语法指定用户本地计算机的字体。
  • format:字体的格式,主要用来帮助浏览器识别,其值主要有以下几种类型:truetype , opentype , truetype-aat , embedded-opentype , avg
  • weight和style:weight定义字体是否为粗体,style主要定义字体样式,如斜体。

其浏览器兼容性不错,IE 6 以上均可以良好的支持。这里列举下其兼容写法。

@font-face {
  font-family: 'industry';
  src:  url('fonts/industry.eot?am645w');
  src:  url('fonts/industry.eot?am645w#iefix') format('embedded-opentype'),
    url('fonts/industry.ttf?am645w') format('truetype'),
    url('fonts/industry.woff?am645w') format('woff'),
    url('fonts/industry.svg?am645w#industry') format('svg');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}

其文件格式支持 .eot、.ttf、.woff、.svg 格式,其中 svg 显而易见是前端同学最容易操控的字体创作格式,本文也是采用以 svg 为基础来进行创作和管理的,和设计进行合作也是基于svg图标格式,通过一些免费工具进行转化和生成 iconfont。

iconfont的制作

传统字体制作需要专业的软件,例如FontLab、TypeTool、FontCreator之类的专业软件。这类软件的学习成本相对比较高,这里推荐 阿里妈妈UED开发的 Iconfont-阿里巴巴矢量图标库 去选择多种多样的图标,然后导出代码得到如下文件结构。

将iconfont.css 引入到项目中,通过对 span 标签设置 iconfont 和 icon-xxx 两个 CSS 类名即可。

在现实的开发场景中,前端开发往往适合设计组同事一起合作定制网站的整体样式主题,设计组同事会提供大量的设计精巧的小图标。设计希望每个图标只需要一个矢量图形,大小颜色由前端自己适配;前端同样希望每个图标能够适配尽可能多的场景,来减少图片加载的性能开发,没有比iconfont更合适的,两者一拍即合开干。

那么问题来了,设计同事提供的图片集合并不是 iconfont,无法直接使用,那么我们需要探索一种优秀的字体图标的生成和管理方案。

生成 iconfont 的图片大多采用 svg 图片格式(如下图),矢量图形的特性最契合 iconfont,但是也有其他图片类型来生成的,例如 Iconfont-阿里巴巴矢量图标库 支持 C4D 或者 BLEND 导出的 PNG 来生成 3D 插画。

<svg width="98" height="84" viewBox="0 0 98 84" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path fill-rule="evenodd" clip-rule="evenodd" d="M52 0H48V16H52V0ZM83.8549 26H14.156L0 51.8V84H98V51.8L83.8549 26ZM61.414 52.5838C61.414 59.4405 55.8628 65 49.0054 65C42.1481 65 36.5969 59.4405 36.5969 52.5838H6L17.2983 31H80.7017L92 52.5838H61.414ZM18 8.04612L21.0461 5L32 15.9538L28.9538 19L18 8.04612ZM75.9538 5L65 15.9539L68.0462 19L79 8.04623L75.9538 5Z" fill="#E6E6E6"/>
</svg>

托管平台就比较多了,可以选择 Iconfont-阿里巴巴矢量图标库iconmoon。本文选择了 iconmoon,主要是因为其导出的iconfont中包含多种字体文件(如下图),其中提供 svg 的字体文件,这个给予我们前端通过 svg API 和 线性代数进行二次加工的机会(后续文章会介绍如何使用这些知识对图形进行简单的转换),本身也是免费的工具。

这两者的使用教程就不做具体介绍了,有兴趣的可以自行去官网探索,相关教程都是挺完备的。

踩的那些坑

在实际使用过程中,发现两个平台在上传 SVG 会发生部分图标的部分内容丢失的情况,例如下面两个的 SVG 图形- wenda图标和setting图标,

上传之后发现部分内容丢失了,

经过排查发现,使用 stroke 实现的图形都会被忽略,而且两个平台都是如此,他们也是推荐使用 fill 来实现,可能事情并不简单。

<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.89355 2.87899C9.96097 2.84726 10.039 2.84726 10.1065 2.87899L16.6425 5.95476C16.6526 5.95953 16.6575 5.96354 16.6595 5.96534C16.6616 5.96728 16.6631 5.96912 16.6644 5.97126C16.6673 5.9761 16.6712 5.98598 16.6712 6C16.6712 6.01402 16.6673 6.02391 16.6644 6.02874C16.6631 6.03088 16.6616 6.03272 16.6595 6.03466C16.6575 6.03646 16.6526 6.04046 16.6425 6.04524L10.1065 9.12101C10.039 9.15274 9.96097 9.15274 9.89355 9.12101L3.35754 6.04524C3.34739 6.04047 3.3425 6.03646 3.34051 6.03466C3.33837 6.03272 3.3369 6.03088 3.3356 6.02874C3.33268 6.0239 3.32883 6.01402 3.32883 6C3.32883 5.98598 3.33268 5.97609 3.3356 5.97126C3.3369 5.96912 3.33837 5.96728 3.34051 5.96534C3.3425 5.96354 3.34739 5.95954 3.35754 5.95476L9.89355 2.87899Z" stroke="#6C727D" stroke-width="1.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 15.0371L9.69933 18.1868C9.89097 18.2707 10.1089 18.2707 10.3006 18.1868L17.5 15.0371V13.3998L9.99995 16.6811L2.5 13.3998V15.0371Z" fill="#6C727D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 11.0371L9.69933 14.1868C9.89097 14.2707 10.1089 14.2707 10.3006 14.1868L17.5 11.0371V9.39978L9.99995 12.6811L2.5 9.39983V11.0371Z" fill="#6C727D"/>
</svg>

日子还要过下去,办法总比困难多。

首先找到了一个在线的转化工具 svg-convert-stroke-to-fill,将两个图标转化后再次上传的结果如下:

解决但又没完全解决,wenda图标还是保持现状。对比了 SVG 内容发现wenda图标的有个 path 既有 fill 也有 stroke 的设计,而转化工具只是把 stroke 的相关属性移除,导致图形未能完全转化。

<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 11.25C10.8245 11.25 10.6546 11.3115 10.5199 11.4238L8 13.5237L5.48014 11.4238C5.34535 11.3115 5.17545 11.25 5 11.25H2C1.30964 11.25 0.75 10.6904 0.75 10V2C0.75 1.30964 1.30964 0.75 2 0.75H14C14.6904 0.75 15.25 1.30964 15.25 2V10C15.25 10.6904 14.6904 11.25 14 11.25H11Z" fill="#F7F8FA" stroke="#6C727D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="4" cy="6" r="1" fill="#6C727D"/>
<circle cx="12" cy="6" r="1" fill="#6C727D"/>
<circle cx="8" cy="6" r="1" fill="#6C727D"/>
</svg>

经过分析发现转化的 svg-fixer 算法对 fill 和 stroke 同时存在的情况处理的存在问题。其算法处理逻辑如下:

针对这种情况,我们将第一条 path 的 fill 属性重复放到新的一条 path 上,这样在分析的时候则会保留 fill 的属性。

<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 11.25C10.8245 11.25 10.6546 11.3115 10.5199 11.4238L8 13.5237L5.48014 11.4238C5.34535 11.3115 5.17545 11.25 5 11.25H2C1.30964 11.25 0.75 10.6904 0.75 10V2C0.75 1.30964 1.30964 0.75 2 0.75H14C14.6904 0.75 15.25 1.30964 15.25 2V10C15.25 10.6904 14.6904 11.25 14 11.25H11Z" fill="#F7F8FA" />
<path d="M11 11.25C10.8245 11.25 10.6546 11.3115 10.5199 11.4238L8 13.5237L5.48014 11.4238C5.34535 11.3115 5.17545 11.25 5 11.25H2C1.30964 11.25 0.75 10.6904 0.75 10V2C0.75 1.30964 1.30964 0.75 2 0.75H14C14.6904 0.75 15.25 1.30964 15.25 2V10C15.25 10.6904 14.6904 11.25 14 11.25H11Z" fill="#F7F8FA" stroke="#6C727D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="4" cy="6" r="1" fill="#6C727D"/>
<circle cx="12" cy="6" r="1" fill="#6C727D"/>
<circle cx="8" cy="6" r="1" fill="#6C727D"/>
</svg>

最终通过简单的颜色调整,完成了 stroke to fill 的转化。

<svg width="16" height="15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.525.078C.816.253.266.803.071 1.533l-.058.214v4.266c0 3.959.004 4.28.046 4.446a2.059 2.059 0 0 0 1.408 1.465c.196.059.231.06 1.88.075l1.68.014 1.3 1.085C7.702 14.245 7.734 14.267 8 14.267s.298-.022 1.673-1.169l1.3-1.085 1.68-.014c1.649-.015 1.684-.016 1.88-.075a2.051 2.051 0 0 0 1.408-1.465c.042-.166.046-.487.046-4.446V1.747l-.058-.214C15.743.839 15.29.356 14.587.102L14.413.04 8.067.035C2.03.03 1.71.033 1.525.078M14.18 1.535a.513.513 0 0 1 .276.257c.049.096.051.222.051 4.212v4.112l-.057.117a.501.501 0 0 1-.165.189l-.108.071-1.708.016c-1.198.011-1.745.026-1.831.048-.289.076-.463.2-1.543 1.1-.588.489-1.079.89-1.092.89-.012 0-.499-.396-1.082-.88-.582-.484-1.126-.919-1.21-.966-.318-.179-.291-.177-2.178-.193-1.918-.016-1.81-.004-1.953-.22l-.073-.111L1.5 6.035c-.007-4.034-.006-4.145.044-4.242a.52.52 0 0 1 .27-.257c.082-.036.803-.041 6.18-.042 5.436-.001 6.098.004 6.186.041M3.76 5.048a1.008 1.008 0 0 0-.654.539c-.068.141-.078.189-.078.413-.001.22.008.273.07.405.094.202.253.368.455.478.16.086.179.09.447.09s.287-.004.447-.09c.202-.11.361-.276.455-.478.062-.132.071-.185.07-.405 0-.223-.01-.272-.077-.412-.202-.417-.716-.661-1.135-.54m4 0a1.008 1.008 0 0 0-.654.539c-.068.141-.078.189-.078.413-.001.22.008.273.07.405.094.202.253.368.455.478.16.086.179.09.447.09s.287-.004.447-.09c.202-.11.361-.276.455-.478.062-.132.071-.185.07-.405 0-.223-.01-.272-.077-.412-.202-.417-.716-.661-1.135-.54m4 0a1.008 1.008 0 0 0-.654.539c-.068.141-.078.189-.078.413-.001.22.008.273.07.405.094.202.253.368.455.478.16.086.179.09.447.09s.287-.004.447-.09c.202-.11.361-.276.455-.478.062-.132.071-.185.07-.405 0-.223-.01-.272-.077-.412-.202-.417-.716-.661-1.135-.54" fill="#6C727D" fill-rule="evenodd"/>
</svg>

后来也发现了在 stack overflow 上有人也提到过 glyph 转化成白色的问题,这里的坑大概率还是有的,也就萌生了自己做个针对 iconfont 场景下的 svg-stroke-to-fill 的想法,目前也在研究 svg d 属性的细节,持续探索中,如果有成果的话一定再出一篇文章(flag已立>_<)。

附录:

www.iconfont.cn/

icomoon.io/

github.com/FortAwesome…

github.com/oslllo/svg-…

github.com/davestewart…