【實(shí)戰(zhàn)】網(wǎng)站圖標(biāo)新技術(shù)「SVG-Sprite」

# 前言

? 前端攻城獅們?cè)谧銮昂笈_(tái)項(xiàng)目的時(shí)候經(jīng)常會(huì)用到很多icon圖標(biāo),剛開始還好钮糖,用的圖片不多梅掠,沒什么感覺,但隨著項(xiàng)目迭代店归,體積與功能不斷增大阎抒,修改和添加圖標(biāo)變得比較麻煩,而且總覺的不夠優(yōu)雅消痛。

# 幾種圖標(biāo)使用辦法

  • 標(biāo)簽 <image>
    ? 想必大家多多少少都用過(guò)且叁。簡(jiǎn)單,讓設(shè)計(jì)師提供一張圖片一丟即可肄满。但其缺點(diǎn)尤為突出谴古。image作為網(wǎng)頁(yè)依賴資源存在,在頁(yè)面載入時(shí)需要去服務(wù)器獲取下載稠歉,這不僅占用了網(wǎng)絡(luò)資源鞭达,更嚴(yán)重的是拖慢了頁(yè)面的展示。

  • 雪碧圖 sprite
    ? 前幾年雪碧圖幾乎成為前端面試中必問的題沪哺,它的出現(xiàn)就是為了解決<image>占用過(guò)多下載線程牙瓢。它就是將多個(gè)圖片合成一個(gè)圖片,然后利用 css 的 background-position 定位顯示不同的 icon 圖標(biāo)阅羹。但使用過(guò)的同學(xué)都有感受勺疼,維護(hù)起來(lái)比較困難。每新增一個(gè)圖標(biāo)捏鱼,都需要改動(dòng)原圖片执庐,還可能不小心出錯(cuò)影響到前面定位好的圖片;不僅如此导梆,一修改雪碧圖轨淌,之前的緩存就失效了。

  • Iconfont
    ? 相信這是現(xiàn)階段絕大多數(shù)開發(fā)者使用的辦法看尼。在此之前递鹉,F(xiàn)ont Awesome也是主流用法之一,筆者用的較少〔卣叮現(xiàn)在的UI庫(kù)就拿 element-UI 來(lái)說(shuō)躏结,本身提供有少量高可用圖標(biāo),但依舊不能滿足日益迭代的特別是設(shè)計(jì)師的刁鉆要求狰域。阿里爸爸開發(fā)的iconfont開源圖庫(kù)提供了海量的圖標(biāo)媳拴,支持自定義圖標(biāo)庫(kù)下載黄橘,單圖標(biāo)定制顏色,下載不同類型(svg禀挫,png旬陡,ai),使用上也支持(unicode语婴,font-class描孟,symbol )三種方式
    ? 有一點(diǎn)不方便就是,你在編輯自定義圖庫(kù)時(shí)需要預(yù)知網(wǎng)站將用到些什么圖標(biāo)砰左,這太難了匿醒,往往后續(xù)需要使用添加圖標(biāo)庫(kù)或者修改已有庫(kù)的方式來(lái)擴(kuò)大圖標(biāo),好在一次性可以增加多個(gè)圖標(biāo)缠导,比雪碧圖改善了太多

  • SVG類雪碧圖 SVG-Sprite
    ? SVG-Sprite廉羔,不僅有著雪碧圖的優(yōu)勢(shì),又避免了改動(dòng)和維護(hù)困難的問題僻造,而且具有svg矢量圖標(biāo)的優(yōu)點(diǎn)憋他,集多優(yōu)勢(shì)于一身的它,必將成為網(wǎng)站圖標(biāo)的未來(lái)之星髓削。
    ?

# SVG Sprite 技術(shù)實(shí)現(xiàn)

? SVG Sprite 與普通 Sprite 具有類似思想竹挡,即將圖標(biāo)圖形整合在一起,利用CSS實(shí)現(xiàn)精準(zhǔn)定位顯示特定圖標(biāo)立膛。

1.【基礎(chǔ)】SVG Sprite與symbol元素

? 目前揪罕,SVG Sprite的最佳實(shí)踐是symbol元素。單純翻譯即“符號(hào)”宝泵,但在這不是這意思好啰。它類似于Flash中的“影片剪輯”,或者“元件”儿奶,理解為元件框往,模塊更加貼切。

? 一個(gè)干凈的 SVG Sprite 由 <svg>, <symbol>標(biāo)簽組合而成闯捎,類似如下

<svg>
  <symbol id="icon-name1">...</symbol> // 第一個(gè)圖標(biāo)路徑形狀
  <symbol id="icon-name2">...</symbol> // 第二個(gè)圖標(biāo)路徑形狀
  <symbol id="icon-name3">...</symbol> // 第三個(gè)圖標(biāo)路徑形狀
</svg>

? 如上<svg>容器就類似于雪碧圖的那張大圖搅窿,而每一個(gè)<symbol>則是每一個(gè)被實(shí)際定位顯示的圖標(biāo),SVG Sprite 使用id作為精準(zhǔn)定位依據(jù)提取目標(biāo)資源隙券,最終復(fù)制到SVG use 位置,強(qiáng)烈建議id語(yǔ)義化定義闹司。不理解的可以接著往下看娱仔。

2.【基礎(chǔ)】SVG 中的 use元素

? use元素是SVG中非常重要的一個(gè)元素,它支持在SVG內(nèi)提取目標(biāo)節(jié)點(diǎn)游桩,并在別的地方復(fù)制它們牲迫。其效果等同于這些節(jié)點(diǎn)被深克隆到一個(gè)不可見的DOM中耐朴,然后粘貼到use使用的位置。它有兩個(gè)特征:
(1)可以重復(fù)調(diào)用
? SVG是代碼型矢量圖片盹憎,其占用代碼行較多筛峭。一個(gè)圖片多處寫會(huì)嚴(yán)重影響代碼可閱讀性。use完美拯救了這個(gè)問題陪每,通過(guò)xlink:href定位需要的元件影晓,使用如下,#號(hào)表示取id

<use xlink:href="#icon-name1" x="50" y="50" /> // x, y 定位顯示位置

(2)支持跨SVG調(diào)用

跨SVG調(diào)用特征是 SVG-Sprite 技術(shù)使用核心所在

? 簡(jiǎn)單理解就是檩禾,SVG內(nèi)部可以use別的SVG內(nèi)定義的symbol挂签。假設(shè)有 1 中的SVG Sprite圖,則使用如下

<svg class="scgClass">
  <use xlink:href="#icon-name1" />
</svg>

? 這里我們還設(shè)計(jì)了一個(gè)svgClass盼产,在Vue項(xiàng)目中可以通過(guò)prop進(jìn)來(lái)饵婆,通過(guò)這個(gè)類來(lái)控制SVG顯示的樣式如大小,顏色等戏售。
? 從這個(gè)案例中獲取你已經(jīng)感受到侨核,我們會(huì)封裝一個(gè)<svg-icon>的組件,外部引用這個(gè)組件灌灾,傳入 svgClass 及圖標(biāo)的icon-name控制展示及樣式搓译,達(dá)到HTML層面優(yōu)雅的使用效果

3.【基礎(chǔ)】SVG 的 aria-hidden 和替代文本

? 類似<img alt="提示文案">中的alt屬性,SVG提供有替代文本以便屏幕閱讀器遇到SVG圖標(biāo)時(shí)讀取的文案紧卒。SVG一般都需要設(shè)置aria-hidden="true"以便閱讀器跳過(guò)閱讀

<svg aria-hidden="true">
  <use xlink:href="#icon-name1" />
</svg>

? 當(dāng)遇到 a標(biāo)簽或者button的內(nèi)容只有是圖標(biāo)時(shí)侥衬,可以在abutton標(biāo)簽上設(shè)置閱讀替代文本屬性aria-label,而SVG依然跳過(guò)跑芳,保證用戶體驗(yàn)

<a href="/news/" arialabel="Latest News">
  <svg aria-hidden="true"><use xlink:href="#icon-name1"></svg>
</a>

?

4. 【實(shí)戰(zhàn)】webpack配置svg-sprite-loader
  1. package.json中添加插件
npm install svg-sprite-loader --save-dev
  1. 配置webpack轴总,修改vue.config.js
    ? 注意默認(rèn)的SVG使用的是url-loader來(lái)解析的博个,我們只針對(duì)icon部分使用svg-sprite-loader處理怀樟,因此需要用到exculdeinclude命令區(qū)分。
// 設(shè)置svg圖片-圖標(biāo)處理方式
config.module
  .rule('svg')
  .exclude.add(resolve('src/icons')) // 除了src/icons下的svg都是用url-loader來(lái)處理
  .end()
config.module
  .rule('icons')
  .test(/\.svg$/)
  .include.add(resolve('src/icons')) // svg-sprite-loader只處理src/icons下的svg
  .end()
  .use('svg-sprite-loader')
  .loader('svg-sprite-loader')
  .options({
    symbolId: 'icon-[name]'
  })
  .end()

? 有了這個(gè)區(qū)分盆佣,就要求我們規(guī)范的管理SVG圖標(biāo)往堡,將其統(tǒng)一放置在src/icons目錄下。

【注】為什么不是src/icons/svg共耍?因?yàn)樵谙乱徊綍?huì)去生成SVG Sprite虑灰,而他不在該目錄里。即src/icons/svg只是源svg圖標(biāo)痹兜,SVG Sprite在src/icons里穆咐,所以用src/icons。只要保證正常SVG圖片不往這里放即可。
?

5. 【實(shí)戰(zhàn)】自動(dòng)導(dǎo)入生成SVG Sprite require.context

? 先理解一下对湃,既要有維護(hù)容易的增添單個(gè)圖標(biāo)崖叫,又要有使用方便的SVG Sprite。這兩者本身相悖拍柒,為滿足這個(gè)功能心傀,我們需要一個(gè)從圖標(biāo)組里生成SVG Sprite的能力,好在webpack提供了require.context自動(dòng)導(dǎo)入功能拆讯,滿足了該需求脂男。我們?cè)谂c@/icons/svg同級(jí)目錄下編寫該邏輯

Sprite生成文件

? 先入為主,SvgIcon.svg是下一步要實(shí)現(xiàn)的組件封裝往果,因?yàn)楸厝挥性S多地方需要用到圖標(biāo)疆液,所以對(duì)SvgIcon組件進(jìn)行全局注冊(cè)

// /icons/index.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'

// 全局注冊(cè)SvgIcon組件 
Vue.component('svg-icon', SvgIcon)

const req = require.context('./svg', false, /\.svg$/) 
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

require.context接受三個(gè)參數(shù),1-源svg文件夾陕贮,2-是否檢索下級(jí)目錄堕油,3-匹配資源正則表達(dá)式。

? 以上代碼片段的意思是:在svg文件夾下肮之,不檢索其子目錄掉缺,尋找后綴名為.svg的能被require所有資源圖標(biāo)。需要注意的是生成的SVG Sprite元件的id名戈擒,會(huì)加上icon-前綴眶明。其最終會(huì)生成一個(gè)類似如下的SVG Sprite

SVG Sprite 樣例

?

6. 【實(shí)戰(zhàn)】上手寫<svg-icon>組件

? 接下來(lái)實(shí)現(xiàn)4中的SvgIcon組件,用于在HTML中優(yōu)雅的編寫引入的圖標(biāo)筐高。我們?cè)谠摻M件中還區(qū)分了圖標(biāo)是否是外聯(lián)SVG圖標(biāo)搜囱,iconClass表示圖標(biāo)的語(yǔ)義化名稱(一般為svg圖片名字),className表示圖標(biāo)個(gè)性化樣式柑土。因?yàn)樯蒘VG Sprite時(shí)symbol的id被加上了icon-蜀肘,為了不影響引用,我們需要在組件實(shí)現(xiàn)時(shí)把類名的前綴加上

// components/svg-icon.vue
<template>
  <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
    <use :xlink:href="iconName" />
  </svg>
</template>
<script>

export default {
  name: 'SvgIcon',
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    }
  },
  computed: {
    isExternal() {
      return this.isExternal(this.iconClass)
    },
    iconName() {
      return `#icon-${this.iconClass}`
    },
    svgClass() {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'
      }
    },
    styleExternalIcon() {
      return {
        mask: `url(${this.iconClass}) no-repeat 50% 50%`,
        '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
      }
    }
  },
  methods: {
    // 判斷是否是外部圖標(biāo)
    isExternal(path) {
      return /^(https?:|mailto:|tel:)/.test(path)
    }
  }
}
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}

.svg-external-icon {
  background-color: currentColor;
  mask-size: cover!important;
  display: inline-block;
}
</style>

?

7. 頁(yè)面使用SVG Sprite

? 有了生成SVG Sprite的能力稽屏,也有了優(yōu)雅使用 SVG Icon 組件扮宠,接下來(lái)就是怎么用了。

? 如果你有設(shè)計(jì)師提供SVG圖標(biāo)最好狐榔,有許多設(shè)計(jì)師使用sketch來(lái)工作坛增,可以很方便的導(dǎo)出SVG。如果沒有設(shè)計(jì)師或不方便實(shí)時(shí)溝通薄腻,可以借用阿里爸爸的IconFont收捣,它支持下載SVG格式的圖片,下載下來(lái)放到src/icons/svg文件加下即可庵楷,require.context會(huì)自動(dòng)更新成SVG Sprite坏晦,開發(fā)者只需要管怎么引用即可。

<svg-icon icon-class="eye" class-name="eyeStyle" />

? 本例中,eye表示SVG圖標(biāo)的名稱(eye.svg)昆婿,eyeStyle是該svg圖標(biāo)的自定義樣式。如果不需要自定義樣式蜓斧,則代碼段更短仓蛆。
?

# 拓展

生成的SVG Sprite會(huì)不會(huì)太大?

? 首先我們來(lái)看一下從阿里icofont 網(wǎng)站上導(dǎo)出的svg長(zhǎng)什么樣

iconfont 的 svg 格式文件

? 對(duì)比于設(shè)計(jì)師可能給我們的svg圖標(biāo),這個(gè)已經(jīng)很精簡(jiǎn)了挎春,但其實(shí)還有很多無(wú)用的信息看疙,造成了冗余。

? 好在svg-sprite-loader考慮到了這點(diǎn)直奋,它目前只會(huì)獲取svg中path的內(nèi)容能庆,而其他的信息一概不要,因此生成的sprite也都是精華的集合脚线,且SVG本身就是代碼型圖片搁胆,無(wú)需太擔(dān)心資源大小問題。當(dāng)然也要適合的控制你的svg圖標(biāo)邮绿,沒用的就不要加進(jìn)來(lái)了渠旁。
?

# 本文參考

未來(lái)必?zé)幔篠VG Sprites技術(shù)介紹
SVG圖標(biāo)制作指南
svg | MDN
優(yōu)雅的使用icon

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市船逮,隨后出現(xiàn)的幾起案子顾腊,更是在濱河造成了極大的恐慌,老刑警劉巖挖胃,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杂靶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡酱鸭,警方通過(guò)查閱死者的電腦和手機(jī)吗垮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)凛辣,“玉大人抱既,你說(shuō)我怎么就攤上這事”馐模” “怎么了防泵?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蝗敢。 經(jīng)常有香客問我捷泞,道長(zhǎng),這世上最難降的妖魔是什么寿谴? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任锁右,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咏瑟。我一直安慰自己拂到,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布码泞。 她就那樣靜靜地躺著兄旬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪余寥。 梳的紋絲不亂的頭發(fā)上领铐,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音宋舷,去河邊找鬼绪撵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛祝蝠,可吹牛的內(nèi)容都是我干的音诈。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼续膳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼改艇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起坟岔,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤谒兄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后社付,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體承疲,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年鸥咖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了燕鸽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡啼辣,死狀恐怖啊研,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸥拧,我是刑警寧澤党远,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站富弦,受9級(jí)特大地震影響沟娱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腕柜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一济似、第九天 我趴在偏房一處隱蔽的房頂上張望矫废。 院中可真熱鬧,春花似錦砰蠢、人聲如沸蓖扑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赵誓。三九已至,卻和暖如春柿赊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背幻枉。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工碰声, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人熬甫。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓胰挑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親椿肩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞻颂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容