抛弃iconfont+symbol, svg在vite+vue的最佳使用方式

9,373 阅读6分钟

iconfont 的缺点

iconfont 共有三种使用方式,分别是 unicode 引用font-class 引用symbol 引用

其中 unicodefont-class 是基于字体的,最严重的缺点是被浏览器最小 12px 限制和不支持多色图标

而最推荐的 symbol 基于 svg 的 use 标签 ,原理是节点的深克隆

symbol 缺点也很简单:不能自动保持宽高比,内部元素样式不能被修改,比如修改内部的 path

svg {
  fill: #fff;
  & > g:nth-child(1) > path {
    /* 修改单条 path 的样式 */
    fill: #666;
  }
  & > g:nth-child(2) > path {
    fill: #aaa;
  }
}

另外 iconfont 对 svg 图标的管理也存在问题,比如

  • 多色图标样式擦除,svg 内的 filter 在上传的时候会被 iconfont 擦除
  • 每上传一次文件都要修改项目cdn链接,没有删除文件也没有修改文件名,只是新增文件都要修改 cdn 链接,没有向后兼容性
  • 内容审查/其他原因,目前(2022年6月26日) iconfont cdn 无法生成,显示功能不可用

本地 svg symbol 的缺点

本地 svg 不存在 iconfont cdn 的缺点,本地 svg 一般用 symbol, 但是 symbol 方式却还是有缺点,目前 symbol 是使用最频繁的方式,缺点也很明显:

  • 不能自动保持宽高比
  • 内部元素样式不能被修改

缺点1: 不能保持宽高比

我们知道 svg 有 viewBox 属性,比如 一个 svg 有 viewBox="0 0 200 100",即

<div>
  <svg viewBox="0 0 200 100"></svg>
<div>
<style>
svg{
  width: 300px;
  height: auto;
}
</style>

那么实际渲染下,svg 的 height 就被自动设置为 150px

但是 symbol 方式就不可行,使用示例如下

<svg>
  <symbol viewBox="0 0 100 100" id="icon-xxx">  </symbol>
</svg>

<svg class="xxx">
  <use xlink:href="#icon-xxx"></use> 
</svg>
<style>
.xxx{
  width: 50px;
  height: auto;
}
</style>

这里 height 并不会按照 viewBox 属性比例自动变成 50px

也就是说 我们想要 svg 像 img 一样自动改变宽高是不可行的

比如 掘金 文章页面 右侧的点赞图标 svg 就没有按照 viewBox 自动改变宽度

这个页面的点赞图标的属性是

<symbol id="icon-zan" viewBox="0 0 20 20" xxx />

使用的时候设置了 height: 100%;,但是 width 并不是和 height 相等

你可以打开控制台,选取点赞图标这个元素,可以看到确实不等

而大多数的关于使用 svg 的文章都没有提到这个问题和解决方案

解决方法也很简单,就是给额外当前使用的 svg 加上 viewBox="0 0 20 20" 即可

缺点2: 内部元素样式不能被修改

如果我们想自定义 svg 内部单条 path 的样式,symbol 方式 无法做到

<svg>
  <symbol viewBox="0 0 100 100" id="icon-xxx">
    <g>
      <path fill="#FFF"></path>
    </g>
  </symbol>
</svg>

<svg class="xxx">
  <use xlink:href="#icon-xxx"></use> 
</svg>
<style>
.xxx path{
  fill: red;
}
</style>

无法修改成功,symbol 方式内部样式不能修改,这个没有好的解决的方法,除非新增 svg

比如说某个 svg 图标,我们想要它被 hover 时,内部元素变一个色,外部元素变另一个色,symbol 方式 只能设置单色图标,对这种情况就黔驴技穷了

什么是 svg 的最佳使用方式

为什么不直接在 html 里使用 svg 呢,直接使用显然不行

svg 一般都是很长的文本,谁乐意在 vue_template/react_tsx 手动把 svg 复制进去呢

不仅复用性为零,而且某些 svg 内部还有 style 标签,而 vue_template 里是不能有 style 标签的

但是优点也很明显,如果使用 svg原生tag , 那么将不存在 symbol 方式 带来的上述两个缺点

谁在使用 svg原生tag

比如 github 的 svg 图标在渲染的时候就是 svg原生tag,并不是 symbol 方式

不信的话,你打开 github.com 后打开控制台,选取页面上的任意一个图标,就能看到它是 svg原生tag

另外 爱奇艺腾讯视频知乎bilibilitwitternpmjsfigmastackoverflow 也在使用

接下来就说明如何使用 vue 的组件来封装 svg原生tag

如何封装 svg原生tag 的 vue组件

在 vite 里面,我们需要一个插件 vite-svg-loader

vite-svg-loader 的原理也很简单

  • 在 代码里 import svg 的时候读取 svg
  • svgo 对 svg 预处理,比如处理 svg 内部的 style 标签等其他优化
  • 然后把这个 svg 转成 vue 组件

这样当你在 vue 里 import 这个 svg 时,就是在使用一个 vue 组件,使用示例如下

// ./vite.config.ts
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import svgLoader from 'vite-svg-loader';

export default defineConfig(({ command, mode }) => {
  return {
    plugins: [
      vue(),
      svgLoader(),
    ],
  };
});
<!-- ./App.vue -->
<script setup lang="ts">
import SvgRaw from './icon.svg?component'
</script>
<template>
  <SvgRaw/>
</template>

这样实际渲染出来,我们在使用 svg 的地方就是 svg原生tag,因此它和直接把 svg 的代码直接复制到 vue 组件里没有区别,它能自动适应宽高比,也能单独修改内部单个 path 的样式

但是这种方式不仅保留了原生 svg 的优点,而且大大提升了 svg 的复用性

进一步的优化

实际上,上述例子还有很多可优化的点

优化1: 按 name 使用 name.svg

我们知道 symbol 方式是这样使用的

<svg class="xxx">
  <use xlink:href="#icon-xxx"></use> 
</svg>

一般我们会对它封装 vue 全局组件,然后传入 name=icon-xxx,那么这个 svg原生tag 组件也可以这样封装

由于此时的 svg 变成了一个个组件,我们想根据 name 来动态使用组件,于是解决方法就是 vue 的动态使用组件

我们先将所有的 svg 文件放在 @/assets/svg/ 目录下,然后封装一个 SvgRaw.vue 的全局组件

<!-- ./SvgRaw.vue -->
<script setup lang="ts">
import type { Component } from 'vue';
import { computed } from 'vue';

const modules = import.meta.globEager('@/assets/svg/*.svg', {
  as: 'component',
});
const props = withDefaults(defineProps<{ name: string }>(), {});
const currentComponent = computed<Component>(() => {
  const fileName = '/' + props.name + '.svg';
  for (const path in modules) {
    const mod = modules[path];
    if (path.endsWith(fileName)) {
      return mod as Component;
    }
  }
  throw new Error('not found svg file:' + fileName);
});
</script>
<template>
  <component :is="currentComponent" />
</template>

全局注册该组件后,使用方式如下,我们不再需要对每个 svg 单独 import

<!-- ./App.vue -->
<template>
  <div>
    <SvgRaw name="xxx"/>
    <div> hello world </div>
  </div>
</template>

优化2: 组件样式的 scoped

我们知道当设置 style scoped 时,template 的每个节点会加上 data-v-hashstyle 的选择器也会加上 [data-v-hash],达到不同组件的样式隔离的效果

如果我们想在 SvgRaw 组件的 style 给 svg 加一些统一的样式,比如

svg {
  width: auto;
  height: auto;
}
* {
  // 颜色渐变效果
  transition: fill 250ms;
}

显然,为了不使这个样式污染全局样式,我们需要设置 style scoped

但是由于我们的 svg 的动态加载的独立组件,内部的节点并不会被加上 data-v-hash,也就无法被 css 选择器选中

因此,我们需要手动给 svg 组件加上 data-v-hash

<script setup lang="ts">
import type { Component } from 'vue';
import {
    computed,
    getCurrentInstance,
    nextTick,
    ref,
    useAttrs,
    watch,
} from 'vue';
const modules = import.meta.globEager('@/assets/svg/*.svg', {
    as: 'component',
});

const props = withDefaults(defineProps<{ name: string }>(), {});
const attrs = useAttrs();
const instance = getCurrentInstance();

const svgRef = ref();

const currentComponent = computed<Component | undefined>(() => {
    const fileName = '/' + props.name + '.svg';
    for (const path in modules) {
        const mod = modules[path];
        if (path.endsWith(fileName)) {
            return mod as Component;
        }
    }
    console.log('not found svg file:' + fileName);
    return undefined;
});

// data-v-hash
let scopeId = '';
if (instance?.type) {
    // __scopeId 存在的条件是 <style scoped>
    const __scopeId = (instance?.type as { __scopeId?: string })?.__scopeId;
    if (__scopeId) {
        scopeId = __scopeId;
    }
}

const attachAttr = async () => {
    await nextTick();
    // 取到 svg dom
    const cpt = svgRef.value;
    if (!cpt) return;
    const svg = cpt.$el as Element | undefined;
    if (!(svg instanceof Element)) return;

    // 由于svg不在vue_template里,所以初始没有添加样式隔离,需要手动给svg和所有子dom添加 data-v-hash
    if (scopeId) {
        svg.setAttribute(scopeId, '');
        svg.querySelectorAll('*').forEach((element) => {
            element.setAttribute(scopeId, '');
        });
    }
};

watch(
    () => props.name,
    async () => {
        await nextTick();
        attachAttr();
    },
    {
        immediate: true,
    },
);
</script>
<template>
    <component
        :is="currentComponent"
        v-if="currentComponent"
        ref="svgRef"
        v-bind="$attrs"
        class="svg-raw"
        :name="name"
    />
</template>

<style scoped>
svg,
path {
    transition: fill 250ms;
}
</style>

优化3: 避免不同 svg 内部 filter id 冲突

某些 svg 效果比较复杂,内部一般会有 filter 节点,比如

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="283" height="81" viewBox="0 0 283 81" fill="white" xmlns="http://www.w3.org/2000/svg">
  <g filter="url(#filter0_d_359_2426)">
    <path
      d="M2.856 18.092V13.722H5.57L2.534 10.594C5.202 8.32467 6.93466 5.81 7.732 3.05L13.252 3.234L11.964 6.224H24.43V10.41H18.266C18.266 11.3913 18.2353 12.4953 18.174 13.722H25.442V18.092H17.898C20.4127 19.4413 22.8813 20.8367 25.304 22.278L22.82 25.866H41.266V46.566H35.378V44.772H13.16V46.566H7.272V27.246L5.892 27.89C4.972 26.602 3.85266 25.0993 2.534 23.382C7.19533 21.6953 10.124 19.932 11.32 18.092H2.856ZM26.96 23.428V5.994H43.98V23.428H26.96ZM13.16 37.228V40.264H35.378V37.228H13.16ZM13.16 30.374V33.364H35.378V30.374H13.16ZM32.25 10.686V18.69H38.69V10.686H32.25ZM21.21 25.866C19.7993 24.8233 17.7293 23.382 15 21.542C13.9267 23.0753 12.2093 24.5167 9.848 25.866H21.21ZM9.434 10.41C8.75933 11.3607 7.79333 12.4647 6.536 13.722H12.654C12.746 12.7713 12.792 11.6673 12.792 10.41H9.434ZM82.114 18.276L80.274 13.538C81.4087 12.802 82.436 11.9127 83.356 10.87C85.4413 8.662 87.4807 5.96333 89.474 2.774L95.408 4.476C92.5253 7.81866 89.9493 10.6707 87.68 13.032L96.098 12.756L93.384 9.076L97.386 6.638C100.269 10.134 102.768 13.262 104.884 16.022V3.51H110.542V9.306C113.578 8.04867 116.798 6.5 120.202 4.66L123.376 8.8C118.899 10.9467 114.621 12.802 110.542 14.366V16.39C110.542 17.218 110.726 17.8007 111.094 18.138C111.462 18.4753 112.06 18.644 112.888 18.644H115.694C117.135 18.644 117.963 18 118.178 16.712C118.423 15.608 118.638 14.1667 118.822 12.388C119.987 12.7867 121.659 13.3233 123.836 13.998C123.591 16.0527 123.269 17.8467 122.87 19.38C122.226 22.2013 120.233 23.612 116.89 23.612H110.864C108.84 23.612 107.353 23.1367 106.402 22.186C105.39 21.174 104.884 19.5947 104.884 17.448V16.528L100.698 19.242L99.226 17.126C96.282 17.218 92.004 17.4173 86.392 17.724C84.9813 17.816 83.5553 18 82.114 18.276ZM82.482 46.198V20.116H101.894V40.632C101.894 42.3493 101.541 43.622 100.836 44.45C100.1 45.278 98.9193 45.7533 97.294 45.876C95.3927 45.9987 93.5833 46.06 91.866 46.06C91.59 44.4653 91.268 42.8707 90.9 41.276C91.6973 41.3373 92.9547 41.368 94.672 41.368C96.0213 41.368 96.696 40.7853 96.696 39.62V38.102H87.68V46.198H82.482ZM110.91 45.738C108.917 45.738 107.429 45.2473 106.448 44.266C105.436 43.254 104.93 41.6747 104.93 39.528V25.268H110.588V30.236C113.992 28.8253 117.258 27.2613 120.386 25.544L123.514 29.914C119.006 32.0607 114.697 33.962 110.588 35.618V38.424C110.557 39.988 111.339 40.7547 112.934 40.724H115.786C117.258 40.724 118.101 40.0647 118.316 38.746C118.531 37.3353 118.699 35.7713 118.822 34.054C120.969 34.79 122.655 35.3267 123.882 35.664C123.637 37.9947 123.33 39.9573 122.962 41.552C122.41 44.3427 120.417 45.738 116.982 45.738H110.91ZM87.68 24.486V27.292H96.696V24.486H87.68ZM87.68 31.294V34.054H96.696V31.294H87.68ZM158.796 23.244V18.184H169.836V14.182H164.96C164.592 15.3473 164.178 16.4973 163.718 17.632C161.571 16.896 159.961 16.3593 158.888 16.022C160.421 12.3727 161.571 8.63133 162.338 4.798L167.306 5.534C166.999 6.97533 166.708 8.18667 166.432 9.168H169.836V3.326H175.31V9.168H183.728V14.182H175.31V18.184H185.2V23.244H175.31V27.108H184.418V38.332C184.541 41.4907 183.314 43.1927 180.738 43.438C179.818 43.53 178.515 43.576 176.828 43.576C176.429 41.6747 176.061 40.0953 175.724 38.838C176.276 38.8993 176.981 38.93 177.84 38.93C178.392 38.93 178.76 38.8227 178.944 38.608C179.159 38.3627 179.266 37.9487 179.266 37.366V31.938H175.31V46.612H169.836V31.938H166.11V43.714H160.866V27.108H169.836V23.244H158.796ZM188.604 46.014C188.267 43.99 187.914 42.104 187.546 40.356C188.957 40.3867 190.659 40.402 192.652 40.402C193.633 40.402 194.308 40.2027 194.676 39.804C195.044 39.4053 195.228 38.7 195.228 37.688V3.326H200.61V38.884C200.61 41.3373 200.211 43.07 199.414 44.082C198.586 45.1553 197.099 45.7533 194.952 45.876L188.604 46.014ZM186.902 35.25V7.926H192.008V35.25H186.902ZM253.05 25.406L249.094 20.76C252.651 15.9453 255.427 10.0573 257.42 3.096L263.538 4.246C262.863 6.27 262.219 8.04867 261.606 9.582H280.604V15.194H266.436V20.944H278.074V26.418H266.436V32.122H279.408V37.596H266.436V46.382H260.41V15.194H259.168C257.389 18.9353 255.35 22.3393 253.05 25.406ZM238.422 30.604L236.766 23.29C240.507 17.5553 243.451 10.824 245.598 3.096L251.578 4.614C250.627 7.742 249.554 10.686 248.358 13.446V46.336H242.516V24.67C241.105 26.9393 239.741 28.9173 238.422 30.604Z" />
  </g>
  <g filter="url(#filter1_d_359_2426)">
    <path
      d="M6.35399 76.8281H4.16258V69.6953H6.35399V76.8281ZM20.6609 76.8281H18.4695L14.7312 72.3594C14.7025 72.3307 14.6596 72.2734 14.6023 72.1875C14.4304 71.9583 14.3015 71.7865 14.2156 71.6719C14.2442 71.8724 14.2585 72.2734 14.2585 72.875V76.8281H12.239V69.6953H14.5593L18.1687 74.0352C18.4551 74.4076 18.627 74.6367 18.6843 74.7227C18.6557 74.4935 18.6413 74.1354 18.6413 73.6484V69.6953H20.6609V76.8281ZM34.624 70.9844H31.5732V76.8281H29.3818V70.9844H26.2881V69.6953H34.624V70.9844ZM47.6848 76.8281H40.2512V69.6953H47.5559V70.9844H42.3996V72.5312H47.2121V73.8633H42.3996V75.5391H47.6848V76.8281ZM60.2729 76.8281H53.269V69.6953H55.4174V75.5391H60.2729V76.8281ZM72.861 76.8281H65.8571V69.6953H68.0056V75.5391H72.861V76.8281ZM80.5937 76.8281H78.4023V69.6953H80.5937V76.8281ZM95.0724 71.3711L93.0959 71.9297C92.6089 71.2708 91.8211 70.9414 90.7326 70.9414C89.3003 71.056 88.5269 71.8151 88.4123 73.2188C88.4696 74.7083 89.3003 75.4961 90.9045 75.582C92.0216 75.582 92.7808 75.3385 93.1818 74.8516V73.9922H90.3888V72.6602H95.1584V75.3242C94.5282 76.4128 93.0815 76.957 90.8185 76.957C87.8394 76.8424 86.2782 75.5964 86.1349 73.2188C86.3068 70.9271 87.825 69.7096 90.6896 69.5664C92.8381 69.5664 94.299 70.168 95.0724 71.3711ZM108.305 76.8281H100.871V69.6953H108.176V70.9844H103.02V72.5312H107.832V73.8633H103.02V75.5391H108.305V76.8281ZM122.311 76.8281H120.12L116.381 72.3594C116.353 72.3307 116.31 72.2734 116.253 72.1875C116.081 71.9583 115.952 71.7865 115.866 71.6719C115.895 71.8724 115.909 72.2734 115.909 72.875V76.8281H113.889V69.6953H116.21L119.819 74.0352C120.105 74.4076 120.277 74.6367 120.335 74.7227C120.306 74.4935 120.292 74.1354 120.292 73.6484V69.6953H122.311V76.8281ZM136.274 70.9844H133.224V76.8281H131.032V70.9844H127.938V69.6953H136.274V70.9844ZM153.158 74.293H151.568V76.8281H149.419V69.6953H153.372C155.664 69.724 156.853 70.4688 156.939 71.9297C156.967 73.5052 155.707 74.293 153.158 74.293ZM152.986 70.8984H151.568V73.0469H152.986C154.103 73.0182 154.676 72.6602 154.704 71.9727C154.647 71.2852 154.074 70.9271 152.986 70.8984ZM170.988 76.8281H168.281L165.961 74.0352H164.714V76.8281H162.566V69.6953H166.906C169.226 69.7526 170.429 70.4544 170.515 71.8008C170.544 72.9753 169.77 73.6914 168.195 73.9492L170.988 76.8281ZM166.562 70.8984H164.714V72.832H166.562C167.679 72.832 168.238 72.5169 168.238 71.8867C168.267 71.1992 167.708 70.8698 166.562 70.8984ZM185.338 73.2617C185.166 75.582 183.605 76.8138 180.654 76.957C177.675 76.8138 176.099 75.582 175.928 73.2617C176.071 70.8841 177.646 69.6523 180.654 69.5664C183.576 69.7096 185.137 70.9414 185.338 73.2617ZM182.974 73.2617C182.917 71.7721 182.129 70.9987 180.611 70.9414C179.064 71.0273 178.248 71.8008 178.162 73.2617C178.219 74.7227 179.021 75.4961 180.568 75.582C182.144 75.5534 182.946 74.7799 182.974 73.2617ZM194.059 76.8281H190.965V69.6953H193.93C197.339 69.724 199.072 70.8841 199.129 73.1758C199.244 75.6966 197.553 76.9141 194.059 76.8281ZM193.887 70.9844H193.156V75.5391H193.887C195.863 75.5391 196.852 74.7513 196.852 73.1758C196.794 71.7435 195.806 71.013 193.887 70.9844ZM204.713 73.7344V69.6953H206.862V73.7344C206.89 74.9375 207.606 75.5534 209.01 75.582C210.356 75.582 211.03 74.9661 211.03 73.7344V69.6953H213.221V73.6914C213.221 75.8685 211.774 76.957 208.881 76.957C206.131 76.9284 204.742 75.8542 204.713 73.7344ZM227.356 71.7578L225.294 72.1016C224.978 71.3568 224.32 70.9701 223.317 70.9414C221.856 70.9987 221.083 71.7578 220.997 73.2188C221.054 74.7083 221.827 75.4961 223.317 75.582C224.491 75.5534 225.179 75.0378 225.379 74.0352L227.442 74.5078C226.955 76.1406 225.58 76.957 223.317 76.957C220.395 76.8424 218.863 75.5964 218.719 73.2188C218.891 70.9271 220.424 69.7096 223.317 69.5664C225.379 69.6237 226.726 70.3542 227.356 71.7578ZM241.018 70.9844H237.968V76.8281H235.776V70.9844H232.682V69.6953H241.018V70.9844ZM248.751 76.8281H246.56V69.6953H248.751V76.8281ZM263.788 73.2617C263.617 75.582 262.055 76.8138 259.105 76.957C256.126 76.8138 254.55 75.582 254.378 73.2617C254.521 70.8841 256.097 69.6523 259.105 69.5664C262.027 69.7096 263.588 70.9414 263.788 73.2617ZM261.425 73.2617C261.368 71.7721 260.58 70.9987 259.062 70.9414C257.515 71.0273 256.699 71.8008 256.613 73.2617C256.67 74.7227 257.472 75.4961 259.019 75.582C260.594 75.5534 261.396 74.7799 261.425 73.2617ZM277.837 76.8281H275.646L271.908 72.3594C271.879 72.3307 271.836 72.2734 271.779 72.1875C271.607 71.9583 271.478 71.7865 271.392 71.6719C271.421 71.8724 271.435 72.2734 271.435 72.875V76.8281H269.416V69.6953H271.736L275.345 74.0352C275.632 74.4076 275.804 74.6367 275.861 74.7227C275.832 74.4935 275.818 74.1354 275.818 73.6484V69.6953H277.837V76.8281Z" />
  </g>
  <defs>
    <filter id="filter0_d_359_2426" x="0.533997" y="2.77399" width="282.07" height="47.838" filterUnits="userSpaceOnUse"
      color-interpolation-filters="sRGB">
      <feFlood flood-opacity="0" result="BackgroundImageFix" />
      <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
        result="hardAlpha" />
      <feOffset dy="2" />
      <feGaussianBlur stdDeviation="1" />
      <feComposite in2="hardAlpha" operator="out" />
      <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
      <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_359_2426" />
      <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_359_2426" result="shape" />
    </filter>
    <filter id="filter1_d_359_2426" x="2.1626" y="69.5664" width="277.675" height="11.3906" filterUnits="userSpaceOnUse"
      color-interpolation-filters="sRGB">
      <feFlood flood-opacity="0" result="BackgroundImageFix" />
      <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
        result="hardAlpha" />
      <feOffset dy="2" />
      <feGaussianBlur stdDeviation="1" />
      <feComposite in2="hardAlpha" operator="out" />
      <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
      <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_359_2426" />
      <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_359_2426" result="shape" />
    </filter>
  </defs>
</svg>

可以看到 filter0_d_359_2426 应该是一个全局唯一的 id,如果其他 svg 内部也有这个 id ,会造成样式冲突,我们想要的效果是 每个 svg 内部样式应该是独立隔离,互不影响的

因此,我们需要对 svg 内部的 id 做唯一化处理,这个可以直接在 vite-svg-loader 配置 svgo 来解决

// ./vite.config.ts
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import svgLoader from 'vite-svg-loader';

export default defineConfig(({ command, mode }) => {
  return {
    plugins: [
      vue(),
      svgLoader({
        svgoConfig: {
          plugins: [
                // https://github.com/svg/svgo#configuration
                svgoConfig: {
                    plugins: [
                        {
                            name: 'cleanupIDs',
                            params: {
                                prefix: {
                                    // 避免不同 svg 内部的 filter id 相同导致样式错乱
                                    // https://github.com/svg/svgo/issues/674#issuecomment-328774019
                                    toString() {
                                        let count: number = this.count ?? 0;
                                        count++;
                                        this.count = count;
                                        return `svg-random-${count.toString(
                                            36,
                                        )}-`;
                                    },
                                } as string,
                            },
                        },
                    ],
                },
          ],
        },
      }),
    ],
  };
});

总结

我们阐述了 iconfont 以及 symbol 方式使用 svg 的缺点,同时说明了使用 svg 原生节点可以解决这些缺点

但是 svg 原生节点 使用不便,于是我们使用一些工具封装了 svg 原生节点的 vue 组件,让它使用便利性与原来一致

同时针对 vue 组件做了 css scoped 和 id 唯一化 的优化