前端工程化之SvgIcon组件

1,042 阅读3分钟

1、背景

我们在做后台系统时,经常会遇到UI给我们一引起svg的图片,虽说svg的图片,可以直接在代码中使用,但是却显得不那么优雅,且显得比较臃肿,那有没有好的解决方案呢?于是使用万能google一搜,看了几篇文章,大致思路就是封装一个SvgIcon组件,通过props来接收svg图片。这样做有很多好处:

  • 图标易于实时修改
  • 图标可以带动画
  • 你可以使用标准的 prop 和默认值来将图标保持在一个典型的尺寸并随时按需改变它们
  • 图标是内联的,所以不需要额外的 HTTP 请求
  • 可以动态地使得图标可访问

2、思路

利用svg的symbol元素,将每个icon包括在symbol中,通过use元素使用该symbol.

那要封装一个SvgIcon组件,那我们先考虑入参有哪些?经过思考,总结了有几个属性:

name: 必填, Svg图标的名称
width: 选填,Svg图标的大小
height: 选填,Svg图标的高度
color: 选填,Svg图标的颜色
iconClass: 选填,Svg图标的样式类

封装SvgIcon组件,主要是利用<svg>元素与其子元素<use>来实现,下面是取自MDN上的一段话:

use元素在SVG文档内取得目标节点,并在别的地方复制它们。它的效果等同于这些节点被深克隆到一个不可见的DOM中,然后将其粘贴到use元素的位置,很像HTML5中的克隆模板元素

我们的每一个icon都对应着一个symbol元素,然后通过use来引用symbol:

<use xlink:href="#symbolId"></use>

那请问要在哪里去获取symbolId,这时就要用到一个loader,即svg-sprite-loader。 首先安装:

yarn add svg-sprite-loader -D

svg-sprite-loader会把我们的icon塞到一个个symbol中,那要如何配置svg-sprite-loader呢?

// vue.config.js
module.exports = {
    chainWebpack: config => {
         // 找到svg-loader
        const svgRule = config.module.rule('svg')
        // 清除已有的loader, 如果不这样做会添加在此loader之后
        svgRule.uses.clear()
        // 正则匹配排除node_modules目录
        svgRule.exclude.add(/node_modules/)
        svgRule.include.add(resolve('src/assets/icons'))
        // 添加svg新的loader处理
        svgRule
          .use('svg-sprite-loader')
          .loader('svg-sprite-loader')
          .tap(options => {
            options = {
              symbolId: 'icon-[name]'
            }
            return options
          })
    }
}

另外,我们其他svg图片,我们还是想当作图片使用:

// vue.config.js
module.exports = {
    chainWebpack: config => {
         // 找到svg-loader
        const svgRule = config.module.rule('svg')
        // 清除已有的loader, 如果不这样做会添加在此loader之后
        svgRule.uses.clear()
        // 正则匹配排除node_modules目录
        svgRule.exclude.add(/node_modules/)
        svgRule.include.add(resolve('src/assets/icons'))
        // 添加svg新的loader处理
        svgRule
          .use('svg-sprite-loader')
          .loader('svg-sprite-loader')
          .tap(options => {
            options = {
              symbolId: 'icon-[name]'
            }
            return options
          })
          
        // 修改images loader 添加svg处理
        const imagesRule = config.module.rule('images')
        imagesRule.exclude.add(resolve('src/assets/icons'))
        config.module
          .rule('images')
          .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)

    }
}

有了配置,那我们就可以开始动手写我们的组件了。

// components/SvgIcon/index.vue
<template>
  <svg
    :class="svgClass"
    aria-hidden="true"
    :width="width"
    :height="height"
    :fill="color"
  >
    <use :xlink:href="iconName" />
  </svg>
</template>

<script>
export default {
  name: 'SvgIcon',
  props: {
    /* SVG图标名称 */
    name: {
      type: String,
      required: true
    },
    /* 样式类 */
    className: {
      type: String,
      default: ''
    },
    /* SVG图标宽度大小 */
    width: {
      type: [String, Number],
      default: 16
    },
    /* SVG图标高度大小 */
    height: {
      type: [String, Number],
      default: 16
    },
    /* SVG图标颜色 */
    color: {
      type: String,
      default: 'currentColor'
    }
  },
  computed: {
    iconName() {
      return `#icon-${this.name}`
    },
    svgClass() {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'
      }
    }
  }
}
</script>

<style>
.svg-icon {
  vertical-align: -0.15em;
  overflow: hidden;
}
</style>

另外在assets/icons中存放我们要做为iconsvg图片,如下图所示:

image.png

紧接着,我们写一个自动导入的方法,避免手动引入svg

// @/core/icons.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg组件

// register globally
Vue.component('SvgIcon', SvgIcon)

const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('@/assets/icons/svg', false, /\.svg$/)
requireAll(req)

main.js中引入icons.js:

import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import './styles/index.scss'
// 引入icons
import '@/core/icons'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

最后我们找个文件检验下成果:

  <svg-icon name="iconxiaoshouguanli1" color="blue" width="30" height="24"></svg-icon>

image.png

换个大小试试:

<svg-icon name="iconxiaoshouguanli1" color="blue" width="48" height="48"></svg-icon>

image.png 可以看到图标明显变大了!

再换个颜色试下:

<svg-icon name="iconxiaoshouguanli1" color="red" width="48" height="48"></svg-icon>

image.png 颜色也生效了。

总结

  • 原理:

    • symbol + use:xlink:href;
    • svg-sprite-loader生成雪碧图;
    • require.context动态引入所有文件;
  • 优化SVG require.contextAPI.