前言
本篇文章其實陸陸續續寫了快半年,主體部分寫好了很久了,但由于種種原因一直沒有發布。
首先來說說寫這篇文章的主要初衷是:在做前端后臺項目的時候經常會用到很多 icon 圖標,剛開始還好,但隨著項目的不斷迭代,每次修改添加圖標會變得很麻煩,而且總覺得不夠優雅,就開始琢磨著有啥簡單方便的工作流呢?
演進史
首先我們來說一下前端 icon 的發展史。
遠古時代
在我剛開始實習時,大部分圖標都是用 img 來實現的。漸漸發現一個頁面的請求資源中圖片 img 占了大部分,所以為了優化有了image sprite
就是所謂的雪碧圖,就是將多個圖片合成一個圖片,然后利用 css 的 background-position 定位顯示不同的 icon 圖標。但這個也有一個很大的痛點,維護困難。每新增一個圖標,都需要改動原始圖片,還可能不小心出錯影響到前面定位好的圖片,而且一修改雪碧圖,圖片緩存就失效了,久而久之你不知道該怎么維護了。
font 庫
后來漸漸地一個項目里幾乎不會使用任何本地的圖片了,而使用一些 font 庫來實現頁面圖標。常見的如 Font Awesome ,使用起來也非常的方便,但它有一個致命的缺點就是找起來真的很不方便,每次找一個圖標特別的費眼睛,還有就是它的定制性也非常的不友善,它的圖標庫一共有675個圖標,說少也不少,但還是會常常出現找不到你所需要圖標的情況。當然對于沒有啥特別 ui 追求的初創公司來說還是能忍一忍的。但隨著公司的壯大,來了越來越多對前端指手畫腳的人,喪心病狂的設計師,他們會說不!這icon這么丑,這簡直是在侮辱他們高級設計師的稱號啊!不過好在這時候有了iconfont 。
iconfont
一個阿里爸爸做的開源圖庫,人家還有專門的 github issue(雖然我的一個 issue 半年多了也沒回應/(ㄒoㄒ)/~),但人家的圖標數量還是很驚人的,不僅有幾百個公司的開源圖標庫,還有各式各樣的小圖標,還支持自定義創建圖標庫,所以不管你是一家創業公司還是對設計很有要求的公司,它都能很好的幫助你解決管理圖標的痛點。你想要的基本都有
iconfont 三種使用姿勢
unicode
最開始我們使用了unicode
的格式,它主要的特點是
優勢
- 兼容性最好,支持ie6+
- 支持按字體的方式去動態調整圖標大小,顏色等等
劣勢
- 不支持多色圖標
- 在不同的設備瀏覽器字體的渲染會略有差別,在不同的瀏覽器或系統中對文字的渲染不同,其顯示的位置和大小可能會受到font-size、line-height、word-spacing等CSS屬性的影響,而且這種影響調整起來較為困難
使用方法:
第一步:引入自定義字體 `font-face
@font-face {
font-family: "iconfont";
src: url('iconfont.eot'); /* IE9*/
src: url('iconfont.eot#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('iconfont.woff') format('woff'), /* chrome, firefox */
url('iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */
}
第二步:定義使用iconfont的樣式
.iconfont {
font-family:"iconfont" !important;
font-size:16px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
-moz-osx-font-smoothing: grayscale;
}
第三步:挑選相應圖標并獲取字體編碼,應用于頁面
<i class="iconfont"></i>
效果圖:
其實它的原理也很簡單,就是通過 @font-face
引入自定義字體(其實就是一個字體庫),它里面規定了
這個對應的形狀就長這企鵝樣。其實類似于 '花褲衩',在不同字體設定下長得是不同的一樣。
不過它的缺點也顯而易見,unicode
的書寫不直觀,語意不明確。光看
這個unicode
你完全不知道它代表的是什么意思。這時候就有了 font-class
。
font-class
與unicode使用方式相比,具有如下特點:
- 兼容性良好,支持ie8+
- 相比于unicode語意明確,書寫更直觀。可以很容易分辨這個icon是什么。
使用方法:
第一步:拷貝項目下面生成的fontclass代碼:
../font_8d5l8fzk5b87iudi.css
第二步:挑選相應圖標并獲取類名,應用于頁面:
<i class="iconfont icon-xxx"></i>
效果圖:
它的主要原理其實是和 unicode
一樣的,它只是多做了一步,將原先
這種寫法換成了.icon-QQ
,它在每個 class 的 before 屬性中寫了unicode
,省去了人為寫的麻煩。如 .icon-QQ:before { content: "\e604"; }
相對于unicode
它的修改更加的方便與直觀。但也有一個大坑,之前樓主一個項目中用到了兩組font-class
由于沒有做好命名空間,所有的class都是放在.iconfont
命名空間下的,一上線引發了各種雪崩問題,修改了半天,所以使用font-class
一定要注意命名空間的問題。
symbol
隨著萬惡的某某瀏覽器逐漸淡出歷史舞臺,svg-icon 使用形式慢慢成為主流和推薦的方法。相關文章可以參考張鑫旭大大的文章未來必熱:SVG Sprite技術介紹
- 支持多色圖標了,不再受單色限制。
- 支持像字體那樣通過font-size,color來調整樣式。
- 支持 ie9+
- 可利用CSS實現動畫。
- 減少HTTP請求。
- 矢量,縮放不失真
- 可以很精細的控制SVG圖標的每一部分
使用方法:
第一步:拷貝項目下面生成的symbol代碼:
引入 ./iconfont.js
第二步:加入通用css代碼(引入一次就行):
<style type="text/css">
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
第三步:挑選相應圖標并獲取類名,應用于頁面:
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xxx"></use>
</svg>
使用svg-icon的好處是我再也不用發送woff|eot|ttf|
這些很多個字體庫請求了,我所有的svg都可以內聯在html內。
還有一個就是 svg 是一個真正的矢量,不管你再怎么的放縮它都不會失真模糊,而且svg可以控制的屬性也更加的豐富,也能做出更加生動和復雜的圖標。現在ui設計師平時都喜歡使用 sketch 來工作,只要輕松一鍵就能導出 svg 了,所以 svg 也更受設計師的青睞。Inline SVG vs Icon Fonts 這篇文章詳細的比較了
svg
和 icon-font
的優劣,大家可以去看看。PS:這里其實還用到了 SVG Sprite
技術。簡單的理解就是類 svg 的似雪碧圖,它在一個 svg 之中運用 symbol 標示了一個一個的 svg 圖標,這樣一個頁面中我們遇到同樣的 svg 就不用重復再畫一個了,直接使用<use xlink:href="#icon-QQ" x="50" y="50" />
就能使用了,具體的細節可以看這篇文章開頭的文章 未來必熱:SVG Sprite技術介紹,在之后的文章中也會手摸手叫你自己如何制作 SVG Sprite
。
創建 icon-component 組件
我們有了圖標,接下來就是如何在自己的項目中優雅的使用它了。
之后的代碼都是基于 vue 的實例(ps: react 也很簡單,原理都是類似的)
//components/Icon-svg
<template>
<svg class="svg-icon" aria-hidden="true">
<use :xlink:href="iconName"></use>
</svg>
</template>
<script>
export default {
name: 'icon-svg',
props: {
iconClass: {
type: String,
required: true
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
}
}
}
</script>
<style>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
//引入svg組件
import IconSvg from '@/components/IconSvg'
//全局注冊icon-svg
Vue.component('icon-svg', IconSvg)
//在代碼中使用
<icon-svg icon-class="password" />
就這樣簡單封裝了一個 Icon-svg
組件 ,我們就可以簡單優雅的在自己的vue項目之中使用圖標了。
進一步改造
但作為一個有逼格的前端開發,怎能就此滿足呢!目前還是有一個致命的缺點的,就是現在所有的 svg-sprite
都是通過 iconfont 的 iconfont.js
生成的。
- 首先它是一段用js來生成svg的代碼,所有圖標 icon 都很不直觀。
你完全不知道哪個圖標名對應什么圖標,一臉尼克揚問號??? 每次增刪改圖標只能整體js文件一起替換。
- 其次它也做不到按需加載,不能根據我們使用了那些 svg 動態的生成
svg-sprite
。 - 自定義性差,通常導出的svg包含大量的無用信息,例如編輯器源信息、注釋等。通常包含其它一些不會影響渲染結果或可以移除的內容。
-
添加不友善,如果我有一些自定義的svg圖標,該如何和原有的
iconfont
整合到一起呢?目前只能將其也上傳到iconfont
和原有的圖標放在一個項目庫中,之后再重新下載,很繁瑣。
使用 svg-sprite
接下來我們就要自己來制作 svg-sprite
了。這里要使用到 svg-sprite-loader 這個神器了, 它是一個 webpack loader ,可以將多個 svg 打包成 svg-sprite
。
我們來介紹如何在 vue-cli
的基礎上進行改造,加入 svg-sprite-loader
。
我們發現vue-cli
默認情況下會使用 url-loader
對svg進行處理,會將它放在/img
目錄下,所以這時候我們引入svg-sprite-loader
會引發一些沖突。
//默認`vue-cli` 對svg做的處理,正則匹配后綴名為.svg的文件,匹配成功之后使用 url-loader 進行處理。
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
}
解決方案有兩種,最簡單的就是你可以將 test 的 svg 去掉,這樣就不會對svg做處理了,當然這樣做是很不友善的。
- 你不能保證你所有的 svg 都是用來當做 icon的,有些真的可能只是用來當做圖片資源的。
- 不能確保你使用的一些第三方類庫會使用到 svg。
所以最安全合理的做法是使用 webpack 的 exclude 和 include ,讓svg-sprite-loader
只處理你指定文件夾下面的 svg,url-loaer
只處理除此文件夾之外的所以 svg,這樣就完美解決了之前沖突的問題。
代碼如下
這樣配置好了,只要引入svg之后填寫類名就可以了
import '@/src/icons/qq.svg; //引入圖標
<svg><use xlink:href="#qq" /></svg> //使用圖標
單這樣還是非常的不優雅,如果我項目中有一百個 icon,難不成我要手動一個個引入么! 偷懶是程序員的第一生產力!!!
自動導入
首先我們創建一個專門放置圖標 icon 的文件夾如:@/src/icons
,將所有 icon 放在這個文件夾下。
之后我們就要使用到 webpack 的 require.context。很多人對于 require.context
可能比較陌生,直白的解釋就是
require.context("./test", false, /.test.js$/);
這行代碼就會去 test 文件夾(不包含子目錄)下面的找所有文件名以.test.js
結尾的文件能被 require 的文件。
更直白的說就是 我們可以通過正則匹配引入相應的文件模塊。
require.context有三個參數:
- directory:說明需要檢索的目錄
- useSubdirectories:是否檢索子目錄
- regExp: 匹配文件的正則表達式
了解這些之后,我們就可以這樣寫來自動引入 @/src/icons
下面所有的圖標了
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)
之后我們增刪改圖標直接直接文件夾下對應的圖標就好了,什么都不用管,就會自動生成 svg symbol
了。
更進一步優化自己的svg
首先我們來看一下 從 阿里iconfont
網站上導出的 svg 長什么樣?
沒錯雖然 iconfont 網站導出的 svg 內容已經算蠻精簡的了,但你會發現其實還是與很多無用的信息,造成了不必要的冗余。就連 iconfont 網站導出的 svg 都這樣,更不用說那些更在意 ui漂不漂亮不懂技術的設計師了(可能)導出的svg了。好在 svg-sprite-loader
也考慮到了這點,它目前只會獲取 svg 中 path 的內容,而其它的信息一概不會獲取。生成 svg 如下圖:
但任何你在 path 中產生的冗余信息它就不會做處理了。如注釋什么的
這時候我們就要使用另一個很好用的東西了-- svgo
SVG files, especially exported from various editors, usually contain a lot of redundant and useless information such as editor metadata, comments, hidden elements, default or non-optimal values and other stuff that can be safely removed or converted without affecting SVG rendering result.
它支持幾十種優化項,非常的強大,8k+的star 也足以說明了問題。
詳細的操作可以參照 官方文檔 張鑫旭大大的文章(沒錯又是這位大大的文章,或許這就是大佬吧!)本文就不展開了。
寫在最后
上面大概闡述了一下前端項目中 icon 使用的演進史。
總的來說還是那句話,適合的才是最好的。就拿之前爭論的選擇 vue react 還是 angular,個人覺得每個框架都有自己的特點和適用的業務場景,所以所有不結合業務場景的推薦和討論都是瞎bb。。。如上文其實大概講了五種前端icon的使用場景,第一種Font Awesome
不用它并不是因為它不好,而是業務場景不適合,如果你團隊沒有專門的設計師或者對 icon 的自定義度不高完全可以使用它,Font Awesome github有五萬多 star,足見社區對它的認可。還比如說,你們項目對低端瀏覽器有較高的適配要求,你還強行要用 svg 作為圖標 icon,那你真的是存心和自己過不去了。所以所有方案都沒有絕對的優與劣之分,適合自己業務場景,解決自己實際痛點,提高自己開發效率的方案就是好的方案。
占坑
本文所涉及的技術在 vue-element-admin 中可以找到完整的實例。
vue-element-admin
也發布了新版本和配套的中文文檔(文檔真的寫的我要吐血了)不管使不使用本項目都推薦一看,應該能對你寫vue的項目有所幫助。歡迎使用和提出不足。
樓主個人免費圈子。