手摸手,带你优雅的使用 SvgIcon

1,505 阅读4分钟

手摸手,带你优雅的使用 SvgIcon

前言:在做前端后台项目的时候经常会用到很多 icon 图标,刚开始还好,但随着项目的不断迭代,每次修改添加图标会变得很麻烦,而且总觉得不够优雅,就开始琢磨着有啥简单方便的工作流呢?

演进史

远古时期,大部分图标都是用img的方式导入,渐渐发现一个页面的请求资源中img资源占用了大部分,所以为了优化有了精灵图。所谓精灵图就是将多个图片合成一张图片,然后利用css的background-postion定位显示不同的icon。但是也有很大的痛点,维护困难,每次图片变更就需要重新修改精灵图。

font库实现页面图标,例如Font Awesome,使用起来也非常的方便,但是找起来很不方便,非常费眼睛,定制性需求也非常不友善,常常出现找不到所需要图标的情况。

**iconfont**一个阿里爸爸做的开源图库,支持上传下载以及颜色自定义设置,不仅有几百个公司的开源图标库,还有各式各样的小图标,还支持自定义创建图标库,所以不管你是一家创业公司还是对设计很有要求的公司,它都能很好的帮助你解决管理图标的痛点。你想要的基本都有~

svg矢量图,用于描述二维矢量图形的一种图形格式,可以任意定制,图像质量不下降的情况下可任意放大或缩小,超强显示效果,完全支持DOM。

svg的使用方式

1. 直接通过img标签使用svg

首先,可以在iconfont中寻找所需要的icon,并下载为svg格式

1.png

其次新建一个html文件 输入img 通过直接给src复制当前文件所在目录即可展示

 <img src="../src/icons/svg/dashboard.svg" alt="">
 <img src="../src/icons/svg/example.svg" alt="">
 <img src="./haha.svg" alt="">

我们来看一下效果~

2.png

2. 直接使用svg标签

在html文件中直接复制svg文件

 <svg width="128" height="100" xmlns="http://www.w3.org/2000/svg">
     <path
  d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 ...

3. 使用symbol-元件包裹多个svg(雪碧图原理)

这样的做法相当于将所有的小icon全部导入,然后利用symbol的语法 使用对应的svg,为下一节的封装svg组件做铺垫

​ 雪碧图原理 symbol - 元件

​ 1. svg标签改成symbol标签

​ 2. 用svg把所有symbol包裹

​ 3. 给所有symbol元素加唯一标识

​ 4. 使用use标签,需要那个svg就直接使用

<symbol id="a">  id代表对应的svg编号 也就是唯一标识 
    ... svg源文件A
</symbol>
<symbol id="a"> 
    ... svg源文件B
</symbol>
.....
使用方式 #a 显示对应的svg
 <svg aria-hidden="true" class="svg-icon">
     <use xlink:href="#a"></use>
 </svg>

大家想一个问题,项目中要使用到的icon有很多,一个一个引入非常非常麻烦,有没有什么快捷的方式,直接生成svg精灵图,在开发过程中,我们只需要使用就可以了呢?

接下来我为大家介绍在vue2以及vue3中如何使用对应的svg精灵图技术

vue2项目 - svg-sprite-loader

1. 通过vue create vue-demo 快速创建vue2项目
2. 删除项目中多余的组件 保留app.vue
3. 在src下创建icons文件夹,接着在此文件夹下创建svg文件,导入所有需要使用到的svg文件(自行添加svg)
4. 安装组件svg-sprite-loader
npm install svg-sprite-loader --save-dev      ||   yarn add svg-sprite-loader

此时我们打开浏览器控制台,已自动生成svg雪碧图

3.png

5. 在src/components下新建文件夹及文件SvgIcon/index.vue,index.vue添加如下内容:

这一步的目的就是封装全局svg组件,并进行默认配置和通讯。

<template>
  <div
    v-if="isExternal"
    :style="styleExternalIcon"
    class="svg-external-icon svg-icon"
   // listeners属性它是一个对象里面包含了作用在这个组件上的所有监听器你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件v-on="$listeners"
  />
   // 使用svg精灵图的固定写法
  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
      // iconName用于接收传递的svg编号
    <use :xlink:href="iconName" />
  </svg>
</template>

<script>
//导入公共方法,校验传入的iconClass是否为外部链接
//匹配http或者 https
import { isExternal } from '@/utils/validate'
export default {
  name: 'SvgIcon',
  props: {
    // 当前svg编号=>必填
    iconClass: {
      type: String,
      required: true
    },
    // svg组件名词 =>选填 可以不写
    className: {
      type: String,
      default: ''
    }
  },
  computed: {
  //匹配http或者 https
    isExternal () {
      return isExternal(this.iconClass)
    },
    iconName () {
      return `#icon-${this.iconClass}`
    },
    svgClass () {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'
      }
    },
    styleExternalIcon () {
      return {
        mask: `url(${this.iconClass}) no-repeat 50% 50%`,
        '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
      }
    }
  }
}
</script>

<style scoped>
// 限制svg大小为父元素的大小    
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}

.svg-external-icon {
  background-color: currentColor;
  mask-size: cover !important;
  display: inline-block;
}
</style>
6. 需要将当前svg组件设置为全局组件

在src下新建icons文件夹,及icons文件夹下svg文件夹、index.js文件,将svg图片放入svg文件夹中,在 index.js文件中添加如下内容

import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg 组件

// 全局注册svg组件
Vue.component('svg-icon', SvgIcon)
// 工程化导入svg图片 require.context(directory,useSubdirectories,regExp)
// directory:表示检索的目录 useSubdirectories:表示是否检索子文件夹 regExp:匹配文件的正则表达式,一般是文件名
// 此处require.context()方法生成了一个类require方法,这个方法接收一个参数req,根据这个参数我们可以得到相应的模块信息。 比如传入svg目录下的404.svg文件名,req('./404.svg'),就会返回相对应的模块。
// 且这个类require方法有keys()方法,可以遍历获得目录下所有的子文件名。
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)
7. 在mian.js中引入icons下的index.js
import '@/icons' // icon
8. 配置 vue.config.js(主要为打包进行设置) =>插件固定写法 可以直接cv
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()
  }
}
最后我们在组件中使用下吧~
<template>
   <div >
      <svg-icon icon-class="lock" class-name="sy-svg" class="icon"/>
       <svg-icon icon-class="haha" class="icon" />
  </div>
</template>

<style scoped >
.icon {
  font-size: 50px;
}
</style>

我们看一下效果~ 手动撒花★,°:.☆( ̄▽ ̄)/$:.°★

4.png

vue3项目 - vite-plugin-svg-icons

实现:根据 icons 文件svg图片打包到项目中,通过组件使用图标

1. 安装vue3项目
pnpm create vue vue3-svgicon
2. 安装插件
yarn add vite-plugin-svg-icons -D
# or
npm i vite-plugin-svg-icons -D
# or
pnpm install vite-plugin-svg-icons -D
3. 使用插件
// vite.config.ts
import { VantResolver } from 'unplugin-vue-components/resolvers'
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
+import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    Components({
      dts: false,
      resolvers: [VantResolver({ importStyle: false })]
    }),
+    createSvgIconsPlugin({
+      // 指定图标文件夹,绝对路径(NODE代码)
+      iconDirs: [path.resolve(process.cwd(), 'src/icons')]
+    })
  ],
4. 导入插件
// mian.ts
import router from './router'
+import 'virtual:svg-icons-register'
import 'vant/lib/index.css'
5. 封装全局组件
<script setup lang="ts">
// 接收name
defineProps<{
  name: string
}>()
</script>

<template>
  <svg aria-hidden="true" class="cp-icon">
    <!-- #icon-文件夹名称-图片名称 -->
    <use :href="`#icon-${name}`" />
  </svg>
</template>

<style lang="scss" scoped>
.cp-icon {
  // em 是一个相对单位,相对的是自身的fint-size大小,如果自身没有,参考父元素
  width: 1em;
  height: 1em;
}
</style>

6. 给组件设置类型 types/components.d.ts
import SvgIcon from '@/components/CpIcon.vue'
declare module 'vue' {
  interface GlobalComponents {
    SvgIcon: typeof SvgIcon
  }
}
7. 使用
<template>
  <cp-icon name="login-eye-off" class="icon"></cp-icon>
  <cp-icon name="home-haha" class="icon"></cp-icon>
</template>

<style lang="scss" scoped>
.icon {
  font-size: 33px;
}
</style>

效果展示~~~

5.png

完结~