Icon 图标处理方案:SvgIcon

3,443 阅读2分钟

在vue项目中,我们除了使用组件库(ant-design-vue element-ui)的icon以外,还有我们自己自定义的图标,对于自定义图标的话,我们暂时还缺少显示的方式。所以说我们需要一个自定义的组件,来显示我们自定义的 svg 图标。

对于这个组件的话,它就需要拥有两种能力:

  1. 显示外部 svg 图标,比如: 'res.lgdsunday.club/user.svg'
  2. 显示项目内的 svg 图标,即从iconfont等网站下载的svg图标文件

1. 首先导入所有的 svg 图标

现在我们从iconfont网站上下载了很多svg的图标文件,我们怎么使用它呢?

import UserIcon form '@src/icon/user.svg'
<svg class="svg-icon" aria-hidden="true">
   <use :xlink:href=" UserIcon" />
</svg>

如果整个项目有100个图标,我们就需要import 100 多次,非常麻烦,那有没有什么办法一次性全部把svg文件全部引入,然后导入到入口文件main.js里面呢?

答案是require.context。通过执行require.context函数获取一个特定的上下文,主要用来实现自动化导入模块,在前端工程中,如果遇到从一个文件夹引入很多模块的情况可以使用这个api,它会遍历文件夹中的指定文件,然后自动导入,使得不需要每次显式的调用import导入模块。

require.context函数接受三个参数:

  • directory {String} -读取文件的路径
  • useSubdirectories {Boolean} -是否遍历文件的子目录
  • regExp {RegExp} -匹配文件的正则
const svgRequire = require.context('./svg', false, /\.svg$/)

require.context函数执行后返回的是一个函数,这个函数就是一个require函数,作用跟import一样。并且这个函数有3个属性:

  • resolve {Function}: 接受一个参数request,request为test文件夹下面匹配文件的相对路径,返回这个匹配文件相对于整个工程的相对路径
  • keys {Function}: 返回匹配成功模块的名字组成的数组
  • id {String}: 执行环境的id

这三个都是作为函数的属性(注意是作为函数的属性, 函数也是对象,有对应的属性)

svgRequire.keys().forEach((svgIcon) => svgRequire(svgIcon))

2、SvgIcon组件

<template>
  <div
    v-if="isExternal"
    :style="styleExternalIcon"
    class="svg-external-icon svg-icon"
    :class="className"
  />
  <svg v-else class="svg-icon" :class="className" aria-hidden="true">
    <use :xlink:href="iconName" />
  </svg>
</template>

// 判断是否是外部svg,如:https://res.lgdsunday.club/user.svg
const isExternal = computed(() => external(props.icon))
const styleExternalIcon = computed(() => ({
  mask: `url(${props.icon}) no-repeat 50% 50%`,
  '-webkit-mask': `url(${props.icon}) no-repeat 50% 50%`
}))

// 项目内的svg图标文件
const iconName = computed(() => `#icon-${props.icon}`)

3. 把组件注册为全局组件,并在main.js引入

export default (app) => {
  app.component('svg-icon', SvgIcon)
}

// main.js 导入 svgIcon
import installIcons from '@/icons'
installIcons(app)

4. 使用 svg-sprite-loader 处理 svg 图标

通过上面的处理之后,打开浏览器,我们发现 图标依然无法展示!  这又是因为什么原因呢?

这是因为我们没有对应的loader来处理后缀为.svg的图标文件,其实也不是没有,通过npm 润inspect把webpack的配置调出来:

"scripts": {
        "inspect": "vue-cli-service inspect --mode development >> webpack.config.development.js"
}

发现处理svg文件的是file-loader,但是这个loader不对svg文件不做任何操作,只是把资源移动到打包之后的文件中,并修改对应的链接。

/* config.module.rule('svg') */
{
  test: /\.(svg)(\?.*)?$/,
  exclude: [
    '/Users/pengchangjun/Documents/admin-template/pcj-admin/src/icons'
  ],
  use: [
    /* config.module.rule('svg').use('file-loader') */
    {
      loader: '/Users/pengchangjun/Documents/admin-template/pcj-admin/node_modules/file-loader/dist/cjs.js',
      options: {
        name: 'img/[name].[hash:8].[ext]'
      }
    }
  ]
},

这种使用方式是利用img或者css来展示:

<img src="https://res.lgdsunday.club/user.svg" alt="" />

但是我们对svg的使用方式:

<svg v-else class="svg-icon" :class="className" aria-hidden="true">
    <use :xlink:href="iconName" />
</svg>

所以,我们需要特殊的一个loader来处理svg图标,这个loader是svg-sprite-loader,它 是 webpack 中专门用来处理 svg 图标的一个 loader

  • 下载: npm i --save-dev svg-sprite-loader@6.0.9
  • 创建 vue.config.js 文件,新增如下配置:
chainWebpack(config) {
  // 设置 svg-sprite-loader
  // 首先把原来svg应用的loader(file-loader)先排除
  config.module.rule('svg').exclude.add(resolve('src/icons')).end()
  // 专门增加一个icons的规则
  config.module
    .rule('icons')
    .test(/\.svg$/)
    .include.add(resolve('src/icons'))
    .end()
    .use('svg-sprite-loader')
    .loader('svg-sprite-loader')
    .options({
      symbolId: 'icon-[name]'
    })
    .end()
}

处理完以上配置之后,重新启动项目,图标即可显示!

我们打开控制台,可以看到body下面有一个svg标签,里面就是我们导入的所有svg的icon。

image.png