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

IconSvg.vue文件:

&lt;template&gt; &lt;svg aria-hidden=&quot;true&quot;&gt; &lt;use :href=&quot;getName&quot;&gt;&lt;/use&gt; &lt;/svg&gt; &lt;/template&gt; &lt;script lang=&quot;ts&quot;&gt; import {defineComponent} from &quot;vue&quot;; const svgParser = new DOMParser(); export default defineComponent({ name: &quot;IconSvg&quot;, props: { name: { type: String, default: &apos;&apos; } }, data (){ return { getName: &apos;&apos; } }, watch: { // 监听 name 变化 &apos;$props.name&apos;: { // 首次执行 immediate: true, async handler (){ // 拼接svg文件名 const getId = `icon-${this.name}` const name = `#${getId}` // 动态加载 const res = await import(`../svg/${this.name}.svg`); // 雪碧图的DOM容器 let container = document.querySelector(&apos;#_SVG_SPRITE_CONTAINER_&apos;); if (!container || !container.querySelector(name)) { if (!container) { // 如果还未创建容器,就创建一个。(此处也可以直接写在index.html里) container = document.createElement(&apos;div&apos;); container.id = &apos;_SVG_SPRITE_CONTAINER_&apos; container.setAttribute(&apos;xmlns&apos;, &apos;http://www.w3.org/2000/svg&apos;) container.setAttribute(&apos;style&apos;, &apos;position: absolute; width: 0; height: 0;overflow: hidden&apos;) document.body.insertBefore(container, document.body.children[0]); } if (!container.querySelector(name)) { // 如果容器内没有该svg,则解析并制作该svg的雪碧图 const svgElement = svgParser.parseFromString(res.default, &quot;image/svg+xml&quot;).querySelector(&apos;svg&apos;); if (svgElement) { //删除影响样式的属性 for (const key of [&apos;width&apos;, &apos;height&apos;, &apos;x&apos;, &apos;y&apos;]) { svgElement.removeAttribute(key) } svgElement.id = getId // 插入到容器里 container.appendChild(svgElement as SVGSVGElement) } } } this.getName = name; } } }, }) &lt;/script&gt;

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

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

这样使用:

&lt;!-- 对应home.svg --&gt; &lt;svg-icon name=&quot;home&quot;/&gt;

小结

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

标签:vue
更新于: 2023-03-23 16:25:32 0