乐趣区

关于前端:vite3-vue3如何封装健壮的SVG插件

前言

小伙伴们好,我是 代码诗人_,明天带大家用如何用 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 文件,那么当初就用到了 readdirSyncreadFileSync;

(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 上,而后装置并应用它,这样最不便你开发不过了。

点击查看更多文章

退出移动版