# 统一解决SVG图标渐变色id重复的问题

# 背景

在svg中,显示渐变色只有以下的一种方案:

<defs>
    <linearGradient id="sundray-wireless-dev-linearGradient-1">
        <stop stop-color="#00A2E4" offset="0%"></stop>
        <stop stop-color="#006FBA" offset="100%"></stop>
    </linearGradient>
</defs>
<g id="四级备份-21" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
    <g id="编组" fill-rule="nonzero">
        <path fill="url(#sundray-wireless-dev-linearGradient-1)"></path>
    </g>
</g>

通过id去找出定义的渐变色,再进行渲染。

svg的这套实现有个缺点就是查找id的逻辑是从全局开始找的,以找到的第一个为准。

这时候如果第一个是被隐藏起来的,就会导致其后面的图标的渐变色渲染不出来。

# 解决方案

借助svg查找id的特性,想到了将svg的渐变色单独提出来,这样每次找的就是定义好的没被隐藏的了。

# 找出所有svg

通过编写vite自定义插件,可以在项目初始化时找出所有的svg

伪代码:

{
  name: 'svg-loader',
  enforce: 'pre',
  async load(id) {
    // 此处查找所有svg
  },
};

# 提取渐变色

判断内容中包含radialGradient、linearGradient的,就认为是带渐变色的svg。

首先生成唯一的id去替换原svg中的渐变色和path中用到的id(设计给的可能会重复)

再通过正则匹配出渐变色的代码,记录起来,后续使用,并从原svg中删除

function handleGradient(content: string, gradientSvg: string[]) {
  if (content.includes('radialGradient') || content.includes('linearGradient')) {
    let _content = content;
    // 保证id为唯一id
    const idMap: Record<string, string> = {}; // 用于存储原始id和新id的映射关系
    _content = _content.replace(/id="([^"]*)"/g, (match, id) => {
      if (!idMap[id]) {
        idMap[id] = uuid();
      }
      return `id="${idMap[id]}"`;
    });
    _content = _content.replace(/url\(#([^"]*)\)/g, (match, id) => {
      if (idMap[id]) {
        return `url(#${idMap[id]})`;
      }
      return id;
    });

    // 截取字符串中的<defs>到</defs>之间的内容
    const defs = _content.match(/<defs>([\s\S]*?)<\/defs>/g)?.[0] || '';
    if (defs) {
      gradientSvg.push(defs);
      _content = _content.replace(defs, '');
    }

    return {
      content: _content,
      isGradient: true,
    };
  }

  return {
    content,
    isGradient: false,
  };
}

# 生成独立渐变色集合

提取完所有渐变色后,循环拼接上,再包一层svg。

function generateGradientSvg(gradientSvg: string[]) {
  const gradient = gradientSvg.reduce((prev, next) => {
    return prev + next;
  }, '');

  return `<svg aria-hidden="true" focusable="false" style="width:0;height:0;position:absolute;">
    ${gradient}
  </svg>`;
}

# 添加到页面

在组件库install时,将刚才生成的svg添加到body的最前面。

注意该svg需移出视口区域,不能影响页面显示。

function appendSVGHtml () {
  const div = document.createElement('div');
  div.style.position = 'absolute';
  div.style.top = '-9999px';
  div.style.left = '-9999px';
  div.innerHTML = SVG_HTML;
  document.body.insertBefore(div, document.body.firstChild);
}

这样,svg每次找的id都是提取出来的那一份,就避免受到隐藏的影响了