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

Programmatically change SVG classes during runtime

-我想多次将 相同的 SVG 绘制到 canvas 上,但每次我都想以编程方式更改特定 类 里面 SVG.

例如下面这张房子的图片:

这个房子的 SVG 有以下 类:

<style>

  .window-class {
    fill: lime;
  }

  .door-class {
    fill: blue;
  }

  .house-class {
    fill: tan;
  }

  .roof-class {
    fill: red;
  }

</style>

如何以编程方式访问这些特定样式-类以便我可以为我绘制的每个新房子更改它们的颜色值?

我正在构建 SVG,方法是创建一个 Image 对象,然后使用以下代码将该图像绘制到 canvas 上:

    // 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;
    

通常情况下,我可以通过以下方式获得我想要更改的特定 类 谁的颜色:

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

然后我会遍历那个 nodeList 并且在我发现每个元素都用这个 window-class 设置样式时,我会这样做:

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

但是由于我是按照上面显示的方式创建我的图像,所以我不确定如何获得其 SVG 的特定 类。

有什么想法吗?

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

更新:

指出多次绘制image/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();

所以当我现在 运行 时,控制台显示 nodeList 是空的:

    nodeList LENGTH = 0

所以基本上这个语句是行不通的:

    let nodeList = document.getElementsByClassName(currentClassName);

以下是产生您想要的结果的一种方法。

  1. 下面的方法将 HTML 中的 <svg> 元素用作模板。该模板被克隆、应用颜色、转换为图像并放置到每个有颜色的房子的 canvas 中。
    • 注意:SVG 的结构发生了变化。 class 属性替换为自定义 data- 属性 data-part,用于通过普通 CSS 选择器应用填充样式。
  2. 每个房子的坐标位置在一个数组中 space 分隔 x y 坐标。该数组还表示要绘制多少个房子
  3. 房子 'parts' 的颜色包含在列出房子 'part' 及其相应颜色的对象中(颜色的数量应与房子的数量相匹配)
  4. 所有 <canvas> CSS 已移至样式表。

我会让你处理 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>

要操纵房子的 DOM,SVG 必须位于 DOM 中。所以我将 SVG 包裹在 <div> 中并隐藏了 div。我已经把它放在屏幕外了,但我可以用其他几种方式隐藏 div。

一旦你这样做了,你的下一个问题是你正在改变元素的 fill,但是这将被你的 SVG 中的 CSS 覆盖。所以你必须删除那些 CSS 样式。

第三,您正在创建 canvas 个对象,但没有将它们附加到 DOM。

您还收到错误消息,因为 canvasX 未初始化。加上 CSS 长度 必须 有单位。所以你需要newCanvas.style.left = canvasX + "px"等等

您还错误地查找了您的元素。 getElementsByClassName(".hose-class") 找不到任何东西。它需要 getElementsByClassName(".hose-class").

最后,我重写了元素查找和颜色分配代码。我已将每个配色方案捆绑到一个配色方案对象数组中。它使 类 到颜色的映射变得更加简单。

// 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>