优雅的使用SVG图标

2,460 阅读3分钟

前言

在前端项目中,使用自定义的icon图标时,需要引用很长的相对地址,添加和修改图标会变得很麻烦。

<img src="./long/path/to/your/svg/icon.svg" />

有没有更简单的icon图标引入方式呢?像使用组件库中的ICON组件一样。

<a-icon type="fast-backward" />

答案是肯定的,我们可以通过SVG Sprite简化自定义图标的使用方式。

SVG Sprite

如果你没听过SVG Sprite,也许听过雪碧图(CSS Sprite),如下图所示。雪碧图是为了减少网络请求次数,将许多小图标整合到一张图片上,然后通过CSS定位技术显示特定位置的图标。雪碧图在使用上存在一些弊端,目前已经很少使用了。

image.png

类似的SVG Sprite是通过<symbol><use>实现的。<symbol>元素可以把svg图标定义成一个图形模板对象,<use>元素通过xlink:href属性引用symbol id展示图形。下面代码定义了三个<symbol>图形模板,此时图形并不会展示到页面上,通过<use>元素引用symbol id后才可展示图形。

//定义图形
<svg width="0" height="0">
    <symbol id="shape1">
        <circle cx="40" cy="40" r="24" style="stroke:#006600; fill:#00cc00"/>
    </symbol>
    <symbol id ="shape2">
        <rect x="10" y="10" height="100" width="100" style="stroke:#006600; fill: #00cc00"/>
    </symbol>
    <symbol id="shape3">
        <polygon points="10,0  60,0  35,50" style="stroke:#660000; fill:#cc3333;"/>
    </symbol>
</svg>
//引用图形
<svg width="500" height="200">
    <use xlink:href="#shape1" x="0" y="25" />
    <use xlink:href="#shape1" x="60" y="25" />
    <use xlink:href="#shape2" x="150" y="0" />
    <use xlink:href="#shape3" x="280" y="10" />
</svg>

image.png

通过上面示例代码可以看出:

  • <use>元素可以跨<svg>元素引用<symbol>
  • <use>可以重复引用<symbol>

如果将项目中的svg图标用<symbol>元素定义成图形模板,并将其组合成一个大的<svg>加载到页面中,如下图所示。那么我们可以在页面的任何位置,只需要一行代码就可以引用这个图标了。

<svg class="size"><use xlink:href="#icon-tubiao" /></svg>

image.png

进一步,将<use><symbol>元素的引用封装到组件中,通过动态的传递svg文件名来展示不同的svg图标,从而实现简洁的引用自定义图标。

<svg-icon name="tubiao" />

如何将svg图标组合一起呢?svg-sprite-loader可以帮我们实现。

svg-sprite-loader使用

svg-sprite-loader会把项目中引入的svg图标塞到一个个<symbol>中,合成一个大的svg,最后将这个大的svg插入到<body>元素中。

下面基于vue2.0介绍svg-sprite-loader配置。

1. 安装svg-sprite-loader

npm i svg-sprite-loader -D

2. 配置vue.config.js

本示例中是将项目中的svg图标统一放到在src/icons/svg目录中,可根据实际需要调整。配置svg-sprite-loader前,首先清除webpack默认loader(如url-loader,file-loader等)对src/icons/svg目录中svg图标处理,然后添加svg-sprite-loader对src/icons/svg目录下的svg图标的处理。指定symbolId为文件名加上“icon-”前缀。

const path = require("path");
function resolve(dir) {
  // __dirname项目根目录的绝对路径
  return path.join(__dirname, dir);
}
module.exports = {
  chainWebpack: (config) => {
    //默认规则不处理src/icons目录下的svg文件
    config.module.rule("svg").exclude.add(resolve("src/icons/svg")).end();
    //针对icons/svg目录下的文件添加svg-sprite-loader规则
    config.module
      .rule("svgicon")
      .test(/\.svg$/)
      .include.add(resolve("src/icons/svg"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "icon-[name]",
      });
  },
}

3. 封装SvgIcon组件

<use><symbol>元素的引用封装到组件中,通过动态的传递svg文件名来展示不同的svg图标。

image.png

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

<script>
export default {
  name: "SvgIcon",
  props: {
    name: {
      type: String,
      required: true,
    },
    className: {
      type: String,
    },
  },
  computed: {
    iconName() {
      //构造symbolId
      return `#icon-${this.name}`;
    },
    svgClass() {
      if (this.className) {
        return "svg-icon " + this.className;
      } else {
        return "svg-icon";
      }
    },
  },
};
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
</style>

4. 全局注册SvgIcon组件,并加载所有图标。

  • 在icons目录下,分别创建svg目录和index.js文件。
  • 在svg目录中存放项目中使用到的所有svg图标。
  • 在index.js中,将SvgIcon组件,注册为全局组件,之后就可以全局使用了。
  • 在index.js中,将svg目录中的图标一次性导入,之后就不需要单独引入了。 image.png
import Vue from "vue";
import SvgIcon from "../components/svgIcon/Index"; //svg组件
//全局注册组件
Vue.component("svg-icon", SvgIcon);
// 定义一个加载目录的函数
const requireAll = (r) => r.keys().map(r);
// 批量导入svg目录下的svg文件
requireAll(require.context("./svg", false, /\.svg$/));

5. 在入口文件中导入icons/index.js 文件

在main.js入口文件导入icons/index.js文件,使其在首次导入时运行一次。

import "./icons/index.js"

image.png

6. 使用

现在只需传入文件名即可引入svg图标。

//name:svg图标的文件名
//class-name:svg图标的样式类名 
<svg-icon name="download" class-name="icon" />
<svg-icon name="supervise" class-name="icon" />
<svg-icon name="view_off" class-name="icon" />

下图是使用SvgIcon组件后的页面DOM结构,可以看到<body>元素中有一个由多个<symbol>元素组成的<svg>,通过<use>元素将图标展现在了页面中。 image.png

总结

本文首先介绍了SVG Sprite原理,然后详细介绍了svg-sprite-loader的安装、配置及SvgIcon组件的封装与使用。希望本文对你起到帮助。