在vue项目中使用 SVG Sprites

前言

网页图标展示方式大概可以分为以下几类

前端工程化 - 工程配置 vue-cli3+

在了解 SVG Sprites 技术之后,引出本文的主角:svg-sprite-loader 这是关键

1、雏形

vue-cli 内置的webpack配置会将svg文件使用 file-loader 进行加载,这不符合我们当前的需求

  • 解决方案:固定目录下的svg文件使用 svg-srpite-loader 进行加载,其它则继续使用 file-loader,目前网上搜索到的解决方案基本如此
// vue.config.js
const path = require('path');
function Resolve(dir) {
  return path.join(__dirname, dir);
}
module.exports = {
  chainWebpack: config => {
    config.module.rule('svg').exclude.add(Resolve('src/assets/icons')); //让file-loader排除掉这个目录
    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()
      //使用svgo-loader 进行优化,去除svg本身的填充色
      .use('svgo-loader').loader('svgo-loader').options({
        plugins: [
          {removeAttrs: {attrs: 'path:fill'}},
          { removeXMLNS: true }
        ]
      })
  }
}
// main.vue
<template>
  <svg>
    <use :xlink:href="'#'+iconId"></use>
  </svg>
</template>
<script>
  import MySvg '@/assets/icons/logo.svg';  // src/assets/icons/logo.svg
  export default {
    data() {
      return {
        iconId: MySvg.default.id
      }
    }
  }
</script>

这样做有一个问题,如果图标是页面专属,其它页面不会用到,把svg都放在同一个目录下,没有跟随组件目录,那这样就非常不好管理。

2、优化

vue-cli的官方文档中看到这么一段话:

2020-11-27_114659.jpg

不再局限于 src/assets/icons 目录下,而是判断文件名以 .icon.svg 结尾:

// vue.config.js
const path = require('path');
function Resolve(dir) {
  return path.join(__dirname, dir);
}
module.exports = {
  chainWebpack: config => {
    //svg sprites
    const svgRule = config.module.rule('svg')
    svgRule.uses.clear()
    svgRule.oneOf('icon').test(/\.icon\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon_[hash:base64:5]',
      }).end()
      .use('svgo-loader').loader('svgo-loader').options({
        plugins: [
          {removeAttrs: {attrs: 'path:fill'}},
          { removeXMLNS: true }
        ]
      }).end()

    //普通svg图片
    svgRule.oneOf('img').test(/\.svg$/)
      .use('file-loader')
      .loader('file-loader').options({
        name: 'img/[name].[hash:8].[ext]'
      })
  }
}

3、和 importrequire 说再见吧

经过上面的优化后,在svg文件引入的方式上还是有些不尽人意,有没有像 img 标签那样舒服的使用方式呢?当然有!首先看看在基于vue-cli脚手架的项目中是怎么实现:
处理静态资源

2020-11-27_120854.jpg

当然,这需要vue-loader处理,详细文档
修改vue-loader后的最终配置:

const path = require('path');
function Resolve(dir) {
  return path.join(__dirname, dir);
}
module.exports = {
  chainWebpack: config => {
    //svg sprites
    const svgRule = config.module.rule('svg')
    svgRule.uses.clear()
    svgRule.oneOf('icon').test(/\.icon\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon_[hash:base64:5]',
      }).end()
      .use('svgo-loader').loader('svgo-loader').options({
        plugins: [
          {removeAttrs: {attrs: 'path:fill'}},
          { removeXMLNS: true }
        ]
      }).end()

    //普通svg图片
    svgRule.oneOf('img').test(/\.svg$/)
      .use('file-loader')
      .loader('file-loader').options({
        name: 'img/[name].[hash:8].[ext]'
      })

    //vue-loader
    config.module
    .rule('vue')
    .use('vue-loader')
      .loader('vue-loader')
      .tap(options => {
        // 修改它的选项...
        options.transformAssetUrls = {
          video: ['src', 'poster'],
          source: 'src',
          img: 'src',
          image: ['xlink:href', 'href'],
          use: ['xlink:href', 'href'],
          'vc-svg-icon': 'src'  //这一项是关键,下面的组件封装有用到
        }
        return options
      })
  }
}

上面的 options.transformAssetUrls['vc-svg-icon'] = 'src',让vue-loader识别vc-svg-icon组件的src属性,并对其解析为一个 模块依赖

4、组件封装

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

<script>
  export default {
    name: 'SvgIcon',
    props: {
      src: module
    },
    computed: {
      href() {
        return `#${this.src.default.id}`
      }
    },
    mounted() {
      //初始化时查找是否存在该 symbol
      const container = document.querySelector('#__SVG_SPRITE_NODE__')
      if (!container.querySelector(this.href)) {
        container.insertAdjacentHTML('beforeend', this.src.default.content)
      }
    },
    destroyed() {
      //组件销毁后查找是否存在无用的 symbol,没有被vc-svg-icon组件引用则移除对应的symbol
      const hasIcon = document.querySelector(`[icon-href="${this.href}"]`)
      const symbol = document.querySelector('#__SVG_SPRITE_NODE__').querySelector(this.href)
      if (!hasIcon && symbol) {
        symbol.remove()
      }
    }
  }
</script>
<style>
  .vc-svg-icon {
    width: 1em;
    height: 1em;
    vertical-align: middle;
    fill: currentColor;
    overflow: hidden;
  }
</style>

mounteddestroyed 生命周期里的处理是为了“用完即销毁”,可自行斟酌,非必要,因为symbol标签的父级svg#__SVG_SPRITE_NODE__是隐藏的(display:none),大量存在无用的symbol标签也不会造成性能问题

5、使用

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

推荐阅读更多精彩内容