根据本人在项目的实践提炼出来,在说svg-sprite-loader使用之前,我们先看一下具体效果:
目录结构
- 把svg图放置在assets/icons/svg下,
- /assets/icons/index.js 注入全局组件
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = (requireContext) => requireContext.keys().map(requireContext)
requireAll(req)
- 在main.js引入 icons
import './assets/icons'
- 在components文件夹下新建两个组件
SvgIcon 是元组件,即需要全局注册的组件svg-icon,代码如下
// components/SvgIcon/index.vue
<template>
<svg
:class="`${svgClass} ${spin ? 'scoped-svg-animation' : ''}`"
aria-hidden="true"
v-on="$listeners"
>
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
name: {
type: String,
required: true,
},
className: {
type: String,
default: '',
},
// 是否旋转
spin: {
type: Boolean,
default: false,
},
},
computed: {
iconName() {
return `#icon-${this.name}`
},
svgClass() {
if (this.className) {
return 'scoped-svg-icon ' + this.className
} else {
return 'scoped-svg-icon'
}
},
},
}
</script>
<style lang="less">
.scoped-svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
&.scoped-svg-animation {
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
&.gray {
color: #666;
}
&.white {
color: #fff;
}
}
</style>
项目中使用svg-icon组件:
// /components/IconSelect/index.vue
<template>
<div class="icon-select-wrapper">
<div>当前选择:{{ currentIcon }}</div>
<el-input v-model="iconVal" placeholder="请输入内容"></el-input>
<div>
<ul class="icon-list">
<li
:class="{ active: item === currentIcon }"
v-for="(item, index) in iconList"
:key="index"
@click="selectIcon(item)"
>
<svg-icon
:class-name="item === currentIcon ? 'white' : 'gray'"
:name="item"
style="width: 30px; height: 30px"
/>
<span class="icon-label">{{ item }}</span>
</li>
</ul>
</div>
</div>
</template>
<script>
import icons from './config'
export default {
data() {
return {
iconList: icons,
iconVal: '',
currentIcon: 'add-circle',
iconClass: '',
}
},
watch: {
iconVal(val) {
if (val) {
this.iconList = this.iconList.filter((item) => item.indexOf(val) > -1)
} else {
this.iconList = icons
}
},
},
methods: {
selectIcon(item) {
this.currentIcon = item
this.iconClass
},
},
}
</script>
<style lang="less">
.icon-select-wrapper {
width: 450px;
}
.icon-list {
width: 450px;
display: flex;
flex-wrap: wrap;
border-left: 1px solid #dcdfe6;
border-top: 1px solid #dcdfe6;
margin-top: 20px;
li {
width: 150px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-right: 1px solid #dcdfe6;
border-bottom: 1px solid #dcdfe6;
padding: 10px 0;
box-sizing: border-box;
cursor: pointer;
.icon-label {
font-size: 12px;
color: #666;
}
&.active {
background: burlywood;
.icon-label {
font-size: 12px;
color: #fff;
}
}
}
}
</style>
使用require.context()方法自动化引入所有icons下的svg,并进行处理,取出svg的名称;
如 add.svg --> add
// /components/IconSelect/config.js
const req = require.context('@/assets/icons/svg', false, /\.svg$/)
console.log(req, 'req')
const requireAll = (requireContext) => requireContext.keys()
const re = /\.\/(.*)\.svg/
const icons = requireAll(req).map((item) => {
console.log(item, '88')
return item.match(re)[1]
})
console.log(icons, 'icons')
export default icons
vue.config.js 配置svg-sprite-loader、svgo-loader如下:
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir)
}
module.exports = {
chainWebpack: (config) => {
// 不对assets/icons的svg打包
config.module.rule('svg').exclude.add(resolve('src/assets/icons')).end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]',
})
.end()
.before('svg-sprite-loader')
.use('svgo-loader')
.loader('svgo-loader')
.end()
},
}
总结,在这个应用中,有如下知识点需要掌握了解:
1. require.context
它是webpack中的api,通过执行require.context函数获取一个特定的上下文,主要用来实现自动化导入模块,在前端工程中,如果遇到从一个文件夹引入很多模块的情况,可以使用这个api,它会遍历文件夹中的指定文件,然后自动导入,而不需要每次调用import导入模块
require.context(directory, useSubdirectories = false, regExp = /^.//);
directory: 要查找的文件路径
useSubdirectories: 是否查找子目录
regExp: 要匹配文件的正则
在本例中,require.context()返回的结果如下:
var map = {
"./add-circle.svg": "./src/assets/icons/svg/add-circle.svg",
"./add.svg": "./src/assets/icons/svg/add.svg",
"./arrow-double-right.svg": "./src/assets/icons/svg/arrow-double-right.svg",
"./arrow-down.svg": "./src/assets/icons/svg/arrow-down.svg",
"./arrow-right.svg": "./src/assets/icons/svg/arrow-right.svg",
"./ashbin.svg": "./src/assets/icons/svg/ashbin.svg",
"./bottom.svg": "./src/assets/icons/svg/bottom.svg",
"./browse.svg": "./src/assets/icons/svg/browse.svg",
"./column-3.svg": "./src/assets/icons/svg/column-3.svg",
"./column-4.svg": "./src/assets/icons/svg/column-4.svg",
"./direction-down.svg": "./src/assets/icons/svg/direction-down.svg",
"./explain.svg": "./src/assets/icons/svg/explain.svg",
"./file-open.svg": "./src/assets/icons/svg/file-open.svg",
"./film.svg": "./src/assets/icons/svg/film.svg",
"./folder-close.svg": "./src/assets/icons/svg/folder-close.svg"
};
function webpackContext(req) {
var id = webpackContextResolve(req);
return __webpack_require__(id);
}
function webpackContextResolve(req) {
if(!__webpack_require__.o(map, req)) {
var e = new Error("Cannot find module '" + req + "'");
e.code = 'MODULE_NOT_FOUND';
throw e;
}
return map[req];
}
webpackContext.keys = function webpackContextKeys() {
return Object.keys(map);
};
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext;
webpackContext.id = "./src/assets/icons/svg sync \\.svg$";
2. svg标签use的使用
本例中:
<svg
:class="`${svgClass} ${spin ? 'scoped-svg-animation' : ''}`"
aria-hidden="true"
v-on="$listeners"
>
<use :xlink:href="iconName" />
</svg>
渲染结果如下: