懶人神器:svg-sprite-loader實現本身的Icon組件

用 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

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...可能須要你的設計師從新切圖,一樣是張鑫旭大佬
關於切圖的這篇文章

引入全部Icon文件

上面咱們的基本功能已經完成了,還有最後一個小小的問題——我每次引用一個文件的時候就得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.contextAPI 來建立本身的context module動態引入icon。它接受三個參數,第一個是文件夾,第二個是是否使用子文件,第三個是文件匹配的正則。
require.context(directory, useSubdirectories = false, regExp = /^\.\//)
對於咱們的項目來講,咱們須要動態引入的就是require.context('./src/assets/icons', false, /\.svg/).

require.context會返回一個函數,而且該函數有keys()idresolve() 屬性。

  • 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$";

最後,咱們requestcontext module下的每個module,引入咱們全部的icon

// 因爲request返回了一個函數,該函數接收req做爲參數,在這裏其實咱們就是把request.keys()中的每個module傳入了request的返回函數中了
request.keys().forEach(request);

總結

  • 原理:

    • symbol + use:xlink:href;
    • svg-sprite-loader生成雪碧圖;
    • require.context動態引入全部文件;
  • 優化SVG

有時候,設計師切的icon並不那麼geek, 有不少多餘的東西,可使用大名鼎鼎的svgo進行優化,
它提供web在線版,webpack loader等。

  • 其餘工具

vue-svgicon這款工具相比咱們的有更多的feature,好比動畫、方向等。它會給每一個icon生成一個相對應的js文件,
用來註冊這個icon。就我目前的應用場景來講,1. 它會生成不少js文件;2.每次新增一個svg時我就得run一次註冊組件的命令。對於我如今的簡單應用場景來講,並無本身寫的簡單方便。
不過在其餘的時候,他也能夠做爲另外一個選擇。

require.contextAPI.

參考資料

相關文章
相關標籤/搜索