简体   繁体   English

在运行时以编程方式更改 SVG 类

[英]Programmatically change SVG classes during runtime

-I want to draw the same SVG onto a canvas multiple times, but each time I want to PROGRAMMATICALLY change the colors of specific classes within that SVG . - 我想多次在canvas上绘制相同的SVG ,但每次我想以编程方式更改该SVG中特定类的颜色。

For example take this house image below:例如下面这张房子的图片:

在此处输入图片说明

The SVG for this House has the following classes:这所房子的 SVG 具有以下类:

<style>

  .window-class {
    fill: lime;
  }

  .door-class {
    fill: blue;
  }

  .house-class {
    fill: tan;
  }

  .roof-class {
    fill: red;
  }

</style>

How do I programmatically access these specific Style-Classes so I can change their color values for each new house that I draw?我如何以编程方式访问这些特定的样式类,以便我可以为我绘制的每个新房子更改它们的颜色值?

I'm constructing the SVG by creating an Image object and then drawing that image onto a canvas using the following code:我通过创建一个 Image 对象然后使用以下代码将该图像绘制到canvas上来构建SVG

    // 1. Create the CANVAS and the CONTEXT:
    var theCanvas = document.getElementById("theCanvas");
    var theContext = theCanvas.getContext("2d");
    theContext.fillStyle = "lightblue";
    theContext.fillRect(0, 0, theCanvas.width, theCanvas.height);

    // 2. Define the SVG data:
    var imageData = '<svg id="HOUSE" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="240.26" height="311.24" viewBox="0 0 240.26 311.24"><defs><style>.house-class {fill: tan;}.roof-class {fill: red;}.roof-class, .window-class, .door-class {stroke: #000;stroke-miterlimit: 10;}.window-class {fill: lime;}.door-class {fill: blue;}</style></defs><g id="House"><rect class="house-class" x="30.08" y="131.74" width="173.07" height="179"/><path d="M270,242V420H98V242H270m1-1H97V421H271V241Z" transform="translate(-67.39 -109.76)"/></g><polygon id="Roof" class="roof-class" points="1.11 131.74 239.11 131.74 117.11 0.74 1.11 131.74"/><rect id="Window2" class="window-class" x="145.11" y="160.74" width="30" height="42"/><rect id="Window1" class="window-class" x="58.61" y="160.74" width="30" height="42"/><rect id="Door" class="door-class" x="92.11" y="228.74" width="52" height="82"/></svg>';

    var DOMURL = window.URL || window.webkitURL || window;

    var img = new Image();
    var svg = new Blob([imageData], { type: 'image/svg+xml;charset=utf-8' });
    var url = DOMURL.createObjectURL(svg);

    img.onload = function () {
        theContext.drawImage(img, 0, 0);
        DOMURL.revokeObjectURL(url);
    }

    img.src = url;
    

Ordinarily I'd be able to get at the specific Classes who's colors I want to change by using this:通常,我可以使用以下方法获得我想要更改颜色的特定类:

    let nodeList = document.getElementsByClassName("window-class");

And then I would iterate through that nodeList and at each element I find that is styled with this window-class , I would do:然后我会遍历那个nodeList并且在我发现使用这个window-class样式的每个元素上,我会这样做:

        element.style.fill = -whatever-the-next-color-would-be-;

But since I'm creating my image in the manner I showed above, I'm not sure how I can get at specific classes of its SVG .但是由于我是按照上面显示的方式创建图像的,所以我不确定如何获得其SVG特定类。

Any thoughts?有什么想法吗?

============================== ==============================

UPDATE:更新:

It was pointed out that the code for drawing the image/SVG multiple times wasn't included, so here it is:有人指出多次绘制图像/SVG的代码没有包括在内,所以这里是:

        // GLOBAL VARIABLES:
        const TOTAL_IMAGES = 3;  // could be 30, or 300
        const canvasWidth = 250;
        const canvasHeight = 320;

        var canvasX, canvasY = 0;

        // COLOR VARIABLES:
        var colorCounter = 0;
        let houseColorsArray = ["fuchsia", "gold", "lighblue"]; // Will have lots more colors for all of these 
        let windowColorsArray = ["yellow", "pink", "lightgreen"];
        let roofColorsArray = ["maroon", "crimson", "darkred"];
        let doorColorsArray = ["darkBlue", "purple", "darkslategray"];


        // CLASS-NAMES
        let classNamesToPaintArray = [".house-class", ".door-class", ".window-class", ".roof-class"];



        function designOneHouse(theCanvas) {
            console.log("\n\n==========================\n=");
            console.log("= =>>In 'designOneHouse()'!\n");

            // 1. Create a Color-Scheme:
            let houseColor = houseColorsArray[colorCounter];
            let doorColor = doorColorsArray[colorCounter];
            let windowColor = windowColorsArray[colorCounter];
            let roofColor = roofColorsArray[colorCounter];
            console.log("  ->Current 'houseColor' = ", houseColor);
            console.log("  ->Current 'doorColor' = ", doorColor);
            console.log("  ->Current 'windowColor' = ", windowColor);
            console.log("  ->Current 'roofColor' = ", roofColor);

            let context = theCanvas.getContext("2d");


            // Iterate the ColorCounter - making sure we don't overflow the ColorsArrays:
            colorCounter++;
            if(colorCounter == houseColorsArray.length) {
                colorCounter = 0;
            }


            // Now GET-AT and PAINT the Individual SVG Components.
            // STRATEGY:
            // 1. Iterate through the Array containing all the CLASS-NAMES who's color I want to change.
            // 2. For each of these classes, I'll need to iterate through all the HTML elements that are OF that class type
            //    (there may be like 10 elements that are all styled by the same Style; I want all of them to be updated!)
            // 

            for(classNameCounter = 0; classNameCounter < classNamesToPaintArray.length; classNameCounter++) {
                let currentClassName = classNamesToPaintArray[classNameCounter];
                console.log("currentClassName = " + currentClassName);

                let nodeList = document.getElementsByClassName(currentClassName);
                console.log("nodeList = " + nodeList);
                console.log("nodeList LENGTH = " + nodeList.length);

                for(var counter = 0; counter < nodeList.length; counter++) {
                    console.log("\n\n===>>IN FOR LOOP -- Node-COUNTER = " + counter);
                    let currentNode = nodeList[counter];
                    console.dir("  > 'childNodes[0]' of 'currentNode' = ");
                    console.dir(currentNode.childNodes[0]);

                    let elements = document.querySelectorAll(".door-class");
                    // Change the text of multiple elements with a loop
                    elements.forEach(element => {
                        element.style.fill = "pink";
                    });

                }

            }

        }



        function makeCanvasGrid() {
            console.log("\n\n====>In 'makeCanvasGrid()'!\n");

            for(var canvasCounter = 0; canvasCounter < TOTAL_IMAGES; canvasCounter++) {
                console.log("\n >FOR LOOP - canvasCounter = " + canvasCounter);

                // 1. Create a new Canvas Object:
                let newCanvas = document.createElement("canvas");
                newCanvas.setAttribute("width", canvasWidth);
                newCanvas.setAttribute("height", canvasHeight);
                newCanvas.setAttribute("id", "newCanvas" + canvasCounter);
                // Log-out just to verify the "id" property was set correctly:
                console.log("  >newCanvas.id  = " + newCanvas.id);

                // 2. Place the Canvas at (x,y) (top, left) coordinates:
                newCanvas.style.position = "absolute";
                newCanvas.style.left = canvasX; //"100px";
                newCanvas.style.top = canvasY;  //"100px";

                designOneHouse(newCanvas);


                // Check the current Canvas' (X, Y) coords, and if needed, reset X to 0 and SKIP to the next "ROW" of Canvasses:
                if(canvasCounter > 0 && canvasCounter % 3 == 0) {
                    console.log("  >>NEXT ROW PLEASE!!!! canvasCount = ", canvasCounter);
                    canvasX = 0;
                    canvasY += canvasHeight + 20;
                }
                else {
                    canvasX += canvasWidth + 10;
                }
            }
        }


        makeCanvasGrid();

SO when I run this right now, the console shows me the nodeList is empty:所以当我现在运行它时,控制台显示nodeList是空的:

    nodeList LENGTH = 0

So basically this statement isn't working:所以基本上这个语句不起作用:

    let nodeList = document.getElementsByClassName(currentClassName);

Below is one way to produce your desired result.以下是产生所需结果的一种方法。

  1. The approach below has the <svg> element in the HTML to be used as a template.下面的方法将 HTML 中的<svg>元素用作模板。 That template is cloned, colors applied, converted into an Image and placed into the canvas for each house that has colors.该模板被克隆,应用颜色,转换为图像并放置到每个具有颜色的房子的画布中。
    • Note: the structure of the SVG changed.注意:SVG 的结构发生了变化。 The class attributes are replaced by a custom data- attribute data-part that is used to apply the fill style through a normal CSS selector. class属性被自定义data-属性data-part替换,该data-part用于通过普通 CSS 选择器应用填充样式。
  2. The coordinate positions of each house are in an array of space separated xy coordinates.每个房子的坐标位置都在一个空间分隔的xy坐标数组中。 The array also indicates how many houses are to be drawn该数组还指示要绘制的房屋数量
  3. The colors for the house 'parts' are included in an Object that lists the house 'part' and its corresponding color (the count of colors should match the number of houses)房屋“部分”的颜色包含在列出房屋“部分”及其相应颜色的对象中(颜色数量应与房屋数量相匹配)
  4. All <canvas> CSS is moved to the stylesheet.所有<canvas> CSS 都移动到样式表中。

I'll let you deal with sizing the image on the canvas.我会让你处理画布上图像的大小。

 const canvas = document.querySelector('canvas'); const context = canvas.getContext("2d"); const housePositions = ["0 10", "85 10", "170 10"]; const parts = { House: ["fuchsia", "gold", "lightblue"], Window: ["yellow", "pink", "lightgreen"], Roof: ["maroon", "crimson", "darkred"], Door: ["darkBlue", "purple", "darkslategray"] }; function addHouse(colorIndex, x, y) { let clonedSvgElement = document.querySelector('svg').cloneNode(true); Object.keys(parts) .forEach(part => { clonedSvgElement.querySelectorAll(`[data-part=${part}]`) .forEach(item => { item.style.fill = parts[part][colorIndex]; }); const blob = new Blob([clonedSvgElement.outerHTML], { type: 'image/svg+xml;charset=utf-8' }); const blobURL = URL.createObjectURL(blob); const image = new Image(); image.onload = () => { context.drawImage(image, x, y, 130, 110); URL.revokeObjectURL(this.src); }; image.src = blobURL; }); } housePositions.forEach((coordString, index) => { const [x, y] = coordString.split(' '); addHouse(index, x, y); });
 canvas { position: absolute; left: 10px; top: 10px; width: 150px; height: 80px; border: 1px solid; background-color: lightblue; } svg { display: none; }
 <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link rel="stylesheet" href="index.css"> <title>Document</title> <script defer src="index.js"></script> </head> <body> <canvas></canvas> <svg id="HOUSE" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="140" height="140" viewBox="0 0 240.26 311.24"><defs></defs><g id="House"><rect data-part="House" x="30.08" y="131.74" width="173.07" height="179"/><path d="M270,242V420H98V242H270m1-1H97V421H271V241Z" transform="translate(-67.39 -109.76)"/></g><polygon data-part="Roof" points="1.11 131.74 239.11 131.74 117.11 0.74 1.11 131.74"/><rect data-part="Window" x="145.11" y="160.74" width="30" height="42"/><rect data-part="Window" x="58.61" y="160.74" width="30" height="42"/><rect data-part="Door" x="92.11" y="228.74" width="52" height="82"/></svg> </body> </html>

To manipulate the house's DOM, the SVG has to be in the DOM.要操作房子的 DOM,SVG 必须在 DOM 中。 So I've wrapped the SVG in a <div> and hidden the div.所以我将 SVG 包裹在<div>并隐藏了 div。 I've put it way offscreen, but I could have hidden the div in several other ways.我已经把它放在屏幕外,但我可以用其他几种方式隐藏 div。

Once you do that, your next problem is that you are changing the fill of the elements, but that will be overridded by the CSS in your SVG.一旦你这样做了,你的下一个问题是你正在改变元素的fill ,但这将被你的 SVG 中的 CSS 覆盖。 So you have to remove those CSS styles.所以你必须删除那些 CSS 样式。

Thirdly, you are creating canvas objects, but you are not attaching them to the DOM.第三,您正在创建画布对象,但没有将它们附加到 DOM。

Also you are getting an error because canvasX isn't initialised.您也收到错误,因为canvasX未初始化。 Plus CSS lengths must have units.加上 CSS 长度必须有单位。 So you need newCanvas.style.left = canvasX + "px" etc.所以你需要newCanvas.style.left = canvasX + "px"等。

You were also looking up your elements wrongly.您也错误地查找了您的元素。 getElementsByClassName(".hose-class") won't find anything. getElementsByClassName(".hose-class")找不到任何东西。 It needed to be getElementsByClassName(".hose-class") .它需要是getElementsByClassName(".hose-class")

Finally, I've rewritten the element lookup and colour assignment code.最后,我重写了元素查找和颜色分配代码。 I've bundled each colour scheme up into an array of colour scheme objects.我已将每种配色方案捆绑到一组配色方案对象中。 It makes mapping classes to colours much simpler.它使将类映射到颜色变得更加简单。

 // GLOBAL VARIABLES: const TOTAL_IMAGES = 3; // could be 30, or 300 const canvasWidth = 250; const canvasHeight = 320; var canvasX = 0, canvasY = 0; // COLOR VARIABLES: var colorCounter = 0; let houseColorSchemes = [ {".house-class": "fuchsia", ".door-class": "darkblue", ".window-class": "yellow", ".roof-class": "maroon"}, {".house-class": "gold", ".door-class": "purple", ".window-class": "pink", ".roof-class": "crimson"}, {".house-class": "lightblue", ".door-class": "darkslategray", ".window-class": "lightgreen", ".roof-class": "darkred"} ]; // CLASS-NAMES let classNamesToPaintArray = [".house-class", ".door-class", ".window-class", ".roof-class"]; // SVG template let houseSVG = document.getElementById("HOUSE"); function designOneHouse(theCanvas) { console.log("\\n\\n==========================\\n="); console.log("= =>>In 'designOneHouse()'!\\n"); let context = theCanvas.getContext("2d"); // Now GET-AT and PAINT the Individual SVG Components. // STRATEGY: // 1. Iterate through the Array containing all the CLASS-NAMES who's color I want to change. // 2. For each of these classes, I'll need to iterate through all the HTML elements that are OF that class type // (there may be like 10 elements that are all styled by the same Style; I want all of them to be updated!) // let colorScheme = houseColorSchemes[colorCounter]; classNamesToPaintArray.forEach(className => { let elements = houseSVG.querySelectorAll(className); elements.forEach(element => element.style.fill = colorScheme[className]); }); var imageData = houseSVG.outerHTML; var DOMURL = window.URL || window.webkitURL || window; var img = new Image(); var svg = new Blob([imageData], { type: 'image/svg+xml;charset=utf-8' }); var url = DOMURL.createObjectURL(svg); img.onload = function () { context.drawImage(img, 0, 0); DOMURL.revokeObjectURL(url); } img.src = url; // Iterate the ColorCounter - making sure we don't overflow the ColorsArrays: colorCounter++; if(colorCounter == houseColorSchemes.length) { colorCounter = 0; } } function makeCanvasGrid() { console.log("\\n\\n====>In 'makeCanvasGrid()'!\\n"); for(var canvasCounter = 0; canvasCounter < TOTAL_IMAGES; canvasCounter++) { console.log("\\n >FOR LOOP - canvasCounter = " + canvasCounter); // 1. Create a new Canvas Object: let newCanvas = document.createElement("canvas"); newCanvas.setAttribute("width", canvasWidth); newCanvas.setAttribute("height", canvasHeight); newCanvas.setAttribute("id", "newCanvas" + canvasCounter); // Log-out just to verify the "id" property was set correctly: console.log(" >newCanvas.id = " + newCanvas.id); // 2. Place the Canvas at (x,y) (top, left) coordinates: newCanvas.style.position = "absolute"; newCanvas.style.left = canvasX + "px"; //"100px"; newCanvas.style.top = canvasY + "px"; //"100px"; document.body.appendChild(newCanvas); designOneHouse(newCanvas); // Check the current Canvas' (X, Y) coords, and if needed, reset X to 0 and SKIP to the next "ROW" of Canvasses: if(canvasCounter > 0 && canvasCounter % 3 == 0) { console.log(" >>NEXT ROW PLEASE!!!! canvasCount = ", canvasCounter); canvasX = 0; canvasY += canvasHeight + 20; } else { canvasX += canvasWidth + 10; } } } makeCanvasGrid();
 #house-template { position: absolute; left: -1000px; }
 <div id="house-template"> <svg id="HOUSE" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="240.26" height="311.24" viewBox="0 0 240.26 311.24"> <defs> <style> .roof-class, .window-class, .door-class {stroke: #000;stroke-miterlimit: 10;} </style> </defs> <g id="House"> <rect class="house-class" x="30.08" y="131.74" width="173.07" height="179"/> <path d="M270,242V420H98V242H270m1-1H97V421H271V241Z" transform="translate(-67.39 -109.76)"/> </g> <polygon id="Roof" class="roof-class" points="1.11 131.74 239.11 131.74 117.11 0.74 1.11 131.74"/> <rect id="Window2" class="window-class" x="145.11" y="160.74" width="30" height="42"/> <rect id="Window1" class="window-class" x="58.61" y="160.74" width="30" height="42"/> <rect id="Door" class="door-class" x="92.11" y="228.74" width="52" height="82"/> </svg> </div>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM