几个月前我刚接触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文件: <template> <svg aria-hidden="true"> <use :href="getName"></use> </svg> </template> <script lang="ts"> import {defineComponent} from "vue"; const svgParser = new DOMParser(); export default defineComponent({ name: "IconSvg", props: { name: { type: String, default: '' } }, data (){ return { getName: '' } }, watch: { // 监听 name 变化 '$props.name': { // 首次执行 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('#_SVG_SPRITE_CONTAINER_'); if (!container || !container.querySelector(name)) { if (!container) { // 如果还未创建容器,就创建一个。(此处也可以直接写在index.html里) container = document.createElement('div'); container.id = '_SVG_SPRITE_CONTAINER_' container.setAttribute('xmlns', 'http://www.w3.org/2000/svg') container.setAttribute('style', 'position: absolute; width: 0; height: 0;overflow: hidden') document.body.insertBefore(container, document.body.children[0]); } if (!container.querySelector(name)) { // 如果容器内没有该svg,则解析并制作该svg的雪碧图 const svgElement = svgParser.parseFromString(res.default, "image/svg+xml").querySelector('svg'); if (svgElement) { //删除影响样式的属性 for (const key of ['width', 'height', 'x', 'y']) { svgElement.removeAttribute(key) } svgElement.id = getId // 插入到容器里 container.appendChild(svgElement as SVGSVGElement) } } } this.getName = name; } } }, }) </script>在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并生成雪碧图,但是方式有点不优雅,有点投机取巧的感觉
vite+vue3下如何使用动态导入的svg-sprite
几个月前我刚接触vue3时就研究过,一直搞忘了分享。
思路
就两点
.svg
文件,读取svg文件的内容,类似raw-loader
。代码
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
文件:<template> <svg aria-hidden="true"> <use :href="getName"></use> </svg> </template> <script lang="ts"> import {defineComponent} from "vue"; const svgParser = new DOMParser(); export default defineComponent({ name: "IconSvg", props: { name: { type: String, default: '' } }, data (){ return { getName: '' } }, watch: { // 监听 name 变化 '$props.name': { // 首次执行 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('#_SVG_SPRITE_CONTAINER_'); if (!container || !container.querySelector(name)) { if (!container) { // 如果还未创建容器,就创建一个。(此处也可以直接写在index.html里) container = document.createElement('div'); container.id = '_SVG_SPRITE_CONTAINER_' container.setAttribute('xmlns', 'http://www.w3.org/2000/svg') container.setAttribute('style', 'position: absolute; width: 0; height: 0;overflow: hidden') document.body.insertBefore(container, document.body.children[0]); } if (!container.querySelector(name)) { // 如果容器内没有该svg,则解析并制作该svg的雪碧图 const svgElement = svgParser.parseFromString(res.default, "image/svg+xml").querySelector('svg'); if (svgElement) { //删除影响样式的属性 for (const key of ['width', 'height', 'x', 'y']) { svgElement.removeAttribute(key) } svgElement.id = getId // 插入到容器里 container.appendChild(svgElement as SVGSVGElement) } } } this.getName = name; } } }, }) </script>
在
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并生成雪碧图,但是方式有点不优雅,有点投机取巧的感觉