• 作者:老汪软件技巧
  • 发表时间:2024-09-24 17:02
  • 浏览量:

对于直接想要直接使用上手的同学请下拉至完整流程或点击右侧目录”完整流程“一键到达

正文为什么要使用 SVG ?

SVG 英文全称为( Scalable Vector Graphics),意思为可缩放的矢量图形,不管你再怎么的放缩它都不会失真模糊,不像 image 缩放到一定程度就会失真模糊。

而对于 SVG 的使用,有以下几种方式:

svg 所在路径:@/assets/icons/example.svg

第一种(刚入职的我):

<img src="@/assets/icons/example.svg" alt="example" />

第二种(现在的我):

 <svg><use :xlink:href="#icon-example" />svg>

第一种方式是最简单的方式,直接将 SVG 文件当作图片使用,但写起来比较麻烦,当需要使用 SVG 的文件多起来的话,就得不断地写图片路径。

第二种方式,是通过 svg-sprite 的方式使用的,相比于你需要找一长串的路径引用之后才能使用,通过webpack 的一些简单配置,就能直接使用 svg 的文件名称来引入,并且在性能上有一定的优化(减少了页面的请求资源)。

svg-sprite 和 css-sprite

因此想要使用 SVG ,svg-sprite 是个很好的选择,为了方便对 svg-sprite 有个直观的理解,这里我们和css-sprite 作个横向对比:

css-spirte 可以看看下图(我们拿懂车帝官网中的分类导航作为例子),也就是所谓的雪碧图,是将多个图片合成一个图片,然后利用 css 的 background-position 定位显示不同的 icon 图标。

懂车帝官网导航栏展示效果:

实际雪碧图:

好处是减少了页面的请求资源,将多个图片请求变为了一个图片请求。

但这个也有一个很大的痛点,维护困难。每新增一个图标,都需要改动原始图片,还可能不小心出错影响到前面定位好的图片,而且一修改雪碧图,图片缓存就失效了,久而久之你不知道该怎么维护了。又或者要命的问题是定位遇到兼容问题,1px,0.5px 偏差时,调整来调整去,搞的你死去活来。

而 svg-sprite 类似 css-sprite,也是多个 svg 图片合在一起(减少了请求资源)。但相比于 css-sprite 使用 background-position 定位来使用图片,svg-sprite 是可以通过 标签直接获取到对应的 svg 图片来使用(具体使用方式下文会说),不需要做什么定位样式处理操作,非常的灵活。

SVG 代码

首先你得对 svg 的代码有一些了解

<svg t="1724898078854" class="example" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4846" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32">
  
  
  <path d="M 512 100 L 912 800 L 112 800 Z" fill="blue">path>
svg>

但。。。寻常的 svg 代码如果通过 来使用的话,是没有办法使用的。因为 只能引用带有 id 的图形元素,并不能引用整个 SVG 文件。因此,我们通常会在 svg 里套一层带有 id 的 symbol 元素(后续会通过 webpack 中 loader 自动添加带有 id 的 symbol 元素)。

symbol

因此我们再次引入了 symbol 这个概念

<svg >
  
  <symbol id="icon-example" viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="40" fill="blue"/>
  symbol>
svg>

下述 symbol 介绍引入的是张鑫旭大大的文章未来必热:SVG Sprite技术介绍中的一段文字

symbol 元素是什么呢?单纯翻译的话,是 “符号” 的意思。但我个人觉得,symbol 应该解释为 “元件” 最为恰当!

我们可以把 SVG 元素看成一个舞台,而 symbol 则是舞台上一个一个组装好的元件,这些一个一个的元件就是我们即将使用的一个一个 SVG 图标。

于是,对于一个集合了三个 SVG 图标的 SVG 元素的代码结构会是这样:

<svg>
    <symbol>
        
        <path d=M L>path>
    symbol>
    <symbol>
        
        <path d=M L>path>
    symbol>
    <symbol>
        
        <path d=M L>path>
    symbol>
svg>

每一个 symbol 就是一个图标元件,但是,只有上面的代码,是无法呈现类似下面的效果的:

为何?

因为,舞台上只是放置了图标,如果你不使用(use),是看不见的。

因此,还差一个“使用”,也就是SVG中的 元素。

但一般的 svg 文件中并没有 symbol 这个元素,因此我们需要进行处理,也就引出了 svg-sprite-loader

svg-sprite-loader

svg-sprite-loader 是一个 webpack 加载器(loader),用来根据导入的 svg 文件自动为其生成 symbol 标签,专门用于将多个 SVG 文件打包成一个 svg-sprite( SVG 精灵图),并通过 标签来复用这些图标。

<svg>
    <circle cx="50" cy="50" r="40" fill="blue"/>
svg>

插入 symbol

<svg>
  
  <symbol id="icon-example" >
    <circle cx="50" cy="50" r="40" fill="blue"/>
  symbol>
svg>

OK,这下对于 svg-sprite 有了比较完整的认识了,接下来就是如何在项目中使用了。

代码使用安装插件

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

webpack 配置(以vue.config.js为例)定义绝对路径函数

const path = require('path')
​
function resolve(dir) {
  return path.join(__dirname, '.', dir)
}

我们需要把本地的 svg 文件统一经过 svg-sprite-loader 处理,因此通常会把 svg 文件放到同一个文件夹路径下(即src/icons),所以我们需要通过 path 来定义一个绝对路径的函数 resolve()。

loader 的配置

module.exports = {
    // set svg-sprite-loader
  【1】config.module.rule("svg").exclude.add(resolve("src/assets/icons")).end();
  
  【2] config.module
      .rule("icons")
      .test(/.svg$/)
      .include.add(resolve("src/assets/icons"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "icon-[name]",
      })
      .end();
}

【1】config.module.rule("svg").exclude.add(resolve("src/icons")).end();

end():表示结束链式调用,返回上一层。

【2】

用日本学50音图pdf_学车教练想睡人_

config.module.rule("icons")

.test(/.svg$/)

.include.add(resolve("src/icons")).end()

.use("svg-sprite-loader")

.loader("svg-sprite-loader")

.options({ symbolId: "icon-[name]" })(重点)

封装组件

webpack 配置完成之后,就是通过标签来使用了,

<svg><use :xlink:href="#icon-example" />svg> // #icon-之后的就是svg的文件名了

但如果每次需要使用 svg 时,都要写一遍代码过于麻烦,所以一般情况下都是进行封装的。

<template>
    // 直接传入svg文件名即可
    <svg-icon icon-class="example" />
template>

// SvgIcon.vue
<template>
   // 通过use使用,直接传入svg文件名即可
   <svg>
      <use :xlink:href="iconName" />
   svg>
template>
    ​
    <script>
    export default {
      props: {
        iconClass:  String,
      },
      computed: {
        iconName() {
          return `#icon-${this.iconClass}`;
        },
      },
    };
script>

直接把组件在 Vue 全局中注册

// src/icons/index.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component// register globally
Vue.component('svg-icon', SvgIcon)

完事了?等等,并没有哦~

手动导入 svg

我们目前只是配置了 svg ,但并没有加载它,不像我们在使用

一样,

会自动加载 svg。

而我们在使用 标签引用 SVG 定义(通常是在 HTML 或 JS 中内嵌),你可以把 SVG 文件作为一个模块使用,像调用组件一样复用和管理图标,因此需要手动导入。在页面加载的时候导入这些 svg 文件,就类似于在项目中你需要 import 各种各样的 js,css,vue 文件。

    import '@/src/icons/example.svg';

类似于上述,但是一个一个引入太过于麻烦了,因此我们又要配置了,能否让其自动的批量导入

自动批量导入

此时,我们就要使用到 webpack 的 require.context

// src/icons/index.jsconst req = require.context('@/icons/svg', true, /.svg$/)
    
// 函数requireAll(这里我们代码复杂点方便理解),
const requireAll = (req) =>
   // req.keys()拿到所有的svg路径
   req.keys().map((item) => {
        // req(item) 导入svg
        return req(item);
   });
}
// 简化后requireAll函数代码
// const requireAll = requireContext => requireContext.keys().map(requireContext)
    
// 调用requireAll
requireAll(req);

很多人对于require.context 可能比较陌生,下面我们对于这段代码进行详细的阐述

req = require.context("@/icons/svg", true, /.svg$/);**

require.context("@/icons/svg", true, /.svg$/);

req 是通过 require.context 创建的上下文对象,用于动态导入模块。具体来说,req 包含了匹配指定路径下的所有 SVG 文件的信息和方法。

1. require.context 的参数

const req = require.context(directory, useSubdirectories, regExp);

2. req 的特性

3. 整体作用

req 使你能够批量导入指定目录中的所有 SVG 文件。例如:

const svgFiles = req.keys().map(req); // 这里是简化后的代码

main.js中引入

import '@/src/icons/index.js'

这样,我们就能在任意 Vue 中使用

<template>
    // 直接传入svg文件名即可
    <svg-icon icon-class="example" />
template>

完整流程

首先,安装好 svg-sprite-loader

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

其次,配置好 webpack 文件(我这里配置的是 vue.config.js)

const path = require("path");
    ​
function resolve(dir) {
   return path.join(__dirname, dir);
}
    ​
module.exports = {
   chainWebpack(config) {
        // set svg-sprite-loader
        config.module.rule("svg").exclude.add(resolve("src/icons")).end();
        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();
   },
}

接着,封装好组件,在 src/components/SvgIcon/index.vue 文件夹下

<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName"/>
  svg>
template>
​
<script>
export default {
  name: 'SvgIcon',
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    }
  },
  computed: {
    iconName() {
      return `#icon-${this.iconClass}`
    },
    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>

然后,全局使用该组件并批量自动导入 svg 文件,在 src/icons/index.js

import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component// register globally
Vue.component('svg-icon', SvgIcon)
​
const req = require.context('./svg', false, /.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

最后,在 main.js 中引入

    import '@/src/icons/index.js'

在 src/icons/svg 下放入 svg 图标,然后在项目中使用即可

<template>
    // 直接传入svg文件名即可
    <svg-icon icon-class="search" />
template>

参考资料