Summary: I'm trying to create a canvas of randomized rock climbing holds using vector graphics generated with properties such as color, rotation, size and path values.
To add depth to these I'm trying to add a sort of randomized shadow to them to show that one or more sides of the shape are raised from the background.
Question:
I've been able to apply this filter onto the svg however as you can see on the below image "Light Filter" I get this white effect bleeding out to the edge of the svg element.
I'd like to find a way to keep that raised effect and have the color show or find a new way to show shadow randomized to each edge of the svg path?
You can find the filter code in the function: addFilter
You can disable the filter effect by commenting out the function addFilter(); and applyFilter();
No Filter:
Light Filter:
//create a filter for the svg copying the rough paper filter and apply it to the svg
var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filter.setAttribute("id", "roughpaper");
filter.setAttribute("x", "0%");
filter.setAttribute("y", "0%");
filter.setAttribute("width", "100%");
filter.setAttribute("height", "100%");
var feDiffuseLighting = document.createElementNS("http://www.w3.org/2000/svg", "feDiffuseLighting");
feDiffuseLighting.setAttribute("in", "noise");
feDiffuseLighting.setAttribute("lighting-color", "#ffffff");
feDiffuseLighting.setAttribute("surfaceScale", "2");
var feDistantLight = document.createElementNS("http://www.w3.org/2000/svg", "feDistantLight");
feDistantLight.setAttribute("azimuth", "45");
feDistantLight.setAttribute("elevation", "60");
feDiffuseLighting.appendChild(feDistantLight);
filter.appendChild(feDiffuseLighting);
document.getElementById("svg-0").appendChild(filter);
body {
background-color: #333;
overflow: hidden;
}
#svg-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.svg-element {
position: absolute;
width: 150px;
height: 150px;
}
<div id="svg-container">
<svg
viewBox="0 0 200 200"
preserveAspectRatio="none"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
class="svg-element"
id="svg-0"
filter="url(#roughpaper)"
>
<path
d="M27.8,-30.1C31.8,-29.8,27.8,-17,30.3,-5C32.9,7,42,18.2,38.3,19.7C34.5,21.1,18,12.7,4.6,21.4C-8.9,30.1,-19.3,55.9,-25.4,58.5C-31.5,61.2,-33.3,40.7,-44.1,24.4C-54.8,8,-74.4,-4.3,-75.5,-15.9C-76.6,-27.6,-59.1,-38.6,-43.4,-36.8C-27.7,-34.9,-13.9,-20.3,-1,-19.1C11.9,-17.9,23.9,-30.3,27.8,-30.1Z"
transform="translate(100, 100)"
class="path"
id="path-0"
style="fill: rgb(106, 76, 147)"
></path>
</svg>
</div>
Diffuse light effects should be multiplied with the original, otherwise you'll see the the lighting color rather than the combination of original color + lighting. So just add a feBlend with a multiply - like so. Update: and then add a feComposite/in to "clip to self" - so you don't see the background lit with the light as well.
//create an array with multiple svg paths
var svgPaths = [
{
path: "M27.8,-30.1C31.8,-29.8,27.8,-17,30.3,-5C32.9,7,42,18.2,38.3,19.7C34.5,21.1,18,12.7,4.6,21.4C-8.9,30.1,-19.3,55.9,-25.4,58.5C-31.5,61.2,-33.3,40.7,-44.1,24.4C-54.8,8,-74.4,-4.3,-75.5,-15.9C-76.6,-27.6,-59.1,-38.6,-43.4,-36.8C-27.7,-34.9,-13.9,-20.3,-1,-19.1C11.9,-17.9,23.9,-30.3,27.8,-30.1Z",
},
{
path: "M36.5,-45.3C49.8,-32.4,64.8,-23.2,69,-10.5C73.3,2.2,66.7,18.5,58.3,34.1C49.8,49.8,39.4,64.8,23.8,74.4C8.1,84,-12.7,88.2,-22.9,77.9C-33,67.7,-32.3,43,-35.4,26.1C-38.5,9.1,-45.3,0,-46.2,-10.5C-47.2,-21,-42.3,-32.7,-33.6,-46.4C-24.9,-60.1,-12.5,-75.7,-0.4,-75.2C11.6,-74.7,23.2,-58.1,36.5,-45.3Z",
}
];
var colors = ["#FF595E", "#FFCA3A", "#8AC926", "#1982C4", "#6A4C93"];
//create a function to apply properties to each svg element
function holdProps() {
//based on the id of the svg element, re-position the svg element on the screen
var svgElements = document.getElementsByClassName("svg-element");
//set the position of the svg element to the top left
for (var i = 0; i < svgElements.length; i++) {
svgElements[i].style.position = "absolute";
svgElements[i].style.top = "0";
svgElements[i].style.left = "0";
}
//console log the bounding box of each svg element
for (var i = 0; i < svgElements.length; i++) {
var svg = svgElements[i];
console.log(svg.getBoundingClientRect());
}
}
//create a function to apply properties to each svg elements path value
function holdPaths() {
//create a path and append it to each svg element
$(".svg-element").each(function () {
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
//set attributes to the above created path
path.setAttribute(
"d",
svgPaths[Math.floor(Math.random() * svgPaths.length)].path
);
path.setAttribute("transform", "translate(100, 100)");
path.setAttribute("class", "path");
path.setAttribute("id", "path-" + $(this).attr("id").split("svg-")[1]);
this.appendChild(path);
});
//If a path id is clicked change the path to a random path with a random color
$(".svg-element").click(function () {
var path = svgPaths[Math.floor(Math.random() * svgPaths.length)].path;
var color = colors[Math.floor(Math.random() * colors.length)];
$(this).find("path").attr("d", path);
$(this).find("path").css("fill", color);
});
}
//create a function to apply random colors to each svg element
function colorHold() {
$(".svg-element path").each(function () {
var color = colors[Math.floor(Math.random() * colors.length)];
$(this).css("fill", color);
//stroke white width 5 if the svg is hoverd over
$(this).hover(function () {
$(this).css("stroke", "white");
$(this).css("stroke-width", "5");
}
//reset stroke to black and stroke width to 1 if the svg is not hovered over
, function () {
$(this).css("stroke", "black");
$(this).css("stroke-width", "0");
}
);
});
}
//create feDistantLight and a fePointLight to each svg element
function addFilter() {
var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filter.setAttribute("id", "roughpaper");
filter.setAttribute("x", "0%");
filter.setAttribute("y", "0%");
filter.setAttribute("width", "100%");
filter.setAttribute("height", "100%");
var feGauss= document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
feGauss.setAttribute("stdDeviation", "4");
feGauss.setAttribute("result", "blur");
filter.appendChild(feGauss);
var feDiffuseLighting = document.createElementNS("http://www.w3.org/2000/svg", "feDiffuseLighting");
feDiffuseLighting.setAttribute("in", "blur");
feDiffuseLighting.setAttribute("lighting-color", "#ffffff");
feDiffuseLighting.setAttribute("surfaceScale", "6");
var feDistantLight = document.createElementNS("http://www.w3.org/2000/svg", "feDistantLight");
feDistantLight.setAttribute("azimuth", "235");
feDistantLight.setAttribute("elevation", "50");
feDiffuseLighting.appendChild(feDistantLight);
filter.appendChild(feDiffuseLighting);
var feBlend= document.createElementNS("http://www.w3.org/2000/svg", "feBlend");
feBlend.setAttribute("mode", "multiply");
feBlend.setAttribute("in2", "SourceGraphic");
filter.appendChild(feBlend);
var feComp= document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
feComp.setAttribute("operator", "in");
feComp.setAttribute("in2", "SourceGraphic");
filter.appendChild(feComp);
document.getElementsByTagName("svg")[0].appendChild(filter);
}
//apply the filter to each svg element
function applyFilter() {
var svgElements = document.getElementsByClassName("svg-element");
for (var i = 0; i < svgElements.length; i++) {
svgElements[i].setAttribute("filter", "url(#roughpaper)");
}
}
//on load run the functions
$(document).ready(function () {
//create a path for each svg element
holdPaths();
//apply properties to each svg element
holdProps();
//apply random colors to each svg element
colorHold();
//Disable these two to remove the filter
//create a filter to each svg element
addFilter();
//apply the filter to each svg element
applyFilter();
});
body {
background-color: #333;
overflow: hidden;
}
#svg-container {
/* full size of page*/
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.svg-element {
position: absolute;
}
svg path {
transition: 0.2s;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<div id="svg-container">
<!-- Create SVG -->
<svg id="svg-01" class="svg-element" width="100%" height="100%" viewBox="0 0 200 200" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
</svg>
</div>
Related
I am trying to change the FILL COLOR of a SHAPE on Mouseover & Mouseout. Most examples I've seen use the events StageX and StageY coordinates. However, those coordinates are the X & Y positions of the mouse at the time of the event. This forces the object to suddently move when "redrawn".
Other examples ask you to call "GetBounds"...which is not available for Shape objects.
I don't want the object to move simply because I am changing the FillColor
I see nothing resembling the top/left in the EVENT.TARGET
Q: How do I calculate or retrieve the Shapes current X/Y (top/left) coordinates?
THE HTML & JAVASCRIPT:
<style>
.dashboard { height:600px; }
.dashboard header { }
.dashboard aside { vertical-align:top; display:inline-block; }
.dashboard aside.control-bar { border: solid 1px black; border-radius: 3px; padding: 5px; height: 100%; width:10%; margin-right: 5px; }
.dashboard aside.control-bar {}
.dashboard aside.control-bar .btn { width:95%; }
.dashboard section { vertical-align:top; display:inline-block; }
.dashboard section.desktop { height:100%; min-width:80%; border:solid 1px black; border-radius: 3px; }
.dashboard section.desktop canvas { height:98%; width:99%; }
.dashboard footer { margin-top:5px; padding:5px; }
</style>
<div class="row">
<div class="col-md-12">
<main role="main" class="dashboard pb-3">
<header qwik-control="header">
<h3>Dashboard</h3>
</header>
<aside class="control-bar">
<center>
<h6>UI Controls</h6>
<a id="btnCreateNode" class="btn btn-sm btn-dark">Create Node</a>
</center>
</aside>
<section class="desktop">
<canvas id="demoCanvas"></canvas>
</section>
<footer>
<center>
<h5 style="color:#C7C9CD;">Button Controls</h5>
</center>
</footer>
</main>
</div>
</div>
<script src="https://code.createjs.com/1.0.0/createjs.min.js"></script>
<script type="text/javascript">
var stage = null,
loader = null;
// GLOBALS
var _PROPERTIES = { node: { y: 100, x: 200, fillColor: '#F9FAFB', fillOverColor: '#FCFCC2', strokeColor: '#000' } };
function node_create(){
console.log('node_create');
var top = Math.random() * 500;
var left = Math.random() * 500;
var width = _PROPERTIES.node.x;
var height = _PROPERTIES.node.y;
// Create
var node = new createjs.Shape();
node.graphics.beginStroke(_PROPERTIES.node.strokeColor);
node.graphics.beginFill(_PROPERTIES.node.fillColor);
node.graphics.setStrokeStyle(1);
node.snapToPixel = true;
node.graphics.drawRect(left, top, width, height);
node.graphics.endFill();
node.name = name;
node.overColor = _PROPERTIES.node.fillOverColor;
node.outColor = _PROPERTIES.node.fillColor;
// Events
node.on("mouseover", node_mouseover);
node.on("mouseout", node_mouseout);
// Display
stage.addChild(node);
stage.update();
};
function node_mouseover(evt) {
console.log('node_mouseover');
// COORDS is the events coordinates...not the targets Top/Left (e.g. cursor)
// How do I get the X/Y of the target? (getBounds does not exist for Shapes)
var target = evt.target;
var coords = { x: evt.stageX, y: evt.stageY };
target.graphics.clear();
target.graphics.beginStroke(_PROPERTIES.node.strokeColor);
target.graphics.beginFill(target.overColor);
target.graphics.drawRect(coords.x, coords.y, _PROPERTIES.node.x, _PROPERTIES.node.y);
target.graphics.endFill();
stage.update();
};
function node_mouseout(evt) {
console.log('node_mouseout');
// COORDS is the events coordinates...not the targets Top/Left (e.g. cursor)
// How do I get the X/Y of the target? (getBounds does not exist for Shapes)
var target = evt.target;
var coords = { x: evt.stageX, y: evt.stageY };
target.graphics.clear();
target.graphics.beginStroke(_PROPERTIES.node.strokeColor);
target.graphics.beginFill(target.outColor);
target.graphics.drawRect(coords.x, coords.y, _PROPERTIES.node.x, _PROPERTIES.node.y);
target.graphics.endFill();
stage.update();
};
$(document).ready(function () {
// Stage
stage = new createjs.Stage('demoCanvas');
stage.canvas.width = window.innerWidth;
stage.canvas.height = window.innerHeight;
// Stage - Events
stage.enableMouseOver(10);
// Queue
loader = new createjs.LoadQueue();
// DOM - EVENTS
$('#btnCreateNode').on('click', function(e){
node_create();
});
});
</script>
The solution is to put your Shapes into a Container. Shapes don't have the same properties & methods useful in determining location that other objects do: like images or bitmaps etc.
As such...Containers are the "solution" to that.
THE JAVASCRIPT:
var _PROPERTIES = { node: { y: 100, x: 200, fillColor: '#F9FAFB', fillOverColor: '#FCFCC2', strokeColor: '#000' } };
function node_create(){
console.log('node_create');
var top = Math.random() * 500;
var left = Math.random() * 500;
var width = _PROPERTIES.node.x;
var height = _PROPERTIES.node.y;
// Create
var node = new createjs.Shape();
node.graphics.beginStroke(_PROPERTIES.node.strokeColor);
node.graphics.beginFill(_PROPERTIES.node.fillColor);
node.graphics.setStrokeStyle(1);
node.snapToPixel = true;
node.graphics.drawRect(0, 0, width, height); //<-- changed
node.graphics.endFill();
node.name = name;
node.overColor = _PROPERTIES.node.fillOverColor; //<-- added
node.outColor = _PROPERTIES.node.fillColor; //<-- added
// Events
node.on("mouseover", node_mouseover);
node.on("mouseout", node_mouseout);
// Container
var container = new createjs.Container(); //<-- added
container.x = top;
container.y = left;
container.addChild(node);
// Display
stage.addChild(container); //<-- changed
stage.update();
};
function node_mouseover(evt) {
console.log('node_mouseover');
// Because of the container...you no longer need to calculate a TOP or LEFT
var target = evt.target;
target.graphics.clear();
target.graphics.beginStroke(_PROPERTIES.node.strokeColor);
target.graphics.beginFill(target.overColor);
target.graphics.drawRect(0, 0, _PROPERTIES.node.x, _PROPERTIES.node.y); //<-- changed
target.graphics.endFill();
stage.update();
};
function node_mouseout(evt) {
console.log('node_mouseout');
// Because of the container...you no longer need to calculate a TOP or LEFT
var target = evt.target;
target.graphics.clear();
target.graphics.beginStroke(_PROPERTIES.node.strokeColor);
target.graphics.beginFill(target.outColor);
target.graphics.drawRect(0, 0, _PROPERTIES.node.x, _PROPERTIES.node.y); //<-- changed
target.graphics.endFill();
stage.update();
};
-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.
For example take this house image below:
The SVG for this House has the following classes:
<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:
// 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:
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.
Any thoughts?
==============================
UPDATE:
It was pointed out that the code for drawing the image/SVG multiple times wasn't included, so here it is:
// 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 LENGTH = 0
So basically this statement isn't working:
let nodeList = document.getElementsByClassName(currentClassName);
To manipulate the house's DOM, the SVG has to be in the DOM. So I've wrapped the SVG in a <div> and hidden the div. I've put it way offscreen, but I could have hidden the div in several other ways.
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. So you have to remove those CSS styles.
Thirdly, you are creating canvas objects, but you are not attaching them to the DOM.
Also you are getting an error because canvasX isn't initialised. Plus CSS lengths must have units. So you need newCanvas.style.left = canvasX + "px" etc.
You were also looking up your elements wrongly. getElementsByClassName(".hose-class") won't find anything. It needed to be 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>
Below is one way to produce your desired result.
The approach below has the <svg> element in the HTML to be used as a template. 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. 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.
The coordinate positions of each house are in an array of space separated x y coordinates. The array also indicates how many houses are to be drawn
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)
All <canvas> CSS is moved to the stylesheet.
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>
I have array of x and y coordinates:
var coordinateX = [10,20,30,40,50,....];
var coordinateY = [10,20,30,40,50,....]:
Those coordinates are circle centers in which I want to show tooltip when hover over circle.
for (var i = 0; i < circles.length; i++) {
circles[i].addEventListener('mousemove', show);
circles[i].addEventListener('mouseout', hide);
}
Tooltip is represented as g element with rectangle and text inside it
<g id="poligon" visibility="hidden" class="element">
<rect width="80" height="20" fill="white"/>
<text class="tooltip" x="4" y="15" dominant-baseline="centered">Tooltip</text>
</g>
var poligon = svgDoc.getElementById("poligon")
function show(evt) {
poligon.setAttributeNS(null, "transform","translate(" + coordinateX[i] + ", " + coordinatey[i] + ")");
poligon.setAttributeNS(null, "visibility", "visible");
textTooltipVrijednost.data = evt.target.getAttributeNS(null, "class");
}
function hide(){
poligon.setAttributeNS(null, "visibility", "hidden");
}
I tried to translate g element using setAttribute and changing x, y values of translate attribute, but I got only last position value. How to do this dynamically for each position?
As I've commented a better idea would be using an html element as a tooltip. This way you won't have problems when the at small sizes of the svg element when the text may become so small that you can't read it.
The main idea is to detect the position of the mouse over the svg element. If the mouse enter a circle the tooltip becomes visible and takes the position of the mouse. Also in this case is displaying the coords of the circle. When the mouse leaves the circle the tooltip's display goes back to none.
I hope it helps.
const SVG_NS = 'http://www.w3.org/2000/svg';
let coordinates = [{cx:10,cy:7,r:5},{cx:10,cy:30,r:5},{cx:50,cy:10,r:5},{cx:33,cy:25,r:5}]
let circles = []
coordinates.forEach(c=>{
circles.push(drawCircle(c, svg))
})
circles.forEach((c,i)=>{
c.addEventListener("mouseenter",(e)=>{
let m = oMousePos(svg, e);
let cx = coordinates[i].cx;
let cy = coordinates[i].cy;
tooltip.style.display = "block"
tooltip.style.left = m.x+"px";
tooltip.style.top = m.y+"px";
tooltip.innerHTML = `${cx}, ${cy}`
})
c.addEventListener("mouseleave",(e)=>{
tooltip.style.display = "none"
})
})
function drawCircle(o, parent) {
var circle = document.createElementNS(SVG_NS, 'circle');
for (var name in o) {
if (o.hasOwnProperty(name)) {
circle.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(circle);
return circle;
}
function oMousePos(svg, evt) {
var ClientRect = svg.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
svg {
border: 1px solid;
width:100vh;
}
#wrap {
position: relative;
}
#tooltip {
position: absolute;
width: 80px;
height: 30px;
background: white;
border: 1px solid;
text-align: center;
line-height: 30px;
top: 0;
left: 0;
display: none;
pointer-events: none;
}
<div id="wrap">
<svg id="svg" viewBox="0 0 60 40"></svg>
<div id="tooltip">0,0</div>
</div>
One solution is to just pass i into your show() function.
for (let i = 0; i < circles.length; i++) { // <-- change "var" to "let"
circles[i].addEventListener('mousemove', function() { show(i) });
circles[i].addEventListener('mouseout', hide);
}
function show(i) {
poligon.setAttributeNS(null, "transform","translate(" + coordinateX[i] + ", " + coordinatey[i] + ")");
poligon.setAttributeNS(null, "visibility", "visible");
textTooltipVrijednost.data = evt.target.getAttributeNS(null, "class");
}
var coordinateX = [10,20,30,40,50];
var coordinateY = [10,20,30,40,50];
var svgDoc = document.querySelector("svg");
var poligon = svgDoc.getElementById("poligon")
for (let i = 0; i < coordinateX.length; i++) {
var circle = document.createElementNS(svgDoc.namespaceURI, "circle");
circle.setAttribute("cx", coordinateX[i]);
circle.setAttribute("cy", coordinateY[i]);
circle.setAttribute("r", "5");
svgDoc.appendChild(circle);
circle.addEventListener('mousemove', function() { show(i); } );
circle.addEventListener('mouseout', hide);
}
function show(i) {
poligon.setAttribute("transform","translate(" + coordinateX[i] + ", " + coordinateY[i] + ")");
poligon.setAttribute("visibility", "visible");
}
function hide(){
poligon.setAttributeNS(null, "visibility", "hidden");
}
<svg width="400" height="400" viewBox="0 0 200 200">
<g id="poligon" visibility="hidden" class="element">
<rect width="80" height="20" fill="white"/>
<text class="tooltip" x="4" y="15" dominant-baseline="centered">Tooltip</text>
</g>
</svg>
I'm trying to add an SVG path that's clipped, with Javascript, but some of the pieces (specifically, the clipPath) aren't working. What am I doing wrong?
Here's a comparison Codepen: working HTML version on the right, failed .js version on the right.
The relevant code:
var fieldShield = function() {
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("style", "height: 100%; width: 100%; position: absolute;");
var clipPath = document.createElement("clipPath");
clipPath.id = "fieldClip";
svg.appendChild(clipPath);
var fill = document.createElementNS("http://www.w3.org/2000/svg", "rect");
fill.id = "fieldFill";
fill.setAttribute("x", "0");
fill.setAttribute("y", "0");
fill.setAttribute("width", "450");
fill.setAttribute("height", "450");
clipPath.appendChild(fill);
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.id = "fieldShield";
path.setAttribute("d", "m395,20c0.910,57.6 0.857,115 0,173-0.833,55.5-3.60,98.8-28.5,133-29.9,40.8-79.8,70.2-144,99.2-64.2-28.9-114-58.4-144-99.2-24.9-33.9-27.6-77.2-28.5-133-0.857-57.6-0.910-115 0-173z");
path.setAttribute("style", "stroke: white; stroke-width: 3;");
svg.appendChild(path);
var use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.className = "divisions";
use.setAttribute("clip-path", "url('#fieldClip')");
use.setAttribute("xlink:href", "#fieldShield");
use.setAttribute("fill", "red");
svg.appendChild(use);
console.log(svg);
document.getElementById("svgHolder").append(svg);
}
The cause of your problem is using deprecated xlink:href attribute on <use>.
Consider using href instead:
var fieldShield = function() {
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("style", "height: 100%; width: 100%; position: absolute;");
var clipPath = document.createElement("clipPath");
clipPath.id = "fieldClip";
svg.appendChild(clipPath);
var fill = document.createElementNS("http://www.w3.org/2000/svg", "rect");
fill.id = "fieldFill";
fill.setAttribute("x", "0");
fill.setAttribute("y", "0");
fill.setAttribute("width", "450");
fill.setAttribute("height", "450");
clipPath.appendChild(fill);
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.id = "fieldShield";
path.setAttribute("d", "m395,20c0.910,57.6 0.857,115 0,173-0.833,55.5-3.60,98.8-28.5,133-29.9,40.8-79.8,70.2-144,99.2-64.2-28.9-114-58.4-144-99.2-24.9-33.9-27.6-77.2-28.5-133-0.857-57.6-0.910-115 0-173z");
path.setAttribute("style", "stroke: white; stroke-width: 3;");
svg.appendChild(path);
var use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.className = "divisions";
use.setAttribute("clip-path", "url('#fieldClip')");
use.setAttribute("href", "#fieldShield");
use.setAttribute("fill", "red");
svg.appendChild(use);
// console.log(svg);
document.getElementById("svgHolder").append(svg);
}
fieldShield();
body {
background: #aaa
}
<div id="svgHolder"></div>
You’re already using Document.createElementNS() to correctly namespace the elements, but you also need to use Element.setAttributeNS() when setting namespaced attributes.
In your example, this affects the following line:
use.setAttribute("xlink:href", "#fieldShield");
The browser treats xlink:href as a single plain attribute, not an attribute with a specified namespace. Instead, you should use the NS version of this function to specify the xlink namespace:
use.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#fieldShield");
I have a page with any number of images with the class pinch-zoom-element I want to replace all of that images with canvas and apply them the PinchZoomCanvas library. Canvas is added to the code, even the library is applied, but the canvas remains at 0px width and height.
var pictures = document.getElementsByClassName('pinch-zoom-element');
for(var i = 0; i < pictures.length; i++){
var imageItem = pictures.item(i);
var canv = document.createElement('canvas');
var foto = imageItem.src;
canv.id = 'canvasIdItem';
canv.style.width = "300px";
canv.style.height = "300px";
imageItem.parentNode.insertBefore(canv, imageItem.nextSibling);
imageItem.parentNode.removeChild(imageItem);
console.log(canv);
var pinchZoom = new PinchZoomCanvas({
canvas: canv,
path: foto,
zoomMax: 2,
doubletap: true,
onZoomEnd: function (zoom, zoomed) {
console.log("---> is zoomed: %s", zoomed);
console.log("---> zoom end at %s", zoom);
},
onZoom: function (zoom) {
console.log("---> zoom is %s", zoom);
}
});
}
How can I get this working? Thanks in advance!
Set your canvas height and width like:
canv.style.width = "300";
canv.style.height = "300";
You can also set it in your css using specific selector:
canvas {
width: 500px;
height: 400px;
}