工程化使用 SVG 图标最佳实践

2,657 阅读5分钟

一、 前言

笔者目前在维护公司的一套 Saas 服务系统。由于运维小哥哥不给安排静态资源服务器,于是乎asstest/svg 文件夹肉眼可见的越来越臃肿。

然后笔者经过层层调研,尝试了各种打包姿势后,终于总结出 svg 图标使用的葵花宝典

可以实现 SVG 图标的自动压缩、自动导入和快速使用。让你学会如何在项目中更工程化维护和使用 SVG 图标,干货满满,坐稳发车!

二、前端图标引用演进史

「第一代」,刀耕火种的时代,切图切下来的图片直接做图标使用,当需要做 active 状态或 hover 之类的时候切换图片路径,操作非常麻烦,也比较耗费性能不便于维护。

「第二代」,在切图的基础上,将多个图标合并一个图片,然后基于 CSS 的 background-position 定位显示不同的图标,虽然减少了 http 请求数量,但是维护困难,每新增一个图标,都需要改动原始图片,还可能不小心出错影响到前面定位好的图片,而且一修改雪碧图,图片缓存就失效了,久而久之你也不知道该怎么维护了

「第三代」,就是unicode,这个就已经是一个改革了,并且兼容性很好,不过操作上还是要麻烦一点需要自定义图标字体颜色背景色之类的需要自己再声明一个class去修饰,比如

<i class="iconfont orange">&#xe604;</i>

「第四代」,也是字体图标,font-class引用,只不过基于上一代做了一些改变,但是也都需要去声明一个基class 然后再声明一个私有class,这种使用方式比 unicode 更加直观

<i class="iconfont icon-downloa" />

「第五代」,也是本文重点,Symbol引用,随着万恶的某某浏览器逐渐淡出历史舞台,svg-icon 使用形式慢慢成为主流和推荐的方法。相关文章可以参考张鑫旭大大的文章未来必热:SVG Sprite技术介绍,使用 Symbol 的优势

  • 支持多色图标了,不再受单色限制。
  • 支持像字体那样通过font-size,color来调整样式。
  • 支持 ie9+
  • 可利用CSS实现动画。
  • 减少HTTP请求。
  • 矢量,缩放不失真
  • 可以很精细的控制SVG图标的每一部分

三、如何使用

我们将结合 Webpack、svgo、svgo-loader、svg-sprite-loader 以及上面提到的 Symbol 引用实现 SVG 图标的自动压缩、自动导入和快速使用,以 Vue 为例子, 首先安装各个依赖

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

第一步,创建 SvgIcon 组件

首先在 components 文件夹创建 svg 图标的使用组,代码如下:

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

<script>
export default {
  name'svg-icon',
  props: {
    name: {
      typeString,
      requiredtrue
    }
  },
  computed: {
    iconName() {
      return `#icon-${this.name}`
    }
  }
}
</script>

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

React 同理,可自行实现

第二步,通过 Webpack 实现图标自动导入

toulan.png

图标库越来越大,手动导入怎么能行,这里介绍一下 webpack 的 require.context

require.context("./test", false, /.test.js$/);

这行代码就会去 test 文件夹(不包含子目录)下面的找所有文件名以 .test.js 结尾的文件能被 require 的文件。

更直白的说就是 我们可以通过正则匹配引入相应的文件模块。

require.context有三个参数:

  • directory:说明需要检索的目录
  • useSubdirectories:是否检索子目录
  • regExp: 匹配文件的正则表达式

我们可以通过require.context自动导入某个文件夹下的所有图标

创建src/icons/svg 文件夹用来存放我们所有的 svg 图标

然后创建src/icons/index.js文件通过require.context自动导入所有图标并全局声明 SvgIcon组件

// src/icons/index.js
import Vue from 'vue'
import SvgIcon from '@/components/svg-icon'

// 全局注册 svg-icon
Vue.component('svg-icon'SvgIcon)

const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg'false/.svg$/)
requireAll(req)

最后在 main.js 导入 src/icons/index.js

// main.js
import Vue from 'vue'
import App from './App.vue'
import '@/icons'

Vue.config.productionTip = false

new Vue({
  renderh => h(App),
}).$mount('#app')

第二步,使用 svgo-loader 做自动压缩

关于svgo,是一个很强大的 svg 图标压缩工具,目前 github star 已经16k,我们使用svgo的主要目的有两个

  • UI设计师 sketch 导出的 svg 冗余信息很多,我们需要压缩 svg图标大小
  • 纯色图标可以去除 svg 图标的fill属性,这样我们就可以使用 css 直接修改图标颜色

使用 svgo 压缩图标结果前后对比,基本每个图标的体积都减少了 60% 的体积

qr.png

但是每次手动调用命令行去压缩图标配置复杂不说,还经常忘记,最重要的是也不能保留图标的原始结构

有没有可以一劳永逸自动压缩图标还能保留 svg 文件原始结构的方法呢?

答案就是svgo-loader

我们结合 webpack 使用 svgo-loader 就可以实现想要的效果了,配置代码如下

const { extendDefaultPlugins } = require("svgo"); // 注意此处为 svgo 2.0 版本以上插件使用方法

chainWebpack(config) => {
    config.module
      .rule("svg") 
      .exclude.add(resolve("src/icons")) // icons 文件夹不走 vue-cli 默认的图标处理
      .end();
    config.module
      .rule("icons"// 自定义icons规则
      .test(/.svg$/)
      .include.add(resolve("src/icons"))
      .end()
      .use("svg-sprite-loader")
      .use("svgo-loader")
      .loader("svgo-loader")
      .options({ 
        pluginsextendDefaultPlugins([ // 如果是有色图标,可以删除本配置,只做压缩
          {
            name"removeAttrs",
            params: { 
              attrs: ["fill""fill-rule"], // 删除 svg 图标的 fill 和 fill-rule 就可以通过css改变颜色
            },
          },
        ]),
      })
      .end();

第三部,使用 svg-sprite-loader 自动生成 SVG 雪碧图

这里又是一个神器 svg-sprite-loader,通过它我们可以很方便的在 webpack 中生成雪碧图,完整配置如下

// vue.config.js
const path = require("path");
const { extendDefaultPlugins } = require("svgo"); // 注意此处为 svgo 2.0 版本以上插件使用方法

function resolve(dir) {
  return path.join(__dirname, dir);
}
module.exports = {
  chainWebpack(config) => {
    config.module
      .rule("svg")
      .exclude.add(resolve("src/icons"))
      .end();
    config.module
      .rule("icons"// icons 文件夹不走 vue-cli 默认的图标处理
      .test(/.svg$/)
      .include.add(resolve("src/icons"))
      .end()
      .use("svg-sprite-loader"// 生成 svg 雪碧图
      .loader("svg-sprite-loader")
      .options({
        symbolId"icon-[name]",
      })
      .end()
      .use("svgo-loader"// 注意loader顺序,后声明的先执行
      .loader("svgo-loader")
      .options({ // 如果是有色图标,可以删除本配置,只做压缩
        pluginsextendDefaultPlugins([
          {
            name"removeAttrs",
            params: {
              attrs: ["fill""fill-rule"],// 删除 svg 图标的 fill 和 fill-rule 就可以通过css改变颜色
            },
          },
        ]),
      })
      .end();
  },
};

这里贴一下原生 webpack 的配置代码

{
  test/.svg$/,
  include: [resolve('src/icons')],
  use: [
    /* config.module.rule('icons').use('svg-sprite-loader') */
    {
      loader'svg-sprite-loader',
      options: {
        symbolId'icon-[name]'
      }
    },
    /* config.module.rule('icons').use('svgo-loader') */
    {
      loader'svgo-loader',
      options: {
  
        pluginsextendDefaultPlugins([
          {
            name"removeAttrs",
            params: {
              attrs: ["fill""fill-rule"]
            },
          },
        ])
      }
    }
  ]
}

然后就可以愉快的使用图标了,使用示例

<!-- name传入图标名字就可以直接使用 -->
<svg-icon name="apple" class="red"></svg-icon> 
<svg-icon name="huawei" class="green"></svg-icon>
<svg-icon name="oneplus" class="yellow"></svg-icon>
<svg-icon name="pixel" class="dark"></svg-icon>

效果图

Snipaste_2021-07-28_23-03-09.png

四、总结

以上就是笔者摸索出来的工程化使用 SVG 图标的最佳实践,大家可以根据自己的实际情况在自己的项目里加以利用改造。gulp 和 vite 也可以进行类似的配置,读者们可以自行探索,找到适合自己的图标维护方案。如果你有静态资源服务器,好吧,当我没说。。。

代码库:github.com/jawn-ha/Svg…