SVG 的使用及在 VUE 项目中的性能优化

711 阅读11分钟

技术博客__2024-10-09+09_33_12.jpg

我们日常编写项目过程中,或多或少都会使用背景图、图标等图片元素,但就图片而言有 JPGPNGSVG 等多种格式,本文将介绍 SVG 这种图片格式在使用过程中的一些小问题,以及在 VUE 项目中使用 SVG 如何进行性能优化。本文对新手友好哦,感兴趣的不妨来看一看啦~

SVG优势和适用场景

何为SVG

SVG(Scalable Vector Graphics)是一种基于XML的二维矢量图形格式,由W3C(World Wide Web Consortium)制定。与基于像素的位图图像格式(如JPEG和PNG)不同,它本质上是一个文本文件,SVG 图像由数学描述的形状、路径、文本和滤镜效果组成。

以下是一个 SVG 文件样例:

<svg width="64" height="41" viewBox="0 0 64 41"  xmlns="http://www.w3.org/2000/svg">
  <g transform="translate(0 1)" fill="none" fill-rule="evenodd">
    <ellipse fill="#F5F5F5" cx="32" cy="33" rx="32" ry="7"/>
    <g fill-rule="nonzero" stroke="#D9D9D9">
      <path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"/>
      <path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" fill="#FAFAFA"/>
    </g>
  </g>
</svg>

简单来说,SVG 是一个文本文件,文件中使用形状、路径、文本等定义描述了 SVG 图片展示为何种效果,看上去更像是 Canvas 画图。如上述文件中 ellipse 定义一个椭圆、path 等定义路径,fillstroke 等定义填色方式。矢量图的形式意味着它们可以无限放大而不失真,并且文件大小通常比位图图像小得多。

SVG 图片特点

不失真

SVG 是矢量图,是由文本和几何图形组成,可任意放大缩小,不会糊。且它在任何分辨率的设备上都能保持清晰,适合响应式网页设计和打印所用。

可编辑

SVG 本质上是一个文本文件,可在编辑器中进行编辑,也可以使用矢量图形编辑器对它进行更复杂的操作。若你了解过 SVG规范 可以像在 Canvas 像画图那样,通过代码直接画一个 SVG 图形出来。

也因 SVG 是文本文件,所以它的体积相较于其它格式的图片会小很多。

可交互

SVG 允许添加交互式元素,如链接、动画和事件处理器,使得 SVG 图像可以和用户进行交互,创建更丰富的用户体验。

支持动画

可以通过 CSS 动画或 JavaScript 脚本给 SVG 图像创建动态效果,如过渡、旋转、缩放等。

可嵌入

SVG 可以作为页面元素直接嵌入到 HTML 代码中,也可以作为外部文件链接进来使用。这使得 SVG 可以与 CSSJavaScript 无缝集成。

SVG 的推荐使用场景

在很多使用图像的场景,使用 PNGSVG 或其它格式的图片都可以满足,但是有一些场景,使用 SVG 绝对可以达到事半功倍的效果。接下来就跟我一起来看一下吧:

图标变色

鼠标hover时文字和对应的图标变色/主题色切换时图标对应变色,相信这样的场景作为前端同学们应该经常能看到。

这个场景使用 PNG 可以做吗,当然也可以,如下图,准备一个常态图标,再准备一个主题色或 hover 状态颜色的图标,当 hover 或切换主题色时,设置对应颜色的图标。

image.png

但这样做的问题是,当主题色很多时,一个图标就要有很多张图片来对应不同的主题色,或者当你的图标量已经很大又需要再增加主题色时,需要额外增加的图片也会很多,而它不过仅仅是变了一个颜色。

而用 SVG 来做会怎样呢,你只需要对它设置颜色即可,是的你没看错,是像文字一样,对它设置 color: <主题色> 即可。

自适应布局

另一个推荐的场景是自适应布局,请注意,这里所提到的自适应布局,不仅仅是同为移动设备之间、平板设备、PC设备之间的自适应,更多的是指 移动设备 -> 平板 -> PC -> 大屏 之间的自适应,当你的网页程序需要去适应不同大小的显示设备时,你的图像就会免不了要进行缩放。

JPGPNG 这种格式的图片,在放大时,若你原图分辨率不够,会出现图片糊了的现象,即图片失真。但若是你直接放分辨率很大的图进来,又难免因为图片太大,造成显示慢、网络加载慢、程序体积大等性能问题。

SVG 特点第一条就展示了,这种矢量图,任意放大不失真,天然适用自适应布局。

大量图标

当你的应用程序达到了一定的规模,你的图标使用量也随之增多,这时你就会发现,图片在网络请求中占比很多,若是你的图标文件为了适应不同场景而分辨率高、体积大,那将会明显拖慢你的程序加载时间。

SVG 这种图片本质上是文本文件,体积小。图标量越大,它的使用优势越明显。

SVG 使用问题

上面把 SVG 说的很好,体积小、又不失真、能通过 CSSJavaScript 自如的控制它的样式、动画等,那么我们直接使用这一种岂不是就满足需求了?别急朋友,我们往下看,SVG 自然也有它不适用的场景,在使用过程中也是需要注意一些问题滴~

浏览器兼容性

SVG 的浏览器兼容性问题,可以说的上是见人下菜碟了。SVG 作为一个较为新的技术,有些浏览器的老版本是不支持或者支持不完整的,如果你的程序目标用户在使用老旧的浏览器,那么这个问题将是致命的。

不适用复杂图像

SVG 作为矢量图,它使用简单的几何图形、路径、颜色等来表现图像,注定了它只能表达一些简单的图像,如:图标、插画等。且 SVG 在处理大量复杂图形时可能比较慢、渲染性能较低。

不能做背景图片使用

SVGCSS 中不能直接作背景图片使用,CSS 背景图片(background-image)支持位图格式(JPG、PNG、GIF等),这意味着如果你想使用 background-size/repeat 等背景属性去控制 SVG 作为背景图的平铺或其他兼容性效果,那它将令你失望了。

SVG 变色失败

上面我们讲了,可以通过给 SVG 设置 color: <颜色值> 的方式,任意控制你的图标颜色,但有时这种方式会失败,原因就是 SVG 本身 strokefill 属性的存在,自定义了图标内框线和色块的颜色,外部设置的颜色就失效了。

想要修复也简单,用编辑器打开 SVG 文件,找出其中的 strokefill 属性删除即可。

当然这针对的是单色图标,对多色图标来说,进行这一步会将原本多色去除,呈现为外部设置的单色。

图标风格不一致

除了变色失败的问题,更常见的问题为 明明我设置的图标大小都一样,但是展示出来的却不一样大,别着急,看看下面这个问题是否存在呢。

image.png

如上图,左右两个同为128 * 128大小的图标,但是左图是从 (32,32)位置起画,右图从(16,16)位置起画,也就是说相同大小的图,两个图实际图像外边距大小不同,内部图像大小不同,这也就是为什么,我们看着好像一样的图,放一起对比的时候就不一样大了。

除了大小不同,线框粗细程度也很让人头疼,尤其是当你没有一个专门的设计师时,需要到图标库自己去找合适的图标,选择线框粗细程度一样的图标就很重要了。如下图,你应该能一眼就看出来哪一个是不合群的:

image.png

影响图标一致性的还有绘图风格和是否同为线形图标/面性图标

image.png

上图第一组图标第一个为面性图标,其他为线性,第一个明显不合适。第二组第二个直角位置更圆润和其他搭配也不是很合适。

无论你是自己找图标还是跟设计师诉说你的需求,都可以从这几方面来着手:

  • 同为线性图或面性图
  • 线框粗细一致
  • 图像距离外边距一致
  • 风格一致

VUE 中如何批量管理 SVG

之前我们讲 SVG 可嵌入时说过,SVG 可以作为页面元素直接嵌入到 HTML 中使用,但是实际使用过程中我们通常不会那么用,将其作为外部文件引入使用将会是一个比较好的选择。

利用require.context()自动化导入图片

require.context() 是 Webpack 提供的一个功能,用于在编译时创建一个上下文 context,允许你动态加载模块。这在处理大型项目时十分高效,无需再手动列出所有需要导入的文件。

require.context(directory, useSubdirectories = false, regExp = /^\.\/.*$/, mode = 'sync')
  • directory: 需要检索的目录
  • useSubdirectories: 是否检索子目录
  • regExp: 匹配文件的正则表达式
  • 加载模式: 可以是 'sync'(同步),'lazy'(懒加载)或 'eager'(急加载)

真实使用:

src/components/SvgIcon/index.vue 定义SvgIcon组件:

<template>
  <i :class="svgClass">
    <svg width="1em" height="1em" aria-hidden="true">
      <use :xlink:href="iconName" />
    </svg>
  </i>
</template>

<script>
// 批量导入 svg
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('../Icon/assets', false, /\.svg$/)
requireAll(req)

export default {
  name: 'SvgIcon',
  props: {
    type: {
      type: String,
      required: true,
    },
    className: {
      type: String,
    },
  },
  computed: {
    iconName () {
      return `#icon-${this.type}`
    },
    svgClass () {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'
      }
    },
  },
}
</script>

<style lang="less" scoped>
.svg-icon {
  font-size: 20px;
}
</style>

添加所需包:

npm install --save svg-sprite-loader

Webpack 配置中增加:

chainWebpack: (config) => {
    // 将src/components/SvgIcon下svg文件引入,使用svg-sprite-loader打包成一个雪碧图
    const svgRule = config.module.rule('svg')
    svgRule.uses.clear()
    svgRule.include.add(resolve('./src/components/SvgIcon'))
    svgRule
      .test(/\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]', // 图标的名称格式化为 icon-<svg文件名>
        extract: true,
        spriteFilename: 'sprite.svg', // 生成的 SVG 雪碧图资源路径
      })
      .end()
    
    // 将src/components/SvgIcon下除svg文件以image形式引入
    const imagesRule = config.module.rule('images')
    imagesRule.exclude.add(resolve('./src/components/SvgIcon'))
    config.module.rule('images').test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
}

src/components/index.js 注册全局组件:

下面定义的是 将 src/components 文件夹下组件都定义为全局组件

import Vue from 'vue'

const requireComponent = require.context('.', true, /.\/(\w*\/)+index\.(jsx?|vue)$/)

const keys = requireComponent.keys().filter(item => {
  const arr = item.split('/')
  return /index\.(jsx?|vue)$/.test(arr[2])
})

keys.forEach(fileName => {
  // 获取组件配置
  const componentConfig = requireComponent(fileName)

  const componentName = componentConfig.default.name

  // 全局注册组件
  Vue.component(
    componentName,
    // 如果这个组件选项是通过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根。
    componentConfig.default || componentConfig,
  )
})

引入全局组件至 main.js

import './components'

组件都定义好了,接下来就可以使用了:

<svg-icon type="star" />

你可以给它添加 class,在 css 中控制它的样式和行为,通过 fontSize 控制其大小

至此,SVG 到自定义组件的过程就完成了,接下来只需要往 src/components/SvgIcon 中增加 SVG 文件,就可以使用对应的 Icon 了。

SVG 在 VUE 中如何做性能优化

任何资源在我们项目中,你使用的足够大、足够多,那对性能的影响都将是灾难级别的。接下来我们看看,SVG 的使用有哪些优化的点呢?

从请求数量优化

一个 SVG 的引入,在网络请求中就是一次请求,十次百次的引入,就会多十次百次的请求,想想你的项目中使用了多少个呢。

减少网络请求,需要将众多的 SVG 合而为一,变成一张 雪碧图,上面 svg-sprite-loader 的使用,已经实现了这一需求。下面看效果:

引入的 SVG 被整理成了一张 sprite.svg 图,只需一次请求即可

image.png

sprite.svg 内容:

我在实际项目中 图标的名称格式化使用的是 oc-<svg文件名>,所以这里 symbol idoc-开头,这个无所谓,你怎么定义怎么用即可。 image.png

从请求大小优化

SVG 作为文本文件,优势之一就是比位图格式的图片文件体积小,那它的体积大小就没有进一步可优化的空间了吗?当然不是,请看:

svgo-loader 是一个基于 svgo 的 Webpack插件,旨在优化 SVG 文件,减少其体积。从而提高前端应用的加载速度和性能。通过利用 svgo 的强大功能,能够自动对导入的 SVG 进行清理和压缩,无需手动干预,非常适合依赖大量 SVG 的项目来进行性能优化。

添加所需包:

npm install --save svgo svgo-loader

Webpack 配置增加:

chainWebpack: (config) => {
    // 将src/components/SvgIcon下svg文件引入,使用svg-sprite-loader打包成一个雪碧图
    const svgRule = config.module.rule('svg')
    svgRule.uses.clear()
    svgRule.include.add(resolve('./src/components/SvgIcon'))
    svgRule
      .test(/\.svg$/)
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]', // 图标的名称格式化为 icon-<svg文件名>
        extract: true,
        spriteFilename: 'sprite.svg', // 生成的 SVG 雪碧图资源路径
      })
      .end()
      .use('svgo-loader')    // 新加内容
      .loader('svgo-loader') // 新加内容
      .options({             // 新加内容
        configFile: false,   // 新加内容
      })                     // 新加内容
      .end()                 // 新加内容
    
    // 将src/components/SvgIcon下除svg文件以image形式引入
    const imagesRule = config.module.rule('images')
    imagesRule.exclude.add(resolve('./src/components/SvgIcon'))
    config.module.rule('images').test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
}

实际情况下的思考

图标变色

之前提到,图标若存在 fill、storke 属性,你在外部使用 color 对它进行改色是不生效的,需要打开 SVG 去除相关的属性。

那其实 svgo-loader 提供了可以自动去除图标自带颜色的功能,需要在配置中加:

.tap(options=>({...options, plugins:[{name: 'removeAttrs', params: { attrs: 'fill'}}]})) //去掉fill属性

就可以自动将多色图标处理成可变色的图标了,若你同时存在多色图标和变色图标,还可以利用 svgo-loader 配置不同的路径,选择忽略某路径的图标保留原色。

图标的缺省值

什么是图标的缺省值呢,其实是指当我使用一个图标,但实际不存在时,为了防止图标展示为空,展示的一个默认图标

实际使用时,我们图标可能不是一个一个使用的,可能是根据某一个参数值自动识别的,当我们增加参数值时,若忘了增加相应的 SVG,就会出现空图标的情况,这时候就需要指定一个缺省的图标了,比如平台类的图标,可以指定一个跟平台相关的缺省图标,跟镜像有关的,可以指定一个与之相关的缺省图标。具体是有 SvgIcon 组件内自定义还是根据不同情况传入参数定义,这取决于你所使用的具体情况了。

图标之间的对比

当我们引入大量图标之后,不同图标之间大小、线框粗细、风格等细节,你通过观看一个个的 SVG 预览图是看不出来区别的,这时候就需要一个把所有图标放一起的页面,方便的对比你添加的图标风格是否合适,也方便查看是否已经存在合适使用的图标。

下面是我在实际使用过程中的图标预览,该页面配置了只运行在本地时可见,对前端开发者友好:

image.png

红框展示的是图标边界,方便查看图标的内部间距是否合适。

写在最后

上述文章是我在实际使用 SVG 过程中的一些问题和思考,如果对你有所帮助,不妨动动小手点点收藏点点赞,以便需要用时随时可以找到。如果发现什么问题,欢迎评论区留言,我会第一时间进行改正。

我是 香辣彩虹屁,致力分享,致力原创,感谢你的观看🙏。