在vue项目中配置使用svg

日常使用svg作为图标使用已经很常见了,以下为几种使用方式 .

file-loader 解析资源文件

最原始的无非就是直接导入使用了 , webpack使用了file-loader 对所有静态资源进行路径解析,包括图片、字体、文件等.

简单的配置对.svg 的解析.

module.exports = {
  module: {
    rules: [
      {
        test: /\.(svg)(\?.*)?$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: 'img/[name].[hash:8].[ext]',
            },
          },
        ],
      },
    ],
  },
};

filer-loader 可以解析import/require() 文件路径,并把文件输出到构建目录中.

然后在组件文件中使用导入的资源

<template>
  <div>
    <img :src="icon404" />
    <!-- <object :data="icon404"></object>
    <iframe :src="icon404"></iframe> -->
  </div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
// import icon404 from "@/assets/icons/404.svg";
const icon404 = require("@/assets/images/icon-404.svg");

interface IState {
  description: string;
}
@Component({
  name: "work-bench",
})
export default class extends Vue implements IState {
  description = "展示当前项目拥有的组件,数据皆为模拟测试数据.";
  // 实例中定义引用
  icon404 = icon404;
}
</script>

通过img \ object \ iframe 作为资源载体加载资源 . 这样引入的方式对于我们常用修改颜色、字号等产生很大不便. 组件中资源过多就存在大量的import .

可以通过打印查看icon404 的值是什么? 是对引用的当前.svg资源的路径地址.

svg-sprite-loader 声明式调用

为什么说是声明式调用 , 因为它将我们需要的svg资源作为一个模版变量,进而进行指向引用即可 .

首先我们按照npm包文档指引进行配置.

安装

# 或 yarn
npm install svg-sprite-loader -D

webpack 简单配置

变更了loader 为svg-sprite-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.(svg)(\?.*)?$/,
        use: [
          {
            loader: 'svg-sprite-loader',
            options: {
              symbolId: "icon-[name]",
            },
          },
        ],
      },
    ],
  },
};

现在在看组件内的.svg 引入 . 已经加载不出来了,这里要注意的是require 导入,

// ... 访问导入的值
this.icon404 = icon404.default;
通过svg标签use 呈现

通过打印查看导入的内容icon404

<symbol xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 400" id="icon-icon-404">
  // ... svg 内容
</sysmbol>

提供了id 作为引用键值 . 再使用img \ object 不行了, 了解过svg基础知识的同学知道, symbol 用来定义一个图形模版,然后使用use 呈现 .

svg-sprite-loader 会将加载的svg定义成symbol 汇聚到一个<svg> 元素中添加到body中.

这样的话 , 我们局部定义svg引用对应的symbol , 通过use标签引用资源.

导入的icon404 是一个SpriteSymbol<id,viewBox,content>实例, 通过id 进行访问 .

<template>
    <div>
    <svg>
       <use :xlink:href="`#${icon404.id}`" />
    </svg>
  </div>
</template>

当我们知道了svg的名称之后,加上webpack的配置symbolId: "icon-[name]" , 就不需要使用icon404 对象了,只需要导入

<template>
  <div>
    <svg>
       <use :xlink:href="#icon-icon-404" />
    </svg>
  </div>
</template>
<script lang="ts">
  import { Component, Vue } from "vue-property-decorator";
  // import icon404 from "@/assets/icons/404.svg";
  // const icon404 = require("@/assets/images/icon-404.svg");
  // 直接导入资源
  import "@/assets/icons/404.svg";

  interface IState {
    description: string;
  }

  @Component({
    name: "work-bench",
  })
  export default class extends Vue implements IState {
    description = "展示当前项目拥有的组件,数据皆为模拟测试数据.";
    // 此处已不再需要定义实例中的变量
    // icon404 = icon404.default;
  }
</script>

就可以愉快的使用啦. 这样还存在一个问题就是,每个需要使用的组件都需要导入 . 而且这明显可以作为一个模版抽离出来进行组件化使用.

批量处理svg

按照上面的思路,我们可以定一个index.ts . 导入所有的svg图片,然后导出 ,

// 引入一遍所有的svg图标
import '@/assets/images/icon-404.svg'
import '@/assets/images/icon-404.svg'
import '@/assets/images/icon-404.svg'
import '@/assets/images/icon-404.svg'
// ... 更多

记得要在主入口文件main.ts 引入.

// icon-svg
import "@/components/svgIcon/index.ts";

这样在所有vue组件中使用,不需要每次都导入了svg了.

定义svg-icon 公共组件

当我们在多个组件中使用某一代码段时,我们就想把它抽离成组件.定一个svgIcon.vue . 这样的

<template>
  <svg :class="svgClass" v-on="$listeners">
    <use :xlink:href="svgName" />
  </svg>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";

@Component({
  name: "svg-icon",
})
export default class extends Vue {
  @Prop() /* 图标名称 */ readonly iconName!: string;
  @Prop({ default: "" }) /* 图标类名 */ readonly iconClass?: string;
    
  // 我们配置的loader 选项中,定义了 icon-[name]
  get svgName() {
    return `#icon-${this.iconName}`;
  }
  get svgClass() {
    return `svg-icon ${this.iconClass}`;
  }
}
</script>
<style lang="less" scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  fill: currentColor;
  overflow: hidden;
}
</style>

包含了svg名称 , 内部处理引入的sysmbolId . 使用的人只需要知道svg名称是什么就行了. 不关注webpack配置. 还可以加上自定义class名.以及原生事件等等.

注册为全局组件,在index.ts 调整,

import Vue from "vue";

import SvgIcon from "./index.vue";
// 引入一遍所有的svg图标
import '@/assets/images/icon-404.svg'
import '@/assets/images/icon-404.svg'
import '@/assets/images/icon-404.svg'
import '@/assets/images/icon-404.svg'
// ... 更多

// SVG 图标
Vue.component("svg-icon", SvgIcon);

然后就可以在所所有的组件中愉快的使用公共组件svg-icon 了,不需要每次都导入.

怎么使用呢, 还是上面的组件.

<template>
  <div>
    <!--<svg>
       <use :xlink:href="#icon-icon-404" />
    </svg>-->
    <!-- 正常使用一个vue组件 -->
    <svg-icon iconName="icon-404"></svg-icon>
  </div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
// 导入不需要
// import icon404 from "@/assets/icons/404.svg";
// const icon404 = require("@/assets/images/icon-404.svg");

interface IState {
  description: string;
}

@Component({
  name: "work-bench",
})
export default class extends Vue implements IState {
  description = "展示当前项目拥有的组件,数据皆为模拟测试数据.";
  // 不需要
  // icon404 = icon404.default;
}
</script>

是不是很完美 . 还需要优化的点就是加载svg资源的,我们只不过是换了个地方import

// 引入一遍所有的svg图标
import '@/assets/images/icon-404.svg'
// 成百上千个 ....

想办法批量加载完成 . 幸好webpack提供了一个API 用于做批量加载的事情. 传送门

require.context 引入指定文件夹下的所有文件

require.context(dir,useSubDir,regExp,mode)

  1. 需要加载的目录路径
  2. 是否需要搜索其他子目录.
  3. 加载匹配文件的正则表达式.
  4. 加载方式mode=sync

返回一个require函数 ,可以接受一个request参数 ;

require函数三个静态属性resolve,keys,id . 通过编译属性kyes 执行每一个加载资源的请求.

// 指定目录加载所有的.svg资源
const req = require.context("@/assets/images", false, /\.svg$/);
// 编译req属性的keys , 对每一个资源路径执行加载函数
req.keys().forEach(req)

然后我们的index.ts 文件就修改为

import Vue from "vue";
import SvgIcon from "./index.vue";
// import "@/assets/images/icon-404.svg";

// SVG 图标
Vue.component("svg-icon", SvgIcon);

const req = require.context("@/assets/images", false, /\.svg$/);
req.keys().forEach(req)

看起来就简洁多了, 你只需要往这个目录添加你想要使用的svg资源就行了, 然后在项目中使用它.

vue.config.js 配置

上面简单的自定义webpack配置时,如何配置webpack. 通常我们都使用了vue-cli 脚手架搭建vue项目.

那就要在vue.config.js 中调整loader配置了.

// vue.config.js
// eslint-disable-next-line
const path = require("path");

module.exports ={
  chainWebpack: (config) => {
    // svg-sprite-loader
    config.module.rules.delete("svg");
    config.module
      .rule("svg-sprite-loader")
      .test(/\.svg$/)
      .include.add(path.join(__dirname, "./src/assets/icons"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({ symbolId: "icon-[name]" });
    }
}

首先移除了.svg 的默认loader 规则配置. 开始添加针对.svg 文件的loader规则配置.

关于vue.config.js 配置详解,还会有另一篇文章来说明 . 解析chainWebpack是如何运转的.

svgo-loader 优化svg

svgo-loader依赖安装svgo , 用来优化svg资源 , 清理掉多余、不必要的信息,比如:元数据信息、批注信息、隐藏的元素等.

还有用来操作svg,svgo 可以转换svgSVG-as-XML SVG-as-JS AST 树从而手动新增、更新、删减元素 .

{
    content: [
      {
        doctype:'',
      },
      {
        comment:'',
      },
      {
        elem: 'svg',
        local: 'svg',
        attrs: {
          // ...
        },
        content:[
          {
            // ... 嵌套子元素
          }
        ]
      }
    ]
}

需要更详细操作svg的可以自行去查看这个库, 没怎么具体使用过. 如果有这方面的需求,肯定还有有更细致的文章.

了解过之后,再来看svgo-loader

安装

 npm install svgo-loader --save-dev

webpack 配置

简单的配置,需要明确的是它不是file-loader \ svg-sprite-loader 的替代. 作用不一样.

起一个中间优化的作用.配置中添加svgo-loader 即可.

module.exports = {
  module: {
    rules: [
      {
        test: /\.(svg)(\?.*)?$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: 'img/[name].[hash:8].[ext]',
            },
          },
                    {
            loader:"svgo-loader"
          }
        ],
      },
    ],
  },
};

vue.config.js 配置

在之前配置的svg-sprite-loader 追加loader配置即可.

// vue.config.js
// eslint-disable-next-line
const path = require("path");

module.exports ={
  chainWebpack: (config) => {
    // svg-sprite-loader
    config.module.rules.delete("svg");
    config.module
      .rule("svg-sprite-loader")
      .test(/\.svg$/)
      .include.add(path.join(__dirname, "./src/assets/icons"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({ symbolId: "icon-[name]" })
        .end()
      .use("svgo-loader")
      .loader("svgo-loader");
    }
}

链接资源

webpack依赖管理-require语句 -

svg基础知识学习

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容