用 svg-sprite-loader 解放你的icon.
好吧,這篇文章的起源就來源於——我懶。css
UI小姐姐設計了本身的icon,可是我不想每次引入icon的時候都寫一大堆:html
<img src="/long/path/to/your/svg/icon.svg" />
很長很長的地址…我以爲最簡單的形式仍是像餓了麼那些UI庫同樣,直接:vue
<el-icon name="icon-file-name"></el-icon>
寫個文件名就能引入個人icon了。webpack
OK, 以上就是咱們的理想模式。So, let’s go!css3
網上搜尋了一圈,一個簡單的解決方案是 —— svg 雪碧圖。git
它的工做原理是: 利用svg的symbol
元素,將每一個icon包括在symbol
中,經過use
元素使用該symbol
.github
OK,若是你對此不瞭解,能夠閱讀張鑫旭老師的這篇文章.web
咱們這裏簡單一點的解釋就是,最終你的svg icon會變成下面這個樣子的 svg 雪碧圖:express
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="__SVG_SPRITE_NODE__"> <symbol class="icon" viewBox="0 0 1024 1024" id="icon名">{{省略的icon path}}</symbol> <symbol class="icon" viewBox="0 0 1024 1024" id="icon名">{{省略的icon path}}</symbol> </svg>
你的每個icon都對應着一個symbol
元素。而後在你的html中,引入這樣的svg, 隨後經過use
在任何你須要icon的地方指向symbol:數組
<use xlink:href="#symbolId"></use>
這個過程當中,咱們能夠把symbol理解爲sketch中內置的圖形,當你須要使用的時候,把這個形狀」拖拽」到你的畫板中就好了。而use
就是這個過程當中的」拖拽」行爲。
要讓咱們本身生成上面那樣的svg雪碧圖——確定是不可能的咯!
恩,你必定想到了,確定有工具!固然你最經常使用的應該是webpack的工具吧,這裏拿好!
svg-sprite-loader
會把你的icon塞到一個個symbol
中,symbol
的id若是不特別指定,就是你的文件名。它最終會在你的html
中嵌入這樣一個svg
,
你就能夠像上面這樣:
<use xlink:href="#symbolId"></use>
隨意使用你的icon咯。
svg-sprite-loader
配置以下:
{ test: /\.svg$/, loader: 'svg-sprite-loader', }
有一點須要注意的是,咱們並非全部的svg都要放在咱們的雪碧圖裏,有的也許我就想當作圖片用。這時候在咱們的webpack
配置中,咱們須要對這兩種svg區別對待。
首先,咱們要把全部要做爲icon的svg團結在一塊兒,放在某個文件夾中,例如assets/icons
。其餘的svg就隨你便啦。
而後對於想要用做圖片的:
{ test: /\.svg$/, loader: 'file-loader', exclude: path.resolve(__dirname, './src/assets/icons') // 不帶icon 玩 }
對於用做icon的:
{ test: /\.svg$/, loader: 'svg-sprite-loader', include: path.resolve(__dirname, './src/assets/icons') // 只帶本身人玩 }
最後,這倆就分道揚鑣啦。
OK, 咱們的問題已經解決了一半,不用每次都寫路徑引入svg
文件了。
可是。。。咱們如今要每次都寫
<svg> <use xlink:href="#symbolId"></use> </svg>
我!不!幹!!!並且也沒達到咱們最初的目的。
因此,咱們確定把上面的那一坨寫成一個組件咯:
<template> <svg :class="svgClass"> <use :xlink:href="`#${name}`"></use> </svg> </template> <script> export default { name: 'icon', props: { name: { type: String, required: true, }, }, } </script>
最後,你就達成目標,這樣使用:
import 'your-icon.svg'; <icon name="your-icon-name"></icon>
若是你想修改圖標的顏色,直接設置該元素的fill
/stroke
屬性。若是設置了這些屬性沒有反應的話,emmm...可能須要你的設計師從新切圖,一樣是張鑫旭大佬
關於切圖的這篇文章
上面咱們的基本功能已經完成了,還有最後一個小小的問題——我每次引用一個文件的時候就得import一下,這確定也不知足咱們偷懶的最終目標。
不過,總會有人比你更懶,或者總會有人比你先懶。在這裏,咱們可使用webpack的require.contextAPI來動態引入你全部的Icon.
如今咱們是不能動態引入模塊,可是webpack爲咱們提供了相關功能,webpack) 容許咱們使用表達式動態引入模塊。好比:require('./template/' + name + '.ejs');
,此時webpack會生成一個context module
A context module is generated. It contains references to all modules in that directory that can be required with a request matching the regular expression. The context module contains a map which translates requests to module ids.
它會被抽象成如下信息:
{ "./table.ejs": 42, // key 是module, value 是module id "./table-row.ejs": 43, "./directory/folder.ejs": 44 }
所以,咱們能夠利用webpack提供的的require.context
API 來建立本身的context module
動態引入icon。它接受三個參數,第一個是文件夾,第二個是是否使用子文件,第三個是文件匹配的正則。require.context(directory, useSubdirectories = false, regExp = /^\.\//)
對於咱們的項目來講,咱們須要動態引入的就是require.context('./src/assets/icons', false, /\.svg/)
.
require.context
會返回一個函數,而且該函數有keys()
,id
, resolve()
屬性。
keys()
方法返回的該模塊能夠處理的全部可能請求的模塊的數組,簡單一點就是知足該參數的模塊;resolve()
返回的是請求的module的id;id
是該context module
的id;總的來講,就是說require.context
幫咱們建立一個上下文,好比在這裏咱們的上下文就是./src/assets/icons
, 隨後咱們就能夠經過request.resolve('./store.svg')
來引入該上下文內的文件了。
咱們打印一下:
const request = require.context('./assets/icons', false, /\.svg$/); console.log(request); console.log(request.keys()); console.log(request.id); console.log('request.resolve()', request.resolve('./store.svg')); console.log(request.resolve);
獲得的結果是:
// request webpackContext(req) { var id = webpackContextResolve(req); return __webpack_require__(id); } // request.keys() ["./airbloom.svg", "./crown.svg", "./store.svg"] // request.id ./src/assets/icons sync \.svg$ // request.resolve('./store.svg'); ./src/assets/icons/store.svg // request.resolve webpackContextResolve(req) { var id = map[req]; if(!(id + 1)) { // check for number or string var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; }
有關的源碼在這裏:
var map = { "./airbloom.svg": "./src/assets/icons/airbloom.svg", "./crown.svg": "./src/assets/icons/crown.svg", "./store.svg": "./src/assets/icons/store.svg" }; function webpackContext(req) { var id = webpackContextResolve(req); return __webpack_require__(id); } function webpackContextResolve(req) { var id = map[req]; if(!(id + 1)) { // check for number or string var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } return id; } webpackContext.keys = function webpackContextKeys() { return Object.keys(map); }; webpackContext.resolve = webpackContextResolve; module.exports = webpackContext; webpackContext.id = "./src/assets/icons sync \\.svg$";
最後,咱們request
該context module
下的每個module
,引入咱們全部的icon
// 因爲request返回了一個函數,該函數接收req做爲參數,在這裏其實咱們就是把request.keys()中的每個module傳入了request的返回函數中了 request.keys().forEach(request);
原理:
symbol
+ use:xlink:href
;svg-sprite-loader
生成雪碧圖;require.context
動態引入全部文件;有時候,設計師切的icon並不那麼geek, 有不少多餘的東西,可使用大名鼎鼎的svgo進行優化,
它提供web在線版,webpack loader等。
vue-svgicon這款工具相比咱們的有更多的feature,好比動畫、方向等。它會給每一個icon生成一個相對應的js文件,
用來註冊這個icon。就我目前的應用場景來講,1. 它會生成不少js文件;2.每次新增一個svg時我就得run一次註冊組件的命令。對於我如今的簡單應用場景來講,並無本身寫的簡單方便。
不過在其餘的時候,他也能夠做爲另外一個選擇。
require.context
API.