[英]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.以下是产生所需结果的一种方法。
<svg>
element in the HTML to be used as a template.<svg>
元素用作模板。 That template is cloned, colors applied, converted into an Image and placed into the canvas for each house that has colors.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 选择器应用填充样式。xy
coordinates.xy
坐标数组中。 The array also indicates how many houses are to be drawn<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.