從 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說

百度官方技術公衆號上線啦!

技術乾貨 · 行業資訊 · 線上沙龍 · 行業大會

招聘信息 · 內推信息 · 技術書籍 · 百度周邊

歡迎各位同窗關注

相關文章
相關標籤/搜索