系列文章

一、关于SVG Sprite技术

听说过雪碧图吗?雪碧图是根据 CSS Sprite 音译过来的,就是将很多很多的小图标放在一张图片上,就称之为雪碧图。然后在使用时切换不同的位置而实现展示需要的图片。这个是一样的原理,所以也可以叫做svg雪碧图,即,将多个symbol标签连接在一起,放在一个svg标签中,每个symbol元素都有一个ID,我们调用的时候只需要使用那个ID,即可准确的显示我们需要的svg图标。

如想详细了解,可以看张鑫旭大佬写的这篇文章 => 传送门

二、用到的插件

在封装组件之前,让我们先来了解两个插件:

1. svg-sprite-loader

这个插件是用来生成上面所述Svg雪碧图的,可以将加载的svg图片拼接成雪碧图,放到页面中,其它地方通过<use>复用

2. svgo-loader

这个插件是用来优化svg代码的,可以通过删除一些冗余的svg代码来达到减小文件体积的目的。不过这个东西大部分是被用来跟svg-sprite-loader配合,实现自定义颜色大小等操作的。因为一般情况下,svg文件直接被img标签引用的话,是不能改变颜色的。因为文件里会存在初始色,也就是path标签上的fill属性不为空,所以无论怎么在css里更改颜色都无济于事。而svgo就可以通过删除fill属性,实现在css中自定义颜色。

3. 安装并配置插件

了解过这两个插件之后,把两个插件安装进项目:

npm i svg-sprite-loader svgo-loader --save-dev

然后去网站上下载一些svg图标(这里推荐阿里巴巴iconfont矢量图标库),并保存在一个你想保存的文件夹中,这里推荐src/assets/icons/svg

最关键的地方来了,也就是通过配置webpack,来让svg-sprite-loader和svgo-loader接管项目中svg文件的解析。关于这个步骤,网上的资料有很多,但是大部分我用过之后都不凑效。于是我不停的摸爬滚打,终于弄出了可行的方案 (该方案仅仅是可行,但并不一定是最完美的)。不过在接管之后,也要注意恢复一下其他普通svg图标的解析loader。

新建一个文件,命名为svgo.config.js(推荐放在src/assets/icons目录下)并将下面的内容拷贝进去:

module.exports = {
  plugins: [
    {
      name: 'removeAttrs',
      params: {
        attrs: ['fill', 'fill-rule']
      }
    }
  ]
}

这个文件是用来配置svgo-loader的,详细配置请参考svgo-loader的ReaderMe文档

修改vue.config.js,将下面的代码加入chainWebpack中,并重启项目:

// 配置 svg-sprite-loader与svgo-loader
const svgRule = config.module.rule('svg')
svgRule.uses.clear()
svgRule
  .test(/\.svg$/)
  .include.add(path.resolve('src/assets/icons'))
  .end()
  .use('svg-sprite-loader')
  .loader('svg-sprite-loader')
  .options({ symbolId: 'icon-[name]' })
  .end()
  .use('svgo-loader')
  .loader('svgo-loader')
  .options({ configFile: '../svgo.config.js' })
  .end()

// 配置正常svg,这样写完以后,普通的svg图片就可以放在/public/imgs目录下了
const normalSvgRule = config.module.rule('normal_svg')
normalSvgRule
  .test(/\.(svg)(\?.*)?$/)
  .include.add(path.resolve('public/imgs'))
  .end()
  .use('file-loader')
  .loader(path.resolve('node_modules/file-loader/dist/cjs.js'))
  // 这里要注意一下,如果你并没有配置assetsDir属性,那么下面的路径应该是:img/[name].[hash:8].[ext]
  .options({ name: 'static/img/[name].[hash:8].[ext]' })
  .end()

重启之后,我们需要使用require,将文件全部引入。新建index.ts,推荐放在src/assets/icons目录下:

const svgFiles = require.context('./svg', false, /\.svg$/)
svgFiles.keys().map(svgFiles)

然后去main.ts中引用一下上一步写好的文件,我们在浏览器的开发者工具中,就会看到这样的Svg Sprite了:
在这里插入图片描述
可以看到,我的svg文件夹下的四个svg文件,已经连在一起,放在了一个svg标签下,并且,每个symbol的id,都是icon-加上文件名。

三、封装SvgIcon组件

这个就直接贴代码吧,没啥好说的:

<template>
  <svg
    class="svgIcon"
    :class="[className, { pointer }]"
    :style="{ '--icon-color': color, '--icon-size': variables[iconSize] }"
  >
    <use :xlink:href="iconName" />
  </svg>
</template>

<script lang="ts">
import { computed, defineComponent } from 'vue'
import variables from '@/assets/styles/variables.scss'

export default defineComponent({
  name: 'SvgIcon',
  props: {
  	name: {
      type: String,
      required: true
    },
    color: String,
    className: String,
    size: String,
    pointer: Boolean
  },
  setup(props) {
    const iconName = computed(() => `#icon-${props.name}`)
    const iconSize = computed(() => `SIZE_${props.size?.toUpperCase()}`)

    return {
      iconName,
      iconSize,
      variables
    }
  }
})
</script>

<style lang="scss">
.svgIcon {
  width: 1em;
  height: 1em;
  overflow: hidden;
  fill: var(--icon-color);
  font-size: var(--icon-size);

  &.pointer {
    cursor: pointer;
  }
}
</style>

记得全局注册一下这个组件,然后使用的时候就直接这样写<svg-icon name="leaf" />,也就是在name属性中写入svg文件名。然后注意一下,我这里的size属性的值只能使用我预定义好的sass变量,你也可以改成直接像12px, 1.5em这样使用::style="{ '--icon-color': color, '--icon-size': size }"

下一篇预告:vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(上)

Logo

前往低代码交流专区

更多推荐