如何优雅的封装svg-icon并运用到项目中(webpack和vite)

如何优雅的封装svg-icon并运用到项目中(webpack和vite)

先看一下封装好的使用方式:

这里先讲vue-cli项目的相关配置,vite项目对应的插件和配置教程在下方,需要的小伙伴可以直接往下翻

一. webpack项目配置流程

1. 安装依赖

首先需要在我们的项目中安装svg-sprite-loader这个loader,svg-sprite-loader是一个Webpack loader,用于将多个SVG文件打包成一个雪碧图(sprite)。它可以帮助优化网页性能,减少HTTP请求的数量,同时提供了一种方便的方式来管理和使用SVG图标

优点:

  • 预加载 在项目运行时就生成所有图标,只需操作一次 dom
  • 高性能 内置缓存,仅当文件被修改时才会重新生成

接下来我们开始安装


(1).安装和配置 svg-sprite-loader:

 npm i svg-sprite-loader -D
 # 或
 pnpm install svg-sprite-loader -D 
 # 或
 yarn add svg-sprite-loader -D

(2).填写配置

如果是vue-cli搭建的项目,则需要在vue.config.js中添加:

const path = require('path')
// 项目的其他配置项,这里进行省略......
module.exports = defineConfig({
  // 项目的其他配置项,这里进行省略......
  chainWebpack: (config) => {
    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()
  }
})

原生webpack的项目,则需要在webpack.config.js中添加对应的loader配置:

注意:url-loader 中要将 icons 文件夹排除, 不让 url-loader 处理该文件夹

  // 其他配置项,这里进行省略......
module.exports = {
  // 其他配置项,这里进行省略......
  module: {
    // 其他配置项,这里进行省略......
    rules: [
      // 其他loader,这里进行省略......
      {
        test: /\.svg$/,
        loader: 'svg-sprite-loader',
        include: [resolve('src/icons')],
        options: {
          symbolId: 'icon-[name]'
        }
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        },
        exclude: [resolve('src/icons')]
      }
    ]
  }
}


2. 新建组件和目录

(1).封装svg-icon组件

components 中新建组件 SvgIcon.vue:

vue3:

<template>
  <div v-if="isExternalClass" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-bind="$attrs"></div>
  <svg v-else :class="svgClass" aria-hidden="true" v-bind="$attrs">
    <use :href="iconName" />
  </svg>
</template>

<script setup lang="ts">
import { computed } from 'vue'
interface Prop {
  iconClass: string
  className?: string
}
const props = defineProps<Prop>()
/** 用于判断组件传入的iconClass是不是路径或链接, 如果是路径或者链接则使用div加mask遮罩的方式展示 */
const isExternalIcon = (path: string): boolean => {
  return /^(\/)+([a-zA-Z0-9\s_\\.\-():/])+(.svg|.png|.jpg)$/g.test(path) || /^(https?:|mailto:|tel:)/.test(path)
}
const isExternalClass = computed(() => isExternalIcon(props.iconClass))
/** 拼接成use标签的href */
const iconName = computed(() => `#icon-${props.iconClass}`)
// svg标签的class
const svgClass = computed(() => {
  if (props.className) {
    return 'svg-icon ' + props.className
  } else {
    return 'svg-icon'
  }
})
// 传入路径或者链接时div的mask遮罩样式
const styleExternalIcon = computed(() => {
  return {
    mask: `url(${props.iconClass}) no-repeat 50% 50%`,
    '-webkit-mask': `url(${props.iconClass}) no-repeat 50% 50%`
  }
})
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  stroke: var(--background-primary);
  overflow: hidden;
  display: inline;
  position: relative;
}
.svg-external-icon {
  background-color: currentColor;
  mask-size: cover !important;
  display: inline-block;
}
</style>

如果是vue2项目,可以使用下面这个组件:

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

<script>
export default {
  name: 'svg-icon',
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String
    }
  },
  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>

(2).在src下icons文件夹下新建svg目录和index.js文件

目录结构:

src/icons
-    /svg      # svg目录保存图标
-    /index.ts # 注册全局组件

新建的index.ts
// 引入封装好的SvgIcon组件
import SvgIcon from '@/components/SvgIcon/index.vue' // svg component
import type { App } from 'vue'

// 全局注册
export const registerSvgIcon = (app: App): void => {
  app.component('svg-icon', SvgIcon)
  const req = require.context('./svg', false, /\.svg$/)
  // eslint-disable-next-line
  const requireAll = (requireContext: any) =>
    requireContext.keys().map(requireContext)
  requireAll(req)
}

然后在项目的main.ts中引入:

main.ts部分代码

// 其他导入,这里省略......
import { createApp } from 'vue'
import App from './App.vue'
import { registerSvgIcon } from '@/icons'
const app = createApp(App).use(router).use(i18n).use(pinia)
registerSvgIcon(app)
app.mount('#app')
	
/* vue2直接Vue.use(registerSvgIcon)即可 */

3. 最后我们就可以在项目中全局使用了

icon-class传入的就是svg目录下文件的文件名,比如目录之放一个有个goBack.svg文件,我们就可以按照下面的方式使用。

<svg-icon icon-class="goBack" />

也可以传入路径或者图片链接的方式,当然这种方式上面vue2对应的组件没有进行封装,感兴趣的小伙伴可以自己琢磨一下

<svg-icon icon-class="../images/logo.png" />

二. vite项目配置流程

1. 安装

yarn add vite-plugin-svg-icons -D
# 或
npm i vite-plugin-svg-icons -D
# 或
pnpm install vite-plugin-svg-icons -D

2. 配置插件

  • vite.config.ts 中的配置插件
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'

export default () => {
  return {
    plugins: [
      createSvgIconsPlugin({
        // 指定需要缓存的图标文件夹
        iconDirs: [path.resolve(process.cwd(), 'src/icons')],
        // 指定symbolId格式
        symbolId: 'icon-[dir]-[name]',
        /**
         * 自定义插入位置
         * @default: body-last
         */
        inject?: 'body-last' | 'body-first'

        /**
         * custom dom id
         * @default: __svg__icons__dom__
         */
        customDomId: '__svg__icons__dom__',
      }),
    ],
  }
}
  • 在 src/main.ts 内引入注册脚本
import 'virtual:svg-icons-register'
  • 在src目录下新建icons文件夹用来存放svg图表

注意:不同与上面webpack的插件配置方式,这里不需要我们手动创建导入的js文件,在上面配置项的iconDirs里指定目录即可,比如上面配置了'src/icons'这个目录,只需要把svg图标全部存放到这个目录即可

3. 封装vue组件

创建svg-icon.vue组件:
<template>
  <svg aria-hidden="true" :style="{ width, height }">
    <use :xlink:href="symbolId" :fill="color" />
  </svg>
</template>

<script setup lang="ts">
import { computed } from 'vue'
interface Props {
  name: string
  prefix?: string
  color?: string
  width?: string
  height?: string
}
const props = withDefaults(defineProps<Props>(), {
  prefix: 'icon',
  color: '#333',
  width: '16px',
  height: '16px'
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
</script>

</script>

和上面wabpack不同的是,目录可以更深层级的嵌套,比如icon目录下有个dir子目录,子目录之下放demo.svg文件,组件内就可以直接使用目录名-文件名的方式,比如:name="dir-demo"

icons 目录结构:

# src/icons

- icon1.svg
- icon2.svg
- icon3.svg
- dir/icon1.svg

其他组件中使用:

<template>
  <div>
    <SvgIcon name="icon1"></SvgIcon>
    <SvgIcon name="icon2"></SvgIcon>
    <SvgIcon name="icon3"></SvgIcon>
    <SvgIcon name="dir-icon1"></SvgIcon>
  </div>
</template>

<script setup lang="ts">
import SvgIcon from './components/SvgIcon.vue'
</script>

也可以注册为全局组件,不用每次都导入:
在components目录下新建一个index.ts 文件

// 引入项目中的全部全局组件
import SvgIcon from './svgIcon/index.vue'
//其他组件,这里省略...

// 组装成一个对象
const allGlobalComponents: any = { 
    SvgIcon,
    //其他组件,这里省略...
}

// 对外暴露插件对象,在main.ts中引入后,直接自动调用install方法
export default {
  install(app: any) {
    // 循环注册所有的全局组件
    Object.keys(allGlobalComponents).forEach((componentName) => {
      app.component(componentName, allGlobalComponents[componentName])
    })
  },
}

main.ts中导入并use:

import { globalComponents } from '@/components'
reateApp(App).use(globalComponents).mount('#app')

4. 封装React组件

(1). /src/components目录下新建SvgIcon.jsx组件:
export default function SvgIcon({
    name,
    prefix = 'icon',
    color = '#333',
    width = '16px',
    height = '16px',
    ...props
  }) {
    const symbolId = `#${prefix}-${name}`
  
    return (
      <svg { ...props } aria-hidden="true" style={{ width, height }}>
        <use href={ symbolId } fill={ color } />
      </svg>
    )
  }

5. 获取所有 SymbolId

import ids from 'virtual:svg-icons-names'
// => ['icon-icon1','icon-icon2','icon-icon3']

6. 配置说明

参数类型默认值说明
iconDirsstring[]-需要生成雪碧图的图标文件夹
symbolIdstringicon-[dir]-[name]svg 的 symbolId 格式,见下方说明
svgoOptionsboolean|SvgoOptionstruesvg 压缩配置,可以是对象Options
injectstringbody-lastsvgDom 默认插入的位置,可选body-first
customDomIdstring__svg__icons__dom__svgDom 插入节点的 ID
  • 33
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值