手摸手,帶你優雅的使用 icon

前言

本篇文章其實陸陸續續寫了快半年,主體部分寫好了很久了,但由于種種原因一直沒有發布。
首先來說說寫這篇文章的主要初衷是:在做前端后臺項目的時候經常會用到很多 icon 圖標,剛開始還好,但隨著項目的不斷迭代,每次修改添加圖標會變得很麻煩,而且總覺得不夠優雅,就開始琢磨著有啥簡單方便的工作流呢?

演進史

首先我們來說一下前端 icon 的發展史。

遠古時代
在我剛開始實習時,大部分圖標都是用 img 來實現的。漸漸發現一個頁面的請求資源中圖片 img 占了大部分,所以為了優化有了image sprite 就是所謂的雪碧圖,就是將多個圖片合成一個圖片,然后利用 css 的 background-position 定位顯示不同的 icon 圖標。但這個也有一個很大的痛點,維護困難。每新增一個圖標,都需要改動原始圖片,還可能不小心出錯影響到前面定位好的圖片,而且一修改雪碧圖,圖片緩存就失效了,久而久之你不知道該怎么維護了。

image

font 庫
后來漸漸地一個項目里幾乎不會使用任何本地的圖片了,而使用一些 font 庫來實現頁面圖標。常見的如 Font Awesome ,使用起來也非常的方便,但它有一個致命的缺點就是找起來真的很不方便,每次找一個圖標特別的費眼睛,還有就是它的定制性也非常的不友善,它的圖標庫一共有675個圖標,說少也不少,但還是會常常出現找不到你所需要圖標的情況。當然對于沒有啥特別 ui 追求的初創公司來說還是能忍一忍的。但隨著公司的壯大,來了越來越多對前端指手畫腳的人,喪心病狂的設計師,他們會說不!這icon這么丑,這簡直是在侮辱他們高級設計師的稱號啊!不過好在這時候有了iconfont

iconfont
一個阿里爸爸做的開源圖庫,人家還有專門的 github issue(雖然我的一個 issue 半年多了也沒回應/(ㄒoㄒ)/~),但人家的圖標數量還是很驚人的,不僅有幾百個公司的開源圖標庫,還有各式各樣的小圖標,還支持自定義創建圖標庫,所以不管你是一家創業公司還是對設計很有要求的公司,它都能很好的幫助你解決管理圖標的痛點。你想要的基本都有

image

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">&#xe604;</i>

效果圖:

image

其實它的原理也很簡單,就是通過 @font-face 引入自定義字體(其實就是一個字體庫),它里面規定了&#xe604 這個對應的形狀就長這企鵝樣。其實類似于 '花褲衩',在不同字體設定下長得是不同的一樣。

image

不過它的缺點也顯而易見,unicode的書寫不直觀,語意不明確。光看&#xe604;這個unicode你完全不知道它代表的是什么意思。這時候就有了 font-class

font-class

與unicode使用方式相比,具有如下特點:

  • 兼容性良好,支持ie8+
  • 相比于unicode語意明確,書寫更直觀。可以很容易分辨這個icon是什么。

使用方法:
第一步:拷貝項目下面生成的fontclass代碼:

../font_8d5l8fzk5b87iudi.css

第二步:挑選相應圖標并獲取類名,應用于頁面:

<i class="iconfont icon-xxx"></i>

效果圖:

image.png

它的主要原理其實是和 unicode 一樣的,它只是多做了一步,將原先&#xe604這種寫法換成了.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內。

image

還有一個就是 svg 是一個真正的矢量,不管你再怎么的放縮它都不會失真模糊,而且svg可以控制的屬性也更加的豐富,也能做出更加生動和復雜的圖標。現在ui設計師平時都喜歡使用 sketch 來工作,只要輕松一鍵就能導出 svg 了,所以 svg 也更受設計師的青睞。Inline SVG vs Icon Fonts 這篇文章詳細的比較了 svgicon-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 的 excludeinclude ,讓svg-sprite-loader只處理你指定文件夾下面的 svg,url-loaer只處理除此文件夾之外的所以 svg,這樣就完美解決了之前沖突的問題。
代碼如下

image

這樣配置好了,只要引入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了。

image

更進一步優化自己的svg

首先我們來看一下 從 阿里iconfont 網站上導出的 svg 長什么樣?

image

沒錯雖然 iconfont 網站導出的 svg 內容已經算蠻精簡的了,但你會發現其實還是與很多無用的信息,造成了不必要的冗余。就連 iconfont 網站導出的 svg 都這樣,更不用說那些更在意 ui漂不漂亮不懂技術的設計師了(可能)導出的svg了。好在 svg-sprite-loader也考慮到了這點,它目前只會獲取 svg 中 path 的內容,而其它的信息一概不會獲取。生成 svg 如下圖:

image

但任何你在 path 中產生的冗余信息它就不會做處理了。如注釋什么的

image

這時候我們就要使用另一個很好用的東西了-- 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的項目有所幫助。歡迎使用和提出不足。
樓主個人免費圈子

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容