懒人神器: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.

参考资料

相关文章
相关标签/搜索