vite+vue3下如何使用动态导入的svg-sprite

  几个月前我刚接触vue3时就研究过,一直搞忘了分享。

起因

  我的vue项目一般都使用动态导入的svg-sprite,意思是:既要是个雪碧图,用<use> 标签复用;而且页面的svg应该是动态按需加载的。我觉得这样性能比较好,在vue2中实现比较简单,但是vue3+vite下我并未找到合适的解决方案,只有雪碧图没有动态导入。于是乎我就想自己做一个。

思路

就两点

  • 首先要做一个vite plugin,作为一个loader加载.svg文件,读取svg文件的内容,类似raw-loader
  • 然后需要一个component,它去动态加载svg文件,并把svg文件的内容拼接到雪碧图里。

代码

vite.config.ts中这样写:

import {defineConfig, Plugin} from 'vite' import vue from '@vitejs/plugin-vue' import fs from "fs"; import {dataToEsm} from "rollup-pluginutils"; const rawSvgPlugin:Plugin = { name: 'raw-svg-file-loader', transform(svg: string, filepath: string) { // 判断后缀是否为svg if (filepath.slice(-4) !== '.svg') return null; const content = fs.readFileSync(filepath).toString() return { // 直接返回svg文件的原始内容 code: dataToEsm(content) } }, } export default defineConfig({ plugins: [vue(), rawSvgPlugin], })

IconSvg.vue文件:

<script setup lang="ts"> // index.html里写一个<div id="SVG_CONTAINER"></div> const SvgContainerId = "SVG_CONTAINER"; const props = defineProps({ name: { type: String, required: true } }); const icon = ref(""); onMounted(() => { watch( () => props.name, (name) => { const id = `icon-${name}`; const eId = "#" + id; const container = document.getElementById(SvgContainerId); // 存储已经加载过的svg const svgIconList: Set<string> = (window as any).svgIconList || ((window as any).svgIconList = new Set()); if (!svgIconList.has(id)) { svgIconList.add(id); // 动态引入 import(`../assets/svg/${name}.svg`).then((res) => { const svgElement = new DOMParser() .parseFromString(res.default, "image/svg+xml") .querySelector("svg"); if (svgElement) { // 去掉无用的属性 for (const key of ["width", "height", "x", "y"]) { svgElement.removeAttribute(key); } svgElement.id = id; container!.appendChild(svgElement); } }).catch(() => svgIconList.delete(id)); } icon.value = eId; }, { immediate: true } ); }); </script> <template> <svg class="--icon-svg common-svg" aria-hidden="true"> <use :href="icon" /> </svg> </template> <style lang="scss"> svg.--icon-svg { display: inline-flex; align-items: center; justify-content: center; } </style>

main.ts里只需要全局注册IconSvg组件就行了:

import { createApp } from 'vue' import App from './App.vue' import IconSvg from "./assets/svg/IconSvg.vue"; createApp(App).component('svg-icon', IconSvg).mount('#app')

这样使用:

<!-- 对应home.svg --> <svg-icon name="home"/>

小结

这样做问题是解决了,可以动态导入svg并生成雪碧图,但是方式有点不优雅,有点投机取巧的感觉sticker

标签:vue
2年前
0