从 Web 图标演进历史看最佳实践

导读:在产品中适当使用图标,可让产品更生动,也更简洁。在前端项目中,处理和引入图标都是必不可少的环节。在 Web 产品中引入图标,大体经历过以下几个阶段:使用独立的图片来引入图标、使用 CSS sprites 技术、使用字体图标(font icons)、使用 SVG(inline SVG/SVG sprites)、在前端视图层框架中封装组件。本文将简单梳理一下图标相关的工做流程的演进,以及咱们在百度设计语言系统推动过程当中相关的一些尝试。css

全文7006字,预计阅读时间 14分钟。html

1、使用独立图片

在过去有很长一段时间,前端是经过引入图片来承载图标。在没有 CSS 支持的时代,用 <img> 标签引入图标图片是惟一的可能。前端

<a href="/contact.html">  <img src="mail.jpg" alt="email"></a>

到了 CSS 支持背景图之后,人们开始使用 background-image 来引入一个个小图片,但本质上没有改变每一个图标都使用单独图片的问题。webpack

显然,这样的方式在有不少图标的网页中将发起不少 HTTP 请求,占用浏览器的并行请求数量,致使总体加载时间缓慢,体验不好。对于有些鼠标悬浮后切换图标的设计,这种方式还会出现第一次切换时须要等待图标加载的问题。(可是使人沮丧的是,直到如今还有网站依然保留着这样的方式。)git

2、CSS Sprite

后来在大约本世纪初的头几年,人们找到了一种新的技巧:经过将图片合并技术(image sprite)引入前端,将数量众多的图标图片进行巧妙拼合,而且在样式中经过 background-position 来经过不一样位置匹配不一样的图标进行显示。例如:github

.toolbtn {  background: url(icons.png);  display: inline-block;  height: 20px;  width: 20px;}#btn1 {  background-position: -20px 0px;}#btn2 {  background-position: -40px 0px;}

虽然这种方式相较于每一个小图标一个图片文件,只会发起一次 HTTP 请求,对性能更加友好,可是依然有着以下问题:web

  • 拼合后的图片很是难以维护,须要手动精心调整。虽然也有一些自动生成“雪碧图”的工具,但因为 background-position 这种方式的限制,生成逻辑没法保证灵活适应各类可能的使用场景。算法

    图片

    图片来自https://www.smashingmagazine.com/2012/04/css-sprites-revisited/chrome

  • 当一个项目图标不少时,图片会在总体下载完之后才显示,可能会致使一段较长的时间内全部图标都没法显示。同时因为高昂的维护成本,很难作到按需加载图标,每每整站的图标都会所有合并到同一个“雪碧图”中。npm

  • 图标颜色是肯定的,没法在前端根据内容上下文灵活调整图标的颜色。

  • 图片尺寸是固定的,进行缩放后很难保证图标的显示效果。

在这个时代,设计师和工程师协做的模式通常来讲都是设计师将设计好的图标文件交付给工程师,由工程师来经过图片编辑工具或者一些雪碧图生成器来维护拼合后的图片,效率和可维护性都很是堪忧。

3、字体图标的崛起

因为图标从某种程度上来看能够被视为“象形文字”,因此当 CSS 开始支持 @font-face 引入 web font,人们马上想到了用它来载入、显示图标。从 2012 年至今,提供大量免费图标的 FontAwesome 就取得了很大的成功(后来开始商业化的 FontAwesome 5 的甚至为他们在 Kickstarter 上筹集到了一百万美金),各类字体图标平台也层出不穷。阿里的 iconfont.cn 平台从多年前开始就已经成为国内最受欢迎的图标托管、共享、管理平台。能够说字体图标时至今日仍是最热门的 web 图标方案之一。

字体图标的原理很是简单,经过占用一些 Unicode 字符编码(一般是私人使用区,U+E000-U+F8FFU+F0000-U+FFFFD 以及 U+100000-U+10FFFD 范围内)并为其绘制字形,同时生成好一堆预约义的图标名 class name,经过 web font 的方式加载资源,经过对应的 class name 来引用图标。因为各个浏览器对 web font 支持的字体格式兼容性有差别,每每须要生成多个格式的字体供浏览器进行选择性加载:

/* iconfont.cn 生成的样式文件大体以下: */@font-face {  font-family: "iconfont";  src: url('iconfont.eot'); /* IE9 */  src: url('iconfont.eot#iefix') format('embedded-opentype'), /* IE6-IE8 */  url('iconfont.woff2') format('woff2'),  url('iconfont.woff') format('woff'),  url('iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */  url('iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */}.iconfont {  font-family: "iconfont" !important;}.icon-flag:before {  content: "\e233";}

在 HTML 中使用:

<i class="icon-flag"></i>

字体图标虽然也很难维护,可是相比“雪碧图”仍是有很多明显的优点:

  • 基于轮廓字体格式的字体图标是经过贝塞尔曲线描述的,能够任意伸缩而且保持显示效果不失真,这在移动端尤其重要。

  • 字体能够轻易地使用 CSS 设置颜色。

但咱们能够看出,这个方案对使用者的工程能力已经有所要求。虽然在这个时代,多数业内前端团队已经都有了初步的工程化能力,开始使用诸如 Grunt/Gulp 甚至 webpack 等工具,基于 Node + npm 去定制各自团队的工程化方案了,可是编排每一个图标的 Unicode 编码、生成对应的 CSS 代码就已经有比较大的工做量,更别说生成这么多格式的字体文件,普通工程师根本无从下手。这也是 iconfont.cn 吸引大量用户的重要缘由。重度依赖第三方平台,本身建设成本又比较高,使得图标的可维护性依然存在必定的痛点。

另外,虽然字体图标解决了一些“雪碧图”的体验问题,它也带来了一些新问题:

  • 字体文件加载须要时间,在文件加载完成前,图标是没法显示的,内容就很容易发生闪烁。在某些浏览器下,处于私有使用区的图标在默认字体下甚至会显示为一个方块字符。

    图片

    图片来自https://github.blog/2016-02-22-delivering-octicons-with-svg/

    这一点实际上和“雪碧图”有着很大的共同点。虽然咱们可使用 data URI 来将资源内联,事实上有很长时间咱们也的确使用过将图片或者字体经过 data URI 编码后内联到 HTML 的方式来避免这个加载的时间差,可是编码自己会增长内容 1/3 左右的尺寸,实际上只能算是一种取舍和妥协。更别说字体图标须要生成如此多格式的字体,内联到 HTML 网页性能将大打折扣。

  • 可访问性问题:对于患有视力障碍使用读屏器的用户,因为字体图标实际由字符承载,不管字体是否加载完毕,读屏器都没法正常朗读其内容,在默认的状态下甚至会读出“unpronounceable”这样不符合预期的内容,能够想象若是一个网页大量使用字体图标却没有逐个标注 aria-hidden 这样的语义标记,会对读屏器用户产生多大的困惑。

4、SVG 图标

SVG 天生就带有可伸缩(SVG 中的 S)特性,很是适合用来实现图标。同时,SVG 是文本文件,同时诸多支持矢量编辑的设计工具都支持经过 SVG 导出,设计师能够直接交付给工程师使用,也再也不须要生成字体文件,大大缓解了可维护性上的痛点。但若是将它当成图片,经过 <img> 或 CSS background-image 来引入,仅仅有这些优点还不足以撼动图标字体的地位。

4.1 内联 SVG

SVG 的真正强大之处在于,当将其内联入 HTML 内容,那么它的文档模型将能够被该页面的 JS/CSS 访问和操做。这为 web 图标开启了新的篇章:

  • 能够经过 CSS 控制图标的颜色甚至具体样式,使得受业务逻辑控制的动画图标成为可能。

  • 在显示效果上,字体图标因为本质上被视为文本,将受到浏览器的文字抗锯齿算法的影响,在特定操做系统、浏览器、字体设置下视觉效果可能会不那么“保真”。而 SVG 被视为图片进行渲染,不会受文字抗锯齿算法影响,渲染效果更加原汁原味。

  • SVG 内联入 HTML 内容并不须要进行编码,重复的 SVG 内容也是对 gzip 友好的,对 HTML 加载速度的性能损耗很小。

  • 不须要发起资源请求,能够随着 HTML 内容进行流式加载和渲染,不会产生任何闪动的体验问题。

  • 图标加载能够作到彻底按需,当前页面没有用到的图标都不会输出。

  • SVG 能够经过 <title> 元素标记内容,对读屏器友好。

相比于经过图片资源加载或者图标字体,只有一个劣势:

  • 图标成为 HTML 内容的一部分,再也不能在 CSS 中指定须要使用的图标了。固然这一点从咱们的实践中来看,并不构成很大的阻碍。

虽然内联 SVG 有不少优点,可是在这个阶段,在开发时使用它们却不像字体图标那么简单直接(引入一个 CSS,前端就能任意使用),须要对工程有必定侵入性的处理。GitHub 在 2016 年全面启用了内联 SVG 的方案,他们的技术栈是 Ruby 的后端渲染,经过服务端脚本定义的 helper 函数来进行图标字体的调用:

<%= octicon(:symbol => "plus") %>

输出:

<svg aria-hidden="true" class="octicon octicon-plus" width="12" height="16" role="img" version="1.1" viewBox="0 0 12 16">    <path d="M12 9H7v5H5V9H0V7h5V2h2v5h5v2z"></path></svg>

4.2 SVG Sprite

因为 SVG 支持一个 <use> 元素,能够从内联的 SVG 中选取特定内容出来做为独立的 SVG 进行显示,因此人们受 CSS sprite 的启发,也设计了一个 SVG sprite 方案。引入整个 SVG sprite 的资源仅须要内联一个 <svg> 元素:

<svg>  <defs>    <symbol id="shape-icon-1">      <!-- icon paths and shapes -->    <symbol>    <symbol id="shape-icon-2">      <!-- icon paths and shapes -->    <symbol>    <!-- etc -->  </defs></svg>

使用时:

<svg viewBox="0 0 16 16" class="icon">  <use xlink:href="#shape-icon-1"></use></svg>

同时,也有很多基于 Grunt/Gulp/webpack 的构建方案,来快速生成 SVG sprite。

这种方式主要的问题在于:

  • 不容易按需引入图标。

  • 在各个场景使用时比较繁琐。

5、前端组件框架的时代

终于到了咱们如今所处的时代,这是一个 web 端渲染逻辑被移到前端,前端工程方向被组件化框架主导的时代。在使用 React/Vue/Angular/Svelte/…… 等各类框架的过程当中,咱们已经习惯于将视图逻辑经过组件进行拆解和复用。那么咱们很天然地就能够经过设计图标组件来对底层方案进行一层封装,暴露给前端更简单直接的 API 来使用图标。要注意的是,这并无在根本上改变 web 图标渲染的方式,底层依然是基于前文提到的各类方案。在不使用这些视图层框架的项目中,咱们依然仰赖使用上述 low-level 的实现来进行开发。

固然,从各方面综合比较,封装内联 SVG 应该是当前最佳的选择。上文 GitHub 后端 helper 的方案对应当前前端的技术方案,实际上就是基于内联 SVG 的图标组件。npm 上目前也有不少基于各个组件框架开发的图标组件,包括 FontAwesome 都已经内置了 SVG、React/Vue 组件等更现代化的方案。

既然体验问题已经由内联 SVG 获得了比较好的解决,那么在这个阶段咱们就有更多的精力去更多地考虑研发效能、一致性、开发体验的问题了。从咱们在百度内部以往的实践中来看,存在这以下的一些问题:

  • 工做流程缺少最佳实践,因为长期各个团队有着较为独立的技术演变,使用的 web 图标方案并不统一。

  • 整个大致系下跨团队的设计师并无很好地共享图标资源,存在必定的重复设计。

  • 有图标组件库,可是图标有限,业务须要新增图标时设计师每每仍是将图标线下交付给工程师,前端经过一些相似 svg-icon-loader 的方案将图标引入项目,但方案每每各不相同。一旦引入这样的流程,至关于给图标在特定项目中新增了一个 fork 版本,往后想作设计风格的统一调整就须要业务跟进修改,成本很高。

  • 针对 SVG 图标组件,咱们没有一个相似 iconfont.cn 的平台进行流程上的收拢,也没有自动化的代码包导出、发布能力。

理想状况下,咱们但愿达成以下目标:

  • 图标设计师维护图标源文件,发布之后没有任何人工干预形成流程分叉,有一个固定的图标库平台提供 single source of truth。

  • 每一个团队能根据自身技术栈,选择须要导出的组件实现类型(React/Vue/San/...)。

  • 图标组件库中的图标数据会被自动优化、压缩。

  • 图标组件库应该是能够跟随图标库的数据更新升级的。

目前咱们在推动百度设计语言系统的过程当中,和工程效能团队一块儿,设计了以下总体方案:

图片

图标平台总体流程

5.1 图标管理平台


这个平台能够视为是一个简单的图标 CMS,能够建立/管理图标库,图标设计师负责来在其中添加、管理图标。在完成数据的更新后,能够选择发布当前图标输出到 API。这个 API 返回图标库中图标的图形数据(SVG 源文件)和元数据,在整个流程中主要有两个消费者:给设计团队使用的 Sketch 插件,以及前端的编译/发布服务。咱们容许图标库发布时经过 webhook 配置须要通知的编译服务,因此有必要的话,不一样的使用方也能够选择本身自定义整套编译发布的流程。

5.2 Sketch 插件


咱们给设计团队提供了联通图标管理平台的 Sketch 插件,设计师能够在插件中快速搜索须要的图标进行使用。经过咱们的插件导出在线标注稿后,标注稿上就会自动标注图标在图标平台中的惟一标识符,这也是咱们用来生成图标组件时用的标识符,前端工程师经过它就能直接从图标组件包中引入对应的图标组件。

5.3 优化/编译/发布服务


这个服务在图标库 API 触发更新时主要作了三件事:

  1. 优化。从 API 读取图标数据,而且将源文件经过 SVGO 进行初步优化。因为咱们但愿图标组件内联到 HTML 之后能够经过 CSS 灵活修改颜色,因此对于常见的单色图标,咱们须要去除全部硬编码的颜色,在有必要时设置为 currentColor。在这一步咱们经过 svgson 遍历 SVG 元素处理相关逻辑。

  2. 编译。获得了优化过的图标数据,咱们须要根据他们来生成咱们的图标组件包。在这里咱们提供了多个框架的组件包模板,每一个模板中都已经提供了对应各自框架的图标组件工厂函数,只须要经过脚本在模板中注入图标数据,便可根据平台数据灵活生成各个业务所须要的组件包。

  3. 发布。根据在 webhook 回调路径中的配置,咱们能够指定须要发布的包的名称,描述等信息。版本号的逻辑也比较简单:

  • 删除/更名图标:major + 1

  • 新增图标:minor + 1

  • 修改图标内容:patch + 1

5.4 图标包模板

编译服务对包模板(boilerplate)仅有的约定是:

  1. 编译服务会在特定目录输出图标数据。

  2. 编译服务会依次调用特定的 npm script。

模板提供者须要提供图标组件的具体实现,以及将图标数据转换为前端代码的构建脚本。若是没有特殊的需求,直接使用咱们提供的 React/Vue 等框架下的组件模板,就能够得到高质量的前端图标组件实现了。

经过编译服务发布完成之后,前端工程师只须要知道:1. 使用的图标来自哪一个 npm 包 2. 这个图标叫什么名字,便可快速在前端项目中引入图标。同时,整个流程保证了设计师产出的设计稿、前端实现的一致,而且能够从图标平台中心化地控制升级。

6、总结

在 Web 产品中引入图标咱们前端工程师作过不少探索,也产出过不少相关的辅助工具来完善整个协做流程。在目前组件化开发的大背景下,咱们经过分析各个方案的优缺点,创建起一套当下的“最佳实践”,减小了流程中的沟通和容易出错的人工操做,高效地达成了设计和实现的一致性。最后,但愿本文的内容能给你们带来收获,谢谢。

---------- END ----------

百度Geek说

百度官方技术公众号上线啦!

技术干货 · 行业资讯 · 线上沙龙 · 行业大会

招聘信息 · 内推信息 · 技术书籍 · 百度周边

欢迎各位同窗关注

相关文章
相关标签/搜索