# 统一解决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都是提取出来的那一份,就避免受到隐藏的影响了