How to change SVG's path color? - javascript

Update:
Yes, I know there are similar questions on SO, but the solutions don't work either.
I want to change SVG's color, I mean paths's color, not a color "inside" but the path itself.
I first tried with css, it did not work at all. Then with js, and it almost work:
This works, that is, an image is loaded. It's black by default.
<object id = 'test' data="images/icons/040__file_delete.svg" type="image/svg+xml"></object>
I want to change it to green.
<script>
$(function(){
document.getElementById("test").addEventListener("load", function() {
var doc = this.getSVGDocument();
console.log(doc);//works fine
var p = doc.querySelector("path"); //works
p.setAttribute("stroke", "green");
});
})
</script>
The above does "work" but adds a "border" to the path. I also tried with "color", "fillcolor", "fill" - nothing works.
Update II: The SVG's source:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" id="图层_1" x="0px" y="0px" viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<style type="text/css">
.st0{fill:#231815;}
</style>
<g>
<g>
<g>
<g>
<g>
<path class="st0" d="M13,17.5H5c-1.4,0-2.5-1.1-2.5-2.5V3c0-1.4,1.1-2.5,2.5-2.5h3.6c0.4,0,0.8,0.2,1.1,0.4l5.4,5.4 c0.3,0.3,0.4,0.7,0.4,1.1V15C15.5,16.4,14.4,17.5,13,17.5z M5,1.5C4.2,1.5,3.5,2.2,3.5,3v12c0,0.8,0.7,1.5,1.5,1.5h8 c0.8,0,1.5-0.7,1.5-1.5V7.4c0-0.1-0.1-0.3-0.1-0.4L8.9,1.6C8.8,1.6,8.7,1.5,8.6,1.5H5z" fill="green"/>
</g>
<g>
<path class="st0" d="M15,7.5h-4C9.6,7.5,8.5,6.4,8.5,5V1c0-0.3,0.2-0.5,0.5-0.5S9.5,0.7,9.5,1v4c0,0.8,0.7,1.5,1.5,1.5h4 c0.3,0,0.5,0.2,0.5,0.5S15.3,7.5,15,7.5z"/>
</g>
</g>
<g>
<g>
<path class="st0" d="M10.5,13.9c-0.1,0-0.3,0-0.4-0.1l-3-3C7,10.5,7,10.2,7.1,10s0.5-0.2,0.7,0l3,3c0.2,0.2,0.2,0.5,0,0.7 C10.8,13.8,10.6,13.9,10.5,13.9z"/>
</g>
<g>
<path class="st0" d="M7.5,13.9c-0.1,0-0.3,0-0.4-0.1C7,13.5,7,13.2,7.1,13l3-3c0.2-0.2,0.5-0.2,0.7,0s0.2,0.5,0,0.7l-3,3 C7.8,13.8,7.6,13.9,7.5,13.9z"/>
</g>
</g>
</g>
</g>
</g>
</svg>

The fill and/or stroke attribute on the path(s) do not override the CSS styling (here's why).
What you need to do is override the CSS styling itself, this can be done by setting the style property, e.g.
<path style="fill:green" ...>
Or in javascript
element.setAttribute('style', 'fill: green');
In your response to my comment you mentioned you'd address the 'single path' issue, in order to provide an example for that too here's why and how to fix it.
The querySelector method only provides the first element (if any) that matches, you want to use the querySelectorAll method, which will provide a NodeList containing all matching elements.
var paths = doc.querySelectorAll("path"),
i;
for (i = 0; i < paths.length: ++i) {
paths[i].setAttribute('style', 'fill:green');
}
As I mentioned in my comment, the getSVGDocument() method may not exist on all browsers you need to support (I know nothing about your requirements, this is just a heads up), you may be interested in the .contentDocument property as described here

What happens here is that the object is not a simple path, but actually the whole "stroke" has been transformed into a big object. This may happen when you export objects with fancy (or not so fancy) brush settings from various drawing applications. You can also get the same result with the Outline feature in Adobe Illustrator, IIRC.
To avoid this, edit the original object in its original illustration software and try the following:
Use a simple stroke, and no brush. This may work.
Use no stroke in the original editor and apply it with JS or CSS in the SVG.

Base on accepted answer, I created sample to click button and change color path.
Important thing:
I need host HTML file to webserver (IIS) to run it. If not it a.contentDocument always return null.
I share for whom concerned.
var svgDoc;
function changeColor() {
svgDoc = a.contentDocument;
// get the inner element by id
var paths = svgDoc.querySelectorAll("path");
// add behaviour
for (i = 0; i < paths.length; i++) {
paths[i].setAttribute('style', 'fill:pink');
}
}
var a = document.getElementById("alphasvg");
// It's important to add an load event listener to the object,
// as it will load the svg doc asynchronously
a.addEventListener("load", function () {
// get the inner DOM of alpha.svg
svgDoc = a.contentDocument;
// get the inner element by id
var paths = svgDoc.querySelectorAll("path");
// add behaviour
for (i = 0; i < paths.length; i++) {
paths[i].setAttribute('style', 'fill:green');
}
}, false);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<object data="test.svg" type="image/svg+xml" id="alphasvg"></object>
<button onclick="changeColor()">Change Color</button>
<script>
</script>
</body>
</html>

If i understand you want to color the stroke of the path
is pretty simple:
add stroke property to your SVG path then apply your color(rgb or hex or hsl)
<math fill="none" stroke: rgb(0,0,0) d="M50 30 L50 -10 C50 -10 90 -10 90 30 Z" />

Related

How can you dynamically import, modify, and export vector files?

I want to be able to import SVG files from the file system, layer them, change their fill colors, and then export them as a single vector file.
The solution to this problem was simply to import the SVG data as a string, swap out constants for various properties, and then write the new string to the file system.
For example, the following svg displays an orange circle:
<svg viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg">
<circle cx="500" cy="500" r="500" fill="orange"/>
</svg>
If you wanted to be able to dynamically change the color of the circle, you could just replace the "orange" text with a placeholder name, such as "{FILL_COLOR}". Now, the SVG should look like this:
<svg viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg">
<circle cx="500" cy="500" r="500" fill="{FILL_COLOR}"/>
</svg>
In your code, you can load the file from the file system as a string and then make changes to it as needed; you could then write the result to the file system. This solution also works perfectly on frontends. Instead of loading the SVG from the file system, simply have it as a hard-coded constant. Once you make the changes to the SVG, you can use the result however you want.
function getCircle(color) {
const svg = `<svg viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg"><circle cx="500" cy="500" r="500" fill="{FILL_COLOR}"/></svg>`;
return svg.replace('{FILL_COLOR}', color);
}
The best source to refer would be their official docs: https://svgjs.dev/docs/3.0/getting-started/
The next in the line source would be stack-overflow's search by svg.js tag: https://stackoverflow.com/questions/tagged/svg.js
Now a quick intro from my side.
SVG.JS lets you work very extensively with SVGs. You can import an SVG into your code and start manipulating it. You can change color, size and even add events and animations.
However before you start working with SVGs, you should make sure that the SVG is compressed using some online tool like this: https://jakearchibald.github.io/svgomg/ and after compressions you should edit your SVG file by adding class selectors within your SVG for paths (regions) so that you can select these paths from your JS code. Example of SVG from one of the projects where I used this technique (trimmed for the sake of simplicity):
<svg id="svg-map" class="svg-map" version="1" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 363 624">
<path class="svg-path-1" fill="#FFFFFF" stroke="#FFFFFF" stroke-miterlimit="10" d="M114 ... 24 42z"/>
<path class="svg-path-2" fill="#FFFFFF" stroke="#FFFFFF" stroke-miterlimit="10" d="M114 ... 24 42z"/>
<path class="svg-path-3" fill="#FFFFFF" stroke="#FFFFFF" stroke-miterlimit="10" d="M114 ... 24 42z"/>
</svg>
You can save your SVG in some folder and render it using the image tag on your HTML page.
Install in your project (Example using NodeJS):
npm install svg.js
Then import this library into your JS code like this:
import { SVG } from '#svgdotjs/svg.js'
And now you can select the SVG from your page using the code like this:
let mapContainer = SVG.select('.some-svg-class');
let myMapObject = mapContainer.first();
And set the viewport:
myMapObject.viewbox(0, 0, 500, 700);
Now you can loop across all the paths (regions) of the map and perform some manipulation like this:
let svgRegionPaths = myMapObject.select('path');
svgRegionPaths.each(function(i, children) {
this.opacity(0.7);
});
This is just an example. You can do a lot more with this library. I haven't shown the example of selecting paths by class selectors but I hope you get the idea.
I was able to pair this with React and make it work more in line with my coding style and requirements for the application.

Animate SVG path from <use> tag

I'm trying to animate an svg path using this technique by Jake Archibald
The technique works well when the svg code is pasted in my document, but fails when I'm referencing the svg from my "defs.svg" file.
My defs.svg looks like this:
<svg xmlns="http://www.w3.org/2000/svg">
<symbol viewBox="0 0 48 50.6" id="icon-result" fill="#FFF">
<path d="M19 49.4c-2 1.7-5.1 1.5-6.9-.5l-11-12.8c-1.7-2-1.5-5.1.5-6.9 2-1.7 5.1-1.5 6.9.5l11 12.8c1.8 2.1 1.6 5.2-.5 6.9z"/>
<path d="M13.6 49.4c-2.2-1.5-2.9-4.5-1.4-6.8L39 2.2C40.5 0 43.5-.7 45.8.8c2.2 1.5 2.9 4.5 1.4 6.8L20.3 48.1c-1.4 2.2-4.5 2.8-6.7 1.3z"/>
</symbol>
</svg>
In my code I do the following:
<svg class="Dashboard__nav-icon">
<use xlink:href="/svg/defs.svg#icon-result" />
</svg>
When trying to animate the code this way I get null when trying to get the path by using Jake Archibalds technique linked above. Is there a way to get the path while still using a separate defs.svg file and the <use> tag?
You won't be able to get the DOM path element via the second <svg> element. That is because elements referenced via a <use> do not appear in the DOM tree where they are used. So the following will not work:
var path = document.querySelector('.Dashboard__nav-icon path');
The symbols elements are not visible in the main document's DOM tree.
However you would be able to reference the paths directly via their definition. But for that to work you would need to inline the defs.svg file.
So, if the defs.svg is in the same file, then you would be able to use:
var path = document.querySelector('#icon-result path');

Intercalate a <g> tag in SVG thru jquery

I try to intercalate a element in my SVG to add a layer in my SVG DOM. The SVG is embeded in a HTML5.
When I do this, the moved element disapears from the HTML. what's wrong?
To see it live, have a look here: http://bl.ocks.org/daohodac/raw/6db306dcb5d66d913f5c/ and click the button to perform the script
here is the initial SVG with the polygon visible:
<svg ...attributes...>
<g id="zoom_anim" ...attributes...>
<polygon ...attributes...></polygon>
</g>
</svg>
here is what I have after the script (I can see it in chrome inspector) with the polygon invisible
<svg ...attributes...>
<g id="zoom_anim_parent_bbsmashed">
<g id="zoom_anim" ...attributes...>
<polygon ...attributes...></polygon>
</g>
</svg>
and here is the script
<script>
var intercalate = function() {
var zoomParentId = "zoom_anim_parent_bbsmashed";
var gId = "#zoom_anim";
$(gId).parent().append($("<g id='"+zoomParentId+"'>"));
var that = $(gId).detach().appendTo("#"+zoomParentId);
};
</script>
Be careful using jQuery with SVG. SVG nodes are not the same as XHTML nodes, and some of the jQuery API doesn't work correctly with SVG. To create an SVG node, you should use document.createElementNS('http://www.w3.org/2000/svg', 'g'). You could also use an SVG library like d3 or snap; both offer a comprehensive API to work with SVG nodes.

Can't read SVG element contents in mobile Safari with Javascript

To reuse certain SVG objects I have symbols defined in an SVG element which is written out at the top of my DOM. To display an SVG I can do:
<svg><use xlink:href="#symbol-identifier" /></svg>
To animate some SVG's I use Snap.svg, but I can't seem to create a Snap object from an SVG referenced by an xlink. To work around this I want to inject the SVG contents (already present in the DOM) at runtime into the SVG currently using the xlink.
To do this I need to get the contents (innerHTML?) of a symbol. This works in every browser i tested, except for mobile safari.
Below a simple test setup (without the symbol-wrap, but works the same):
HTML:
<div id="outer" style="width: 100px; height: 100px;">
<svg id="inner" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="5" fill="#ffffff" />
</svg>
</div>
Javascript:
var outerElement = document.getElementById('outer');
var innerElement = document.getElementById('inner');
outerElement.innerHTML // returns <svg id=".. etc..
innerElement.toString() // returns [object SVGSymbolElement]
innerElement.getAttribute('viewBox') // returns 0 0 100 100
innerElement.innerHTML // returns undefined (in Mobile Safari)
// Everywhere else the content <circle .. etc .. is returned.
Why is this? And is there another way of getting the SVG contents as a string, without regexping away all the unwanted HTML around the outerElement.
XMLSerializer can serialize elements.
var string = XMLSerializer().serializeToString(innerElement);
should work for you.

For some reason the SVG does not update and render the markers

I am making a SVG with snapsvg and generating a path with
paper.path("M" + e.pageX + " " + e.pageY + "L20 20");
I have a hover event and the class "selected" gets added to the path
.selected class references this marker
<defs>
<marker id="knob" viewbox="0 0 10 10" refx="0" refy="5" markerunits="strokeWidth" markerwidth="4" markerheight="3" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z"></path>
</marker>
</defs>
I am linking the markers to the class like this
<style>
.selected{
stroke: #006600;
marker-mid:url(#knob);
marker-end:url(#knob);
marker-start:url(#knob);
fill: #00cc00;
}
</style>
For some reason the SVG does not update and render the markers. However when I copy the SVG from the the page and paste it back in the page using chrome developer tools it updates and shows the markers.
Does any one know why it would be doing this?
I have made a JSFiddle of with a snippet of random crap I was making http://jsfiddle.net/XTD4x/
If you click in the Result window it will start drawing a line. Click to add nodes. double click to stop drawing. hover to add the class selected
The problem is you put the <marker> element (and, strictly speaking, the <style> element as well) in the wrong namespace. It needs to go into the SVG namespace, but jQuery's functions like .prepend() put it into the HTML namespace (as they rely on .innerHTML, I guess). Therefore, use either DOM methods like .createElementNS(), or use DOMParser, like
$("svg defs").prepend(
(new DOMParser()).parseFromString(
'<svg xmlns="http://www.w3.org/2000/svg"><style type="text/css"> .selected{ stroke: #006600; marker-mid:url(#knob); marker-end:url(#knob); marker-start:url(#knob); fill: #00cc00; }</style><marker id="knob" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="strokeWidth" markerWidth="4" markerHeight="3" orient="auto"><circle cx="6" cy="6" r="5" /></marker></svg>',
"image/svg+xml"
).documentElement.childNodes
);
Notice I wrapped the elements into a complete SVG document so that I can parse it.
Try the modified fiddle.
Second answer: Define the marker outside the SVG generated by snapsvg and put the CSS in an external stylesheet or another <style> element outside the snapsvg SVG. No JavaScript required for this, no need to parse and insert anything.
See another fiddle.

Categories

Resources