前言
小伙伴们好,我是代码诗人_
,明天带大家用如何用vue3 + vite3封装一个强壮
的svg插件!
首先得晓得为什么要应用svg
- 概念:SVG是一种基于XML的
矢量图形格局
,SVG 与诸如 DOM 和 XSL 之类的 W3C 规范是一个整体,用于在Web和其余环境中显示各种图形;它容许咱们编写可缩放的二维图形,并可通过CSS或JavaScript进行操作。它由万维网联盟制订,是一个凋谢规范。 - 长处:
可伸缩性,响应性,交互性,可编程性,体积小性能高
。
因为SVG是基于矢量的,所有在放大图形时不会呈现任何升高或失落保真度的状况。它只是从新绘制以适应更大的尺寸,这使得它非常适合多语境场景,例如响应式Web设计。而且性能也极佳,与栅格图形(如GIF,JPG和PNG)相比,SVG图形通常是较小的文件。 - 浏览器反对度:svg对市面上常见的浏览器曾经有很好的反对度
**所以,综上所述,日常开发中应用svg来代替图片及图标是咱们的不二抉择!**
# 为什么要封装
如果对svg不封装,代码必定会难以保护,浏览性极差,比方:
再看下封装后的代码成果:
一个自定义标签组件即可搞定,也不便保护和浏览
<svg-icon iconName="q" />
上面咱们就开始编写插件及组件,来实现它,分5步走
- 第一步: 建设所需文件
- 第二步:封装转换并读取svg文件的插件
- 第三步:利用vite transformIndexHtml封装渲染svg内容
- 第四步:封装vue组件并全局注册
第五步:页面中利用传值
第一步:建设所需文件
首先在src文件夹下建设如下文件:
目录文件解释
- SvgIcon/index.vue 封装
<svg-icon />
组件 - svg目录寄存你的svg格局的文件
- index.js 全局注册svg的钩子
- svgTagView.js svg插件
第二步:封装(转换并读取svg)文件的插件
1. 首先在svgTagView.js中引入node文件系统,并在vite中应用
import { readFileSync, readdirSync } from 'fs'
- readdirSync:同步 readdir().返回文件数组列表
- readFileSync:同步读取文件内容
还不晓得node fs文件系统的小伙伴,请点击 传送门
而后在vite.config.js
中的插件plugins
中应用它
import { svgTagView } from './src/icons/svgTagView.js' plugins: [ vue(), svgTagView() ]
2. 设置svg的正则匹配标签及属性
const attributeMatch = { svgTag: /<svg([^>+].*?)>/, //匹配多个<svg 未闭合的标签 clearWidthHeight: /(width|height)="([^>+].*?)"/g, // 全局匹配为svg的width或height属性 hasViewBox: /(viewBox="[^>+].*?")/g, // 全局匹配视区属性(viewBox) clearReturn: /(\r)|(\n)/g // 全局匹配回车和换行}let idPerfix = ''
还不晓得svg属性viewbox(svg视区,就是在svg上截取一小块,放大到整个svg显示
)的小伙伴,请点击 传送门
3. 递归的模式读取svg文件信息及文件列表
递归svg文件,那么当初就用到了readdirSync
和 readFileSync
;
(1. 首先咱们用readdirSync
获取文件列表信息
const svgRes = [] //let dir = './src/icons/svg/', //寄存svg文件的目录 const dirents = readdirSync(dir, { withFileTypes: true // 将文件作为fs.Dirent对象返回, }) console.log(dirents) // 文件列表信息
fs.Dirent
是目录项(能够是文件或目录中的子目录)的示意,通过读取 fs.Dir 返回。 目录项是文件名和文件类型对的组合。
dirent会返回icons/svg下的文件列表信息
启动我的项目,在终端中会看到文件列表信息,这是我的svg文件信息:
能够分明地看到 文件名称name和独立的文件 [Symbol(type)]
(2. 遍历文件列表信息,转换svg
遍历文件获取内容须要递归,所以咱们将第一步获取文件信息的代码放到一个函数中,
用readFileSync
读取文件内容,并用repalce办法配合第一大步的正则逐渐匹配转换svg文件,
最初失去一个含有<symbol
标签的字符串数组
实现:
function recursivelyFindSvg(dir) { const svgRes = [] const dirents = readdirSync(dir, { withFileTypes: true // 将文件作为fs.Dirent对象返回, }) for (const dirent of dirents) { if (dirent.isDirectory()) { // 判断是否一个目录 svgRes.push(...recursivelyFindSvg(dir + dirent.name + '/')) //递归获取svg文件内容 } else { const svg = readFileSync(dir + dirent.name) .toString() .replace(attributeMatch.clearReturn, '') .replace(attributeMatch.svgTag, ($1, $2) => { let width = 0 let height = 0 let content = $2.replace(attributeMatch.clearWidthHeight, (s1, s2, s3) => { if (s2 === 'width') { width = s3 } else if (s2 === 'height') { height = s3 } return '' }) if (!attributeMatch.hasViewBox.test($2)) { content += `viewBox="0 0 ${width} ${height}"` } return `<symbol id="${idPerfix}-${dirent.name.replace('.svg', '')}" ${content}>` }) .replace('</svg>', '</symbol>') svgRes.push(svg) } } console.log(svgRes) return svgRes}
运行此段代码,能够在终端看到如下信息:
第二大步就算实现了
第三步:利用vite transformIndexHtml封装渲染svg内容
下面第二步获取了svg的容,当初咱们要将这些内容渲染到视图上,
那么就用到了vite的transformIndexHtml
1. 解释下 transformIndexHtml
:
transformIndexHtml
是转换 index.html
的专用钩子。钩子接管以后的 HTML 字符串和转换上下文。上下文在开发期间裸露ViteDevServer
实例,在构建期间裸露 Rollup 输入的包。
这个钩子能够是异步的,并且能够返回以下其中之一:
- 通过转换的 HTML 字符串
- 注入到现有 HTML 中的标签描述符对象数组(
{ tag, attrs, children }
)。每个标签也能够指定它应该被注入到哪里(默认是在<head>
之前) 一个蕴含
{ html, tags }
的对象2.
transformIndexHtml
渲染svg
利用transformIndexHtml配合replace替换<body
,并将第二步的svg数组内容放到
svg规范定义的标签中
规范svg标签:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0"></svg>
实现:
const svgTagView = (path = './src/icons/svg/', perfix = 'icon') => { if (path === '') return idPerfix = perfix const res = recursivelyFindSvg(path) return { name: 'svg-transform', transformIndexHtml(html) { // transformIndexHtml 转换 index.html 的专用钩子,vite3 独享 return html.replace( '<body>', `<body> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0"> ${res.join('')} </svg> ` ) } }}
到这里插件就算封装完了,而后咱们须要将svg门路写入vite.config.js
中,便于全局配置
svgTagView('./src/icons/svg/')
附上残缺代码如下:
import { readFileSync, readdirSync } from 'fs'const attributeMatch = { svgTag: /<svg([^>+].*?)>/, clearWidthHeight: /(width|height)="([^>+].*?)"/g, hasViewBox: /(viewBox="[^>+].*?")/g, clearReturn: /(\r)|(\n)/g}let idPerfix = ''function recursivelyFindSvg(dir) { const svgRes = [] const dirents = readdirSync(dir, { withFileTypes: true // 将文件作为fs.Dirent对象返回, //fs.Dirent 是目录项(能够是文件或目录中的子目录)的示意,通过读取 fs.Dir 返回。 目录项是文件名和文件类型对的组合。 }) // console.log(dirents) // { name: 'guanyuwomeng1.svg', [Symbol(type)]: 1 } for (const dirent of dirents) { if (dirent.isDirectory()) { // 判断是否一个目录 svgRes.push(...recursivelyFindSvg(dir + dirent.name + '/')) //递归获取svg文件内容 } else { // 读取文件信息并用repalce办法,利用第一步的正则匹配转换svg文件 const svg = readFileSync(dir + dirent.name) .toString() .replace(attributeMatch.clearReturn, '') .replace(attributeMatch.svgTag, ($1, $2) => { let width = 0 let height = 0 let content = $2.replace(attributeMatch.clearWidthHeight, (s1, s2, s3) => { if (s2 === 'width') { width = s3 } else if (s2 === 'height') { height = s3 } return '' }) if (!attributeMatch.hasViewBox.test($2)) { content += `viewBox="0 0 ${width} ${height}"` } return `<symbol id="${idPerfix}-${dirent.name.replace('.svg', '')}" ${content}>` }) .replace('</svg>', '</symbol>') svgRes.push(svg) } } // console.log(" ~ file: svgTagView.js:28 ~ recursivelyFindSvg ~ svgRes", svgRes) return svgRes}export const svgTagView = (path, perfix = 'icon') => { if (path === '') return idPerfix = perfix const res = recursivelyFindSvg(path) return { name: 'svg-transform', transformIndexHtml(html) { // transformIndexHtml 转换 index.html 的专用钩子,vite3 独享 return html.replace( '<body>', ` <body> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0"> ${res.join('')} </svg> ` ) } }}
插件是封装完了,下一步咱们就得封装svg vue组件,来渲染证实它
第四步:封装vue组件并全局注册
关上咱们第一次新建的文件components/SvgIcon/index.vue
1. 封装一个全局通用的svg-icon
组件如下:
可自行配置svg标签可有的属性(width,class,name等等...),封装语法很简略,这里不再赘述
<template> <svg :class="svgClass" v-bind="$attrs" :style="{ width, height, color }"> <use :href="svgName"></use> </svg></template><script setup> import { computed } from 'vue' const props = defineProps({ iconName: { type: String, default: '' }, //对不同区域的 icon 款式调整,如字体大小 className: { type: String, default: '' }, width: { type: Number, default: 22 }, height: { type: Number, default: 22 }, color: { type: String, default: '#333' } }) const svgName = computed(() => `#icon-${props.iconName}`) const svgClass = computed(() => { if (props.name) { return `svg-icon icon-${props.iconName}` } return `svg-icon` })</script><style> .svg-icon { width: 22px; height: 22px; fill: currentColor; /*此属性为更改svg色彩属性设置*/ stroke: currentColor; overflow: hidden; vertical-align: text-top; }</style>
2. 全局注册上一步封装的svg-icon
关上第一次新建的icons/idnex.js
应用install
钩子在vue实例上注册它,如下:
import SvgIcon from '@/components/SvgIcon/index.vue'export default { install: (app) => { app.component('svg-icon', SvgIcon) }}
而后在入口文件main.js
use应用它
import { createApp } from 'vue'import App from './App.vue'import SvgIcon from './icons/index'const app = createApp(App)app.use(SvgIcon)app.mount('#app')
全局注册和use应用搞定,下一步咱们在模板中应用它(<svg-icon />
)
第五步:页面中利用<svg-icon />组件传值
<template><svg-icon iconName="ziliao2" /><svg-icon iconName="ziliao2" /><svg-icon iconName="ziliao2" /><template/>
页面展现成果如下:
到这里插件和组件及应用就实现啦!!
总结
- 第一步: 建设所需文件
- 第二步:封装转换并读取svg文件的插件
- 第三步:利用vite transformIndexHtml封装渲染svg内容
- 第四步:封装vue组件并全局注册
- 第五步:页面中利用传值
当然,封装的插件(svgTagView
)你也能够自行公布到npm上,而后装置并应用它,这样最不便你开发不过了。
点击查看更多文章