# 前言
? 前端攻城獅們?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í)侥衬,可以在a
或button
標(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
- 在
package.json
中添加插件
npm install svg-sprite-loader --save-dev
- 配置webpack轴总,修改
vue.config.js
。
? 注意默認(rèn)的SVG使用的是url-loader
來(lái)解析的博个,我們只針對(duì)icon部分使用svg-sprite-loader
處理怀樟,因此需要用到exculde
和include
命令區(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í)目錄下編寫該邏輯
? 先入為主,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
?
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)什么樣
? 對(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