为了实现更换svg颜色的操作

为了实现更换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();
}

Leave a comment

Your email address will not be published. Required fields are marked *