Vue项目中如何优雅的使用icon

在开始了解如何在 Vue 项目中规范的使用 icon 前,我们需要先了解一些 icon 最基础的知识点- sprite 技术。

Sprite 技术

目前,SVG Sprite最佳实践是使用symbol元素。symbol元素是什么呢?单纯翻译的话,是“符号”的意思。然而这个释义并不符合这里的场景。不知大家有没有用过Flash,symbol实际上就类似于Flash中的“影片剪辑”、或者“元件”。因此,我个人觉得,symbol应该解释为“元件”最为恰当!那,symbol和SVG Sprite又有什么关系呢?我们可以把SVG元素看成一个舞台,而symbol则是舞台上一个一个组装好的元件,这这些一个一个的元件就是我们即将使用的一个一个SVG图标。
于是,对于一个集合了三个SVG图标的SVG元素的代码结构会是这样:

<svg>
    <symbol>
        <!-- 第1个图标路径形状之类代码 -->
    </symbol>
    <symbol>
        <!-- 第2个图标路径形状之类代码 -->
    </symbol>
    <symbol>
        <!-- 第3个图标路径形状之类代码 -->
    </symbol>
</svg>

每一个symbol就是一个图标元件,但是只有上面的代码,是无法呈现任何东西的。
因为一个 symbol 元素本身是不呈现的,只有 symbol 元素的实例(一个引用了 symbol 的 元素)才能呈现。

symbol 就像一件衣服,没人穿 就不知道上身效果如何。

而 use 元素是 SVG 中非常强大,非常重要的一个元素,尤其是在 web 开发中:

  • 可重复调用;
  • 跨 SVG 调用;

1、可重复调用

开发中,你好不容易用了很多坐标值,绘制了一个图形,如果再弄一个相同的图形,你会怎么办?再复制一遍代码吗?学编程的都知道一个道理,重复调用的东西我们就要封装,而在 SVG 中我们不用封装,只需直接重复 就行:

<svg>
  <symbol>
    <g id="shape">
        <rect x="0" y="0" width="50" height="50" />
        <circle cx="0" cy="0" r="50" />
    </g>javascript:;
  </symbol>

  <use xlink:href="#shape" x="50" y="50" />
  <use xlink:href="#shape" x="200" y="50" />
</svg>

同样的 symbol ,只是调用的时候 x 轴距离稍微不一样,我们可直接用 调整即可。
首先,注意到没有,use元素是通过xlink:href属性,寻找要使用的元素的。#shape对应的就是id为shape的元素。use元素可以有自己的坐标,以及支持transform变换,甚至可以use其他use元素。
这里,两个use元素使用的是同一个 symbol 元素(组合),从而实现了图形的重复调用功能。
2、跨 SVG 调用
SVG中的use元素可以调用其他SVG文件的元素,只要在一个文档(HTML)中。
假设上诉例子中id为 shape 的元素和如下例子在同一个 HTML 中,一样是可以使用的:

<svg width="500" height="110">
    <use xlink:href="#shape" x="50" y="50" />
</svg>

而这个跨SVG调用就是“SVG Sprite技术”的核心所在。
试想下,我们只要在页面某处载入一个充满Sprite(symbol)的SVG文件(或直接include SVG代码),于是,在页面的任何角落,只要想使用这个图标,只要简单这一点代码就可以了,图标尺寸CSS控制,里面只有一个仅有xlink:href属性的use元素,Done! 完成!也即是说,在HTML层面,图标使用的代码成本,跟传统的CSS Sprite或者流行的font-face几乎无异,代码简洁,而且很好维护。所有的SVG图标都在一个SVG源上。尺寸可任意拉伸,且颜色可控,真乃Web图标的未来之星。

如何在 Vue 项目中使用 Sprite 技术

上诉我们了解了 Sprite 技术的基本原理,无非就是引入 iconfont.js (包含已生成的所有 symbol,一个用 js 生成的 svg 代码)然后就可以直接 use 了。但是有个缺点,就是不够直观,毕竟没人能直接从代码中看出自己引入的 icon 是什么样的,也不知道所需的 icon 是对应哪个图标名,每次使用都要去查文档,而且增删改图标时,都得生成新的 js 去替换原来的 iconfont.js。

所以在 Vue 项目中,我们可以使用 svg-sprite-loader ,这是一个 webpack loader ,事实只要是使用 webpack 编译的项目都可以用这个插件,它可以将多个 SVG 图片打包成 svg-sprite。

首先,我们在 Vue 项目中安装它:

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

然后创建一个文件夹:
在 src 目录下创建文件夹,主要是用来存放要使用的 svg 图片文件,例如 src/svg

配置 webpackConfig:
既然安装了 svg-sprite-loader ,要使用它还需进行 webpack 的配置才行

1、Vue CLI3.0及以上版本,我们主要是使用 vue.config.js 进行 webpack 配置:

module.exports = {
  chainWebpack: config => {
    // 清空默认svg规则
    config.module
      .rule('svg')
      .uses.clear()
    config.module //针对svg文件添加svg-sprite-loader规则
      .rule('svg1')
      .test(/\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })
      .end()
  }
}

或者

module.exports = {
  chainWebpack: config => {
    // svg rule loader
    const svgRule = config.module.rule('svg') // 找到svg-loader
    svgRule.uses.clear() // 清除已有的loader, 如果不这样做会添加在此loader之后
    svgRule.exclude.add(/node_modules/) // 正则匹配排除node_modules目录
    svgRule // 添加svg新的loader处理
      .test(/\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]',
      })
    // 修改images loader 添加svg处理
    const imagesRule = config.module.rule('images')
    imagesRule.exclude.add(resolve('src/assets/icons'))
    config.module
      .rule('images')
      .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
  }
}

2、而在之前旧的脚手架搭建的 Vue 项目,如 超融合项目这些,我们是在 src/build/webpack.base.conf.js 中配置:

module: {
    rules: [
      {
        test: /\.svg$/,
        loader: 'svg-sprite-loader',
        include: [resolve('src/svg')],
        options: {
          symbolId: 'icon-[name]'
        }
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        exclude: [resolve('src/svg')], // 去除默认图片处理对指定 svg 的影响
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      }
    ]
  }

自动导入

当我们将要用的图标放入上诉操作中创建的文件夹时,我们还需要用到 webpack 的 require.context 去对这些图标文件进行导入。

require.context(directory,useSubdirectories,regExp):

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

require.context("./test", false, /.test.js$/); 这行代码就会去 test
文件夹(不包含子目录)下面的找所有文件名以 .test.js 结尾的文件能被 require 的文件。 更直白的说就是
我们可以通过正则匹配引入相应的文件模块。

在 main.js 中加入以下代码:

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

之后我们直接对svg 文件夹中的图标进行增删改就行,什么都不用管,就会自动生成 svg symbol了。
这个时候我们可以在项目中直接使用图标了:

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

但是我们可以将其封装下成标准的 Vue 组件:

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

<script>
export default {
  name: 'SvgIcon',
  props: {
    iconClass: {
      type: String,
      required: true,
    },
    className: { // 自定义 svg 类名,后期可根据类名修改 svg 样式
      type: String,
      default: '',
    },
  },
  computed: {
    iconName() {
      return `#icon-${this.iconClass}`; // 拼接成设置好的 id 名格式
    }
  },
};
</script>

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

这样我们就大功告成,可以在 Vue 项目中,随心所欲的直接使用 SVG 文件了。

上一篇:android-Andengine-在精灵上/中绘制文字


下一篇:Unity图集Include In Build问题