圖標雖小,裏面的門道可一點都不少。甚至可以說,圖標的演化是 Web 技術演化的一個縮影。本文將帶你回顧一下圖標簡史,瞭解一下圖標技術的來龍去脈。

古代:一個圖標一張圖

史前時代的圖標,正如我們的直覺一樣,就是一張圖片。那時候的網絡很慢,一分鐘只夠下載一個頁面,因此內容爲王,美觀是次要的,“沒什麼用”的圖標還沒有被人們視爲頁面上的必備元素。圖標個數少、使用頻率低,自然就沒人在上面花心思了。

近代:CSS Sprites(雪碧圖)

隨着網上內容迅速豐富,內容的比拼已經沒有更多花樣可玩了,於是網站的競爭轉向了“用戶體驗”領域。當然,後來內容又重新回到了舞臺中央,不過這已經是後話了。

在體驗方面追求差異化的方式很多,而在寬帶網絡還不夠普及的時代,最直觀的方面就是加載速度。 然而“一個圖標一張圖”的方式在加載速度方面受到了嚴重限制。限制主要來自兩個方面:建立連接的時間,和瀏覽器的併發下載數量限制。前者來自 HTTP 協議,而後者則來自瀏覽器的實現。

CSS Sprites

HTTP 方面的限制很容易理解,代理、DNS、握手、發送請求、TTFB 的時間對於像圖標這樣的小文件來說很可能遠超下載內容的時間。

瀏覽器的併發限制其實在技術上來說是很有必要的。如果不限制併發下載數,一方面瀏覽器就會開很多個線程,用戶的機器受不了;另一方面服務器也會收到大量的併發請求,服務器也受不了,很容易壓垮一些技術不過硬的網站。

瀏覽器限制併發下載數,就會導致超出併發限制的請求被迫進行排隊,對於圖標、圖片、css、js 等小文件很多的頁面來說,即使網速已經較快,這種排隊也可能會持續很久。

顯然,優化的方向就是減少併發下載的需求。因此,優化的方案也就顯而易見了:把各種小圖標拼合成一個大圖,然後想辦法讓瀏覽器把它重新切成多個就可以了。恰好,瀏覽器有一個特性叫 background-position ,也就是說假如我們把這張大圖設置爲當前元素(寬 w 、高 h )的背景,並且指定了 background-position(x, y) ,那麼當前元素的背景就是從大圖上 (x, y, x+w, y+h) 截取出來的那個區域。這樣一來,就把 N 個併發下載合併成了一張大圖和一個 css 文件。

這種方案如果做手動拼合是非常繁瑣的,因此有人開發了工具來整合到前端工具鏈中,並且 UX 的一些工具也逐漸提供了直接導出雪碧圖的功能。

近代:Data URL

除了拼合成雪碧圖之外,還有另一種技術也可以減少併發下載請求,那就是 Data URL。

簡單來說,Data URL 就是這樣的形式: data:[<mediatype>][;base64],<data>, 比如 data:image/svg+xml;utf8,<svg ... > ... </svg> ,也可以對 data 部分進行 base64 編碼,比如 data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo

Data URL

對於瀏覽器來說,Data URL 和普通的 http URL 沒有什麼區別 —— 除了不用額外下載。因此,凡是能用 http URL 的地方都可以換成 Data URL,比如 html 中的 <img src="..."/> ,css 中的 background-image: url(...) 。這樣一來,就把圖標的下載合併到了 html/css 的下載過程中。

但是,這種方式也有缺點,那就是拖慢了整體渲染速度。

通常來講,瀏覽器的下載優先級是 html > css > 圖片等資源的,因此我們經常看到一個網站展現出來之後,裏面的圖片還只顯示了一半,過一會兒纔會完全顯示。如果我們把大量圖標塞到 css 甚至 html 中,就會增大它們的體積,導致首屏展現變慢。

所以,是否使用 Data URL 技術需要仔細權衡,根據性能測量數據進行優化。

現代:字體圖標

隨着視網膜屏幕的登場,圖標面臨着新的嚴峻挑戰,那就是分辨率。

通常來說,圖標文件的分辨率和屏幕的邏輯分辨率是一樣的,但是在視網膜屏下,這個論斷不再成立。如果視網膜屏的設備像素比( devicePixelRatio ,簡稱 dpr)是 3,那麼圖標就需要三個像素才能在視網膜屏下繪製出一個完美的邏輯像素,否則就會有粗糙感。

即使不考慮下載大小的問題,也需要對原有工具鏈進行改造纔行。那不如干脆試試另一種方案。

近代的另一項發明派上了用場,那就是“自定義字體”。

這本來是爲了解決讓瀏覽器顯示更好看的文字而創造的技術,比如要想用一種用戶機器上沒有的字體顯示藝術字,我們只需要提供一個字體文件,這些字體文件包含我們要用的那些文字的字體輪廓數據就可以了。這些輪廓數據是矢量數據,用來表示每個字的“畫法”:從 0,0 開始,以 50%,10% 爲控制點,畫一條貝塞爾曲線到 100%,30% 。顯然,這種數據是不會受到屏幕分辨率影響的,就像我們日常看到的文字一樣,無論把它放到多大,它都是平滑而且不失真的。事實上,這正是一切矢量繪圖技術共同的優點。請記住它,因爲後面我們還會用到另一種矢量繪圖技術。

既然我們可以通過控制顯示數據,把字母 A 顯示爲手寫體的 A,那麼我們是不是也可以把它顯示成一個看起來和 A 完全不一樣的圖標呢?比如……一座房子?當然可以,事實上,這正是字體圖標的基本原理。

除了支持平滑縮放的優勢以外,字體圖標還有另一個優勢,那就是它本身就是文字。它會受到字號、前景色、行高等參數的控制,和普通文字沒有任何區別。而圖標,在實際應用中經常會和普通文字一起混排,這些特點正是我們想要的。

字體圖標

不過,字體圖標也有一些缺點。

首要的缺點是單色。由於字體中只有矢量數據,沒有顏色數據,因此,字體圖標必然是單色的。這在一些場景下是不夠用的。

其次是工具鏈複雜。雖然有一些工具可以幫你把一組 svg 文件拼合成一個字體文件,但是它們對 svg 的格式有嚴格的要求,不是任何一個 svg 都可以用的。你很難向 UX 解釋什麼樣的圖能用、什麼樣的圖不能用。其次,即使是可用的 svg,你也很難告訴工具每個圖標的字體基線是哪個(通俗來說,基線就是你這個圖標的底部和字母 g 的底部對齊,還是和字母 h 的底部對齊)。

基於這些特點,在普通的團隊中使用自定義字體圖標是相當困難的。不過好在還有不普通的團隊,比如 FontAwesome,他們專門製作、維護了一組免費圖標貢獻給開源社區。如果你需要的圖標恰好是其中之一,那麼直接用就可以了,你需要做的只是引入它的 css 之後,在 html 中使用 <i class="fa fa-home"></i>

當代:svg 圖標

FontAwesome 雖好,但也不是萬能的。它往往不足以融入 UX 的 Design System,而 UX 顯然也不願意削足適履,爲了圖標而改變自己的整體設計。

因此,對開發團隊更友好的方式仍然應該是高度可定製的、方便單個處理的。如果能直接使用 UX 提供給我們的 svg 文件顯然是最理想的。問題在於,該怎麼用。這裏面的門道可就多了。

圖標

在這種場景下雪碧圖和 Data URL 仍然是可用的,因爲它們只需要圖片,而不管圖片的格式,svg 也是圖片,也有同樣的優缺點 —— 但能支持視網膜屏。

不過,svg 的特點,讓我們還有了一些另外的用法。

首先,可以把 svg 內聯到 html 中。svg 和 html 在語法上非常像,都是 xml 語系,只是使用了不同的命名空間(xmlns),因此我們可以把 svg 作爲一個元素內聯到 html 中,現代瀏覽器可以正確地解釋它們。這種用法比較自然,html 中引入的 css 也同樣可以作用於 svg 內部的元素上,圖文可以無縫整合在一起。

不過這種用法有兩個問題。其一是 svg 中各個元素的 id 會併入頁面的命名空間中,比如在 svg 中引用了一個名爲 a 的過濾器,那麼如果 html 或另一個 svg 中也定義了它,就會互相沖突。在稍大點的項目中要解決這種衝突會相當麻煩。其二是如果這個圖標出現很多次,它的內容就會在 html 中重複很多遍,體積也會相應的增大。

好在,svg 有一種機制可以解決這個問題,也就是 use 標籤。使用 use 標籤,你可以根據 id 引用本頁面中的 svg 元素,甚至來自其它 svg 文件中的元素。比如要引用本頁面中的 id 爲 arect 元素,你只需要寫 <use xlink:href="#a"> 即可,並且在這裏你可以指定自己的 svg 屬性,以覆蓋原始元素上的 svg 設置。這樣一來,圖標內容被重複很多遍的問題就解決了。如果寫成 </use><use xlink:href="path/to/file.svg#a"> 則可以引用外部文件 path/to/file.svg 中定義的元素,那麼 id 衝突的問題也同樣解決了,因爲它們不在同一個命名空間。

不過,這種方式相對於字體圖標還有兩個缺點:

一是圖標的顏色不會自動跟隨文字顏色。比如原始元素定義的 rect 是紅色的,那麼無論你把它混排到什麼顏色的文字中,它都是紅色的。難道我們要在每個使用它的地方都手動覆蓋一下顏色嗎?當然不必,我們還有另一個特性可以解決這個問題,那就是 currentColor 。這是一個預定義的特殊顏色值,它的意思就是取當前的文字顏色。比如當你寫 <rect fill="currentColor"></rect> 時,把它混排到灰色文字中,這個 rect 的填充色就是灰色的,混排到藍色文字中就是藍色的。而且,這個圖標的其它部分你仍然可以指定特定的顏色,比如圖標主體部分跟隨文字顏色,而某個特殊區域總是顯示爲藍色。 經過這樣的處理之後,你不但可以彌補相對於字體圖標的缺點,還可以更進一步,支持彩色圖標了!即使你不需要彩色圖標,憑藉 svg 對元素透明度的支持,也可以讓你的圖標比字體圖標更加豐富多彩。

二是圖標的大小不會自動跟隨字體大小。不過這個就好解決了,因爲 css 中有一個特性就是把當前字號作爲尺寸單位,也就是 em ,比如圖標大小設置爲 1em 就會讓圖標的實際尺寸跟當前字號一致。

當代:合字(Ligature)

你知道“囍”字嗎?嚴格來說,它不是一個字,而是一個“合字”。也就是說這是兩個漢字,只是顯示成了一個字的樣子。只是因爲它非常常見,所以在字庫中給了它一個單獨的位置。但是大多數類似的文字是得不到這種特殊待遇的,比如“孔孟好學”的合體,以及“biangbiang面”中的“biang”字;字母上的聲調(比如漢語拼音)也是合字。

那麼,要如何用標準的方式來顯示這些合字呢?實際上,現代的字體庫早就已經支持合字了,只是在現實中用得不多,一般人沒怎麼注意罷了。不過,在圖標領域,它重新找回了用武之地。我所知道的最早使用合字的圖標體系是 Google 的 Material Design,比如用 <i class="material-icons">home</i> 就可以顯示出一座房子,它是怎麼工作的呢?實際上, material-icons 類爲這個 i 元素指定了一個支持合字的字體庫: 'Material Icons' ,然後就會在字體庫中檢索出 home 這個合字對應的單字,並且把那個單字顯示出來就可以了。換句話說, home 是某個單字的別名。

但是,我們爲什麼不像 FontAwesome 那樣直接引用這個單字,而要用合字中轉一次呢?在回答這個問題之前,我們先要知道一個概念,那就是:

訪問互聯網並不是我們這些健全人的專利!

世界上有很多殘疾人,特別是視障人士,比如盲人、弱視等,甚至等我們老了都有可能加入他們的行列。他們訪問互聯網時難以像我們一樣憑視覺閱讀網頁,而需要藉助一種屏幕閱讀器。

屏幕閱讀器無法理解某個單字表示的是房子形狀的圖標,因此頁面的編寫者就需要給這個圖標加上特殊的 aria-label 等屬性,以便屏幕閱讀器朗讀它們。這稱爲 Accessibility(無障礙),簡稱 a11y。回想一下,你加過幾個這種屬性?很多人都不加,因爲麻煩。但使用合字就不需要考慮這種問題了,因爲合字本身就是可讀的,在 html 中的寫法就像普通文本一樣。所以,你只要自然而然的使用合字,就已經滿足了 a11y 的一些要求。

因此,雖然“合字”本身沒有多少新的技術,但是我仍然把它歸於“當代”,它值得作爲一種趨勢受到重視。

圖標在開發中的其它方面

在實際的開發工作中,還有一些問題需要考慮。

圖標開發

第一個問題是搖樹優化,也就是說,我們沒有實際使用到的圖標應該自動被優化掉,而不應該讓我們手工檢查哪些圖標沒用到,並且從源碼中刪掉。前面的大多數方案都難以給出完美的答案,只有內聯 svg 方式是一個相對理想的方案。簡單來說,寫一個構建工具,當你在 html 中發現了一個 <img src="path/to/file.svg"/> 時,把這個 svg 文件的內容讀出來,並且內聯到 html 中。這樣,只要一個文件從未被引用過,就會自動優化掉。如果你用基於 WebPack 的構建工具,可以引入我寫的一個 “markup-inline-loader”。當然,如果你使用 Angular 這樣的現代框架,你就不需要爲此做什麼額外的工作了。你只要把每個圖標做成一個組件,使用 svg 內容作爲模板,然後像普通組件一樣引用它就可以了。Angular 會自動幫你優化掉沒有引用過的組件。

第二個問題是 SPA。現代的前端應用基本上都是單頁面應用(SPA),因此往往並不需要同時下載大量的圖標,而是按需加載。因此,“古代”那種“一個圖標一張圖”的方式未必就真的不可接受,針對你的實際業務場景,做一下鏈路分析,它沒準反倒是最合適的方案。

第三個問題是 svg 文件本身的優化。很多工具導出的 svg 文件很囉嗦,裏面有很多對於顯示沒有意義的東西。一些 svg 圖標即使減小到原來體積的一半兒都不會影響顯示,因此,針對 svg 本身做一些優化也是有價值的。當然,這事不必手工來做,有一個現成的工具可以做這事,它叫做 svgo ,你只要運行 npm i -g svgo 命令就可以全局安裝它了。你可以用 svgo 命令對單個文件或者整個目錄做優化;可以手工使用,也可以把它集成到工具鏈裏。

結語

這些圖標技術,雖然出現時間上有先後,但並不是簡單的替代關係,而是各有優缺點,適用於不同的場景。

隨着需求和技術條件的變化,選型策略也要做出調整,有些時候還要混合使用,以發揮各自的優勢。

相關文章