为了实现更换svg颜色的操作
面临的挑战有
解析SVG元素
通过参考get-svg-colors这个工程
https://github.com/colorjs/get-svg-colors/
我得到了通过cheerio来解析svg文本的处理方式
但在实际中,一个svg的颜色表达有很多种
可能来自行内style,属性,或是style里定义的class描述.
还会存在于一些引用型如 渐变引用
而get-svg-colors并不能解决 class描述型的问题.
因此我接合window.getComputedStyle方法,解决了这一问题
同时因为有些SVG的ID值不是很规范,我也统一换成了uuid
const cheerio = require("cheerio");
const _ = require("lodash");
const chroma = require("chroma-js");
const uuid = require("uuid").v4;
const hexy = /^#[0-9a-f]{3,6}$/i;
function isColorString(str) {
if (!str) return false;
if (str === "none") return false;
if (str.indexOf("url") != -1) return false;
return true;
}
function color(str) {
return isColorString(str) ? chroma(str) : null;
}
let container;
if (process.env.ENV != "node") {
container = document.createElement("div");
container.style.position = "absolute";
container.style.overflow = "hidden";
container.style.width = "0";
container.style.height = "0";
document.body.append(container);
}
/**
* 整理一下svg文本.规范化id和class的命名
* 为svg默认加上了width,height 100%的值
* @param svg
*/
export function getCleanSVG(svg) {
const $ = cheerio.load(svg, { decodeEntities: false });
let ids = {};
$("[id]").each(function (i, el) {
let newId = ids[$(this).attr("id")] || "_" + uuid();
ids[$(this).attr("id")] = newId;
$(this).attr("id", newId);
});
let classes = {};
$("[class]").each(function (i, el) {
let classesStr = _.compact(
$(this).attr("class").trimLeft().trimRight().split(" ")
);
let newClasses = classesStr.map((c) => {
let newId = classes[c] || "_" + uuid();
classes[c] = newId;
return newId;
});
$(this).attr("class", newClasses.join(" "));
});
$("svg").first().css("width", "100%");
$("svg").first().css("height", "100%");
let newSvg = $.root().html();
Object.keys(ids).forEach((k) => {
newSvg = newSvg.replace(
new RegExp(`(['"\(])#${k}(['"\)])`, "igsm"),
`$1#${ids[k]}$2`
);
});
Object.keys(classes).forEach((k) => {
newSvg = newSvg.replace(
new RegExp(`(\<style\>.*\).${k}(.*\<\/style\>)`, "igsm"),
`$1.${classes[k]}$2`
);
});
let innerSvg = /\<svg.*?\>(.*)\<\/svg\>/gims.exec(newSvg)[1];
var outSvgEle = document.createElementNS(
"http://www.w3.org/2000/svg",
"svg"
);
outSvgEle.innerHTML = innerSvg;
container.append(outSvgEle);
outSvgEle.querySelectorAll("[class]").forEach((el: any, i) => {
let computedStyle = window.getComputedStyle(el);
el.style.fill = computedStyle.fill;
});
newSvg = newSvg.replace(
/(\<svg.*?\>).*(\<\/svg\>)/gims,
`$1${outSvgEle.innerHTML}$2`
);
return newSvg;
}
/**
* 获取svg中的颜色映射关系
* 最后得到的是{color:[index,type]}这样的对象
* @param svg
*/
export function getSVGColorMap(svg) {
const $ = cheerio.load(svg);
let fills = [];
let strokes = [];
let stops = [];
$("[style],[stop-color],[stroke],[fill],[class]").each(function (i, el) {
if (
$(this).css("fill") !== undefined ||
$(this).attr("fill") !== undefined
) {
let c = color($(this).css("fill") || $(this).attr("fill"));
c !== null && fills.push([c, i, 0]);
}
if (
$(this).css("stroke") != undefined ||
$(this).attr("stroke") !== undefined
) {
let c = color($(this).css("stroke") || $(this).attr("stroke"));
c !== null && strokes.push([c, i, 1]);
}
if (
$(this).css("stop-color") !== undefined ||
$(this).attr("stop-color") !== undefined
) {
let c = color(
$(this).css("stop-color") || $(this).attr("stop-color")
);
c !== null && stops.push([c, i, 2]);
}
});
//原先的数据是[color,序号,type]这样的三个二维数组. type值:0:fill,1:stroke,2:stop-color
//1.合并
//2.然后再以color为key做分组
// 得到 {color:[[color,index,type]]}
//3.再通过mapValues,配合进一步的处理函数,将值缩减成只有唯一的index的
//4.最后得到的就是{color:[index,type]} 这样的对象
return _.mapValues(
_.groupBy(fills.concat(strokes).concat(stops), (d) => d[0]),
(data) => _.uniq(_.map(data, ([c, i, t]) => [i, t]))
);
}
/**
* 为svg设置新的颜色值
* @param svg
* @param colorMap
* @param newColors
*/
export function setSVGColors(svg, colorMap, newColors) {
const $ = cheerio.load(svg);
let oldColors = Object.keys(colorMap);
let ncMap = {};
let changeColors = oldColors.forEach((c, i) => {
if (c != newColors[i]) {
colorMap[c].forEach(([cIndex, type]) => {
ncMap[cIndex] = [newColors[i], type];
});
}
});
$("[style],[stop-color],[stroke],[fill]").each(function (i, el) {
if (ncMap[i] !== undefined) {
let v = ncMap[i][0];
let t = ncMap[i][1];
switch (t) {
case 0:
$(el).css("fill", v);
break;
case 1:
$(el).css("stroke", v);
break;
case 2:
$(el).css("stop-color", v);
break;
}
}
});
return $.root().html();
}