JavaScript render SVG from text - javascript

I have an svg element as plain text
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>
How can I render this on a HTML page by inserting it with JavaScript/jquery without recreating every node with
document.createElementNS("http://www.w3.org/2000/svg", "svg")
(because otherwise it will not be rendered)

Use innerHTML to insert it.
document.documentElement.innerHTML = '<svg height="100" width="100">\n <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />\n</svg>'

try the following code.
var svg = '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="30px" height="30px" viewBox="0 0 30 30" enable-background="new 0 0 30 30" xml:space="preserve">'+
'<path fill="#156BB1" d="M22.906,10.438c0,4.367-6.281,14.312-7.906,17.031c-1.719-2.75-7.906-12.665-7.906-17.031S10.634,2.531,15,2.531S22.906,6.071,22.906,10.438z"/>'+
'<circle fill="#FFFFFF" cx="15" cy="10.677" r="3.291"/></svg>';
var mysvg = new Image();
mysvg.src = 'data:image/svg+xml,' + escape(svg);
and then use mysvgvariable to place it where ever you want within your html.

do this with JQuery:
var str = '<svg height="100" width="100"> <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" /></svg>'
$("body").append(str)
working code here

Related

Is it possible to programatically control fill color of individual cells of svg pattern?

<svg viewBox="200 190 500 500" id="example">
<defs>
<pattern id="patt" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<g stroke="black" strokeWidth="0.5" >
<path id='a' fill='green' d="M0,0.054V20h21V0.054H0z M15.422,18.129l-5.264-2.768l-5.265,2.768l1.006-5.863L1.64,8.114l5.887-0.855 l2.632-5.334l2.633,5.334l5.885,0.855l-4.258,4.152L15.422,18.129z"/>
</g>
</pattern>
</defs>
<g fill="url(#patt)" stroke="orange" >
<circle cx="450" cy="300" r="100"/>
</g>
</svg>
Requirement is to create a pattern of svg in which fill color of each element in the pattern has to be manipulated.
Only when you create all colored shapes inside the <pattern> yourself:
<svg-pattern colors="green,red,blue,yellow"></svg-pattern>
<svg-pattern colors="purple,hotpink,hotpink,purple"></svg-pattern>
<script>
customElements.define("svg-pattern", class extends HTMLElement {
connectedCallback() {
let colors = this.getAttribute("colors").split(",");
let star = `v20h21v-20h-21zm15.4 18-5.3-2.8-5.3 2.8 1-5.9-4.3-4.2 5.9-.9 2.6-5.3 2.6 5.3 5.9.9-4.3 4.2 1 6z`;
let id = "unique" + Math.random();
this.innerHTML = `<svg width="180" height="180" style="display:inline-block" viewBox="0 0 200 200">
<defs>
<pattern id="${id}" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse">
<g stroke="black" strokeWidth="0.5" >
<path fill='${colors[0]}' d="m0 0 ${star}"/>
<path fill='${colors[1]}' d="m20 0 ${star}"/>
<path fill='${colors[2]}' d="m0 20 ${star}"/>
<path fill='${colors[3]}' d="m20 20 ${star}"/>
</g>
</pattern>
</defs>
<g fill="url(#${id})">
<circle cx="100" cy="100" r="100"/>
</g>
</svg>`;
}
});
</script>

How to reverse the order of svg elements

I am working with a svg element which is following
<svg class="layer1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150">
<rect class="bg" id="bg" width="150" height="150" fill="#e6e6e6"></rect>
<circle class="circ0" id="circ0" cx="75" cy="75" r="72" fill="none" stroke="blue" stroke-linecap="round" stroke-linejoin="round"></circle>
<circle class="circ1" id="circ1" cx="75" cy="75" r="69" fill="none" stroke="green" stroke-linecap="round" stroke-linejoin="round"></circle>
<circle class="circ2" id="circ2" cx="75" cy="75" r="66" fill="none" stroke="red" stroke-linecap="round" stroke-linejoin="round"></circle>
<script href="index.js"></script>
</svg>
I want to reverse the order of these circles with javascript, which I am currently doing by this way
const svg = document.querySelector("svg");
var x = document.querySelectorAll("[class^='circ']");
var bucket = [];
x.forEach((a, i) => {
bucket.push(a)
});
bucket.reverse();
x.forEach(
(a, i) => a.parentNode.removeChild(a)
);
bucket.forEach(
(a, i) => {
a.setAttribute("class", 'circ' + [i]);
a.setAttribute("id", "circ" + [i]);
svg.appendChild(a);
}
)
<svg class="layer1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150">
<rect class="bg" id="bg" width="150" height="150" fill="#e6e6e6"></rect>
<circle class="circ0" id="circ0" cx="75" cy="75" r="72" fill="none" stroke="blue" stroke-linecap="round" stroke-linejoin="round"></circle>
<circle class="circ1" id="circ1" cx="75" cy="75" r="69" fill="none" stroke="green" stroke-linecap="round" stroke-linejoin="round"></circle>
<circle class="circ2" id="circ2" cx="75" cy="75" r="66" fill="none" stroke="red" stroke-linecap="round" stroke-linejoin="round"></circle>
<script href="index.js"></script>
</svg>
It gives me this
Is there a better way of doing this?
append(child) by itself moves DOM Nodes. So your code can be simplified.
But for complexer SVG you probably want to swap DOM positions, because there could be other Elements in between you don't want to affect.
Hold CTRL key to see what happens with append
Click to see the swapping version,
a matter of processing an Array and swapping the first with the last element.
Note: append was not available in Internet Explorer, that is why you see most posts using appendChild.
Modern browsers have loads more DOM goodies: replaceWith after , before etc.
<svg viewBox="0 0 10 10" style="height:200px">
<style>
text { font-size: 2px }
[y="3"]{ fill:yellow }
.first { stroke: black; stroke-width: 0.5 }
</style>
<rect class="bg" id="bg" width="10" height="10" fill="grey"></rect>
<circle class="first" id="c0" cx="2" cy="5" r="2" fill="red" />
<text x="0" y="3">R</text>
<circle class="second" id="c1" cx="4" cy="5" r="3" fill="green" />
<text x="1" y="3">G</text>
<circle class="last" id="c2" cx="6" cy="5" r="4" fill="blue" />
<text x="2" y="3">B</text>
<text x="1" y="6">Click Me!</text>
</svg>
<script>
let svg = document.querySelector("svg");
function append() {
[...svg.querySelectorAll("circle")]
.reverse().forEach((c, i) => {
c.parentNode.append(c);
c.setAttribute("class", c.id = 'c' + i);
});
}
function swap() {
function swapElements(e1, e2) {
let {id,previousSibling,className:{baseVal:c2}} = e2;
e1.after(e2); // put e2 after e1
e2.id = e1.id; e2.setAttribute("class", e1.getAttribute("class"));
previousSibling.after(e1); // put e1 after where e2 WAS
e1.id = id; e1.setAttribute("class", c2);
}
let circles = [...svg.querySelectorAll("circle")];
while (circles.length) {
let c1 = circles.shift();
if (circles.length) swapElements(c1, circles.pop())
}
}
svg.onclick = (e) => (e.ctrlKey && append()) || swap();
</script>

Real group transparency in three.js

Like this question, I want to change opacity of a group. But the accepted answer in that question is not completely correct. Changing opacity of a group is not equal to changing opacity of it's parts. I can show this with this small svg example:
Group opacity (Good):
<svg width="200" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.5">
<g>
<rect x="20" y="30" width="10" height="50" fill="brown"/>
<g style="fill: #0b0">
<circle cx="20" cy="30" r="10"/>
<circle cx="30" cy="30" r="10"/>
<circle cx="25" cy="20" r="10"/>
</g>
</g>
</g>
</svg>
Individual opacity (Bad):
<svg width="200" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g>
<g>
<rect x="20" y="30" width="10" height="50" fill="brown" opacity="0.5"/>
<g style="fill: #0b0">
<circle cx="20" cy="30" r="10" opacity="0.5"/>
<circle cx="30" cy="30" r="10" opacity="0.5"/>
<circle cx="25" cy="20" r="10" opacity="0.5"/>
</g>
</g>
</g>
</svg>
How to do something like this in three.js?

Add filter to SVG dynamically

I have the following SVG image inline on a webpage:
const svg = document.getElementById('svg');
const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
filter.setAttribute('id', 'image');
filter.setAttribute('x', '0%');
filter.setAttribute('y', '0%');
filter.setAttribute('width', '100%');
filter.setAttribute('height', '100%');
const feImage = document.createElementNS('http://www.w3.org/2000/svg', 'feImage');
feImage.setAttribute('xlink:href', 'http://lorempixel.com/100/100/');
filter.appendChild(feImage);
svg.querySelector('defs').appendChild(filter);
svg.querySelector('circle').setAttribute('filter', 'url(#image)');
<svg width="200" height="200" viewBox="0 0 200 200" id="svg">
<defs></defs>
<circle cx="100" cy="100" r="50" fill="none" stroke-width="2" stroke="gray"></circle>
</svg>
Now I would like to add an image filter to this svg dynamically and then apply the filter to the circle. But what happens is the circle becomes invisible the filter does not work.
When I add the filter to the svg hardcoded everything works just fine:
<svg width="200" height="200" viewBox="0 0 200 200" id="svg">
<defs>
<filter id="image" x="0%" y="0%" width="100%" height="100%">
<feImage xlink:href="http://lorempixel.com/100/100/"></feImage>
</filter>
</defs>
<circle cx="100" cy="100" r="50" fill="none" stroke-width="2" stroke="gray" filter="url(#image)"></circle>
</svg>
My question is why doesn't this work?
You need to use: document.createElementNS(…) for svg elements and in this case setAttributeNS(…) as well;
For instance change:
const filter = document.createElement('filter');
to
const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
Additionally change:
feImage.setAttribute('xlink:href', 'http://lorempixel.com/100/100/');
to
feImage.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', 'http://lorempixel.com/100/100/');
fiddle

Can I add a mask to an svg-element without using an id?

I want to assign a svg-mask to a svg-image. I can make this work using an id on the mask like this:
<svg id="svg1" width="5cm" height="5cm" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<mask id="mask">
<circle cx="100" cy="100" r="100" fill="white"></circle>
</mask>
</defs>
<rect x="0" y="0" width="200" height="200" fill="red" mask="url(#mask)"></rect>
</svg>
However I want to load this svg multiple times, with a different id in the svg-tag. Therefore I will generate duplicates of the '#mask'-id. Using multiple id's is invalid code. So I want to use a class to refer to the appropriate mask. That means I cannot use the mask=url()-technique.
<svg id="svg2" width="5cm" height="5cm" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<mask class="mask">
<circle cx="100" cy="100" r="100" fill="white"></circle>
</mask>
</defs>
<rect x="0" y="0" width="200" height="200" fill="red" mask="url(can't use this)"></rect>
</svg>
Is there a way I can apply a mask to the rect element if the mask has a class instead of id? Maybe using javaScript or some other way I didn't think of.
The full story/context:
I am actually making an svg image slider-module for Joomla with php. This php generates a module containing javascript, css and an svg. I use the javascript to animate the mask.
I do actually have it working with unique id's. I was just wondering if there is a way to assign a mask to an element without referring to id's. I may want to do this because my code is getting a bit more confusing to read, because I have to use some php in my javascript/svg and css for each unique id.
No. You can only reference masks via an id. You cannot reference SVG masks any other way.
According to your description I understand you have a identical grafical entity you want to mask with different forms, multiple times. Write that down DRY:
<!-- start with an invisible svg that only contains mask definitions -->
<svg width="0" height="0"
xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- first, you have a circular mask -->
<mask id="circle-mask">
<circle cx="100" cy="100" r="80" fill="white" />
</mask>
<!-- then, you have a different mask, lets say a diamond -->
<mask id="diamond-mask">
<polygon points="100,20 180,100 100,180 20,100" fill="white" />
</mask>
</defs>
</svg>
<!-- further into your document, you want to mask a rectangle -->
<svg id="svg1" width="5cm" height="5cm" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<!-- reference the circle mask -->
<rect x="0" y="0" width="200" height="200" fill="red" mask="url(#circle-mask)" />
</svg>
<!-- with the circle again, as often as you want, nothing changes -->
<svg id="svg2" width="5cm" height="5cm" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<!-- the mask is the same, so no difference to above -->
<rect x="0" y="0" width="200" height="200" fill="red" mask="url(#circle-mask)" />
</svg>
<!-- and now with the diamond; that one is different -->
<svg id="svg3" width="5cm" height="5cm" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<!-- if the mask changes, you need to change the reference -->
<rect x="0" y="0" width="200" height="200" fill="red" mask="url(#diamond-mask)" />
</svg>
You could also reference the masks in a stylesheet and give your referencing elements a class according to the mask shape:
.masked.circular rect {
mask: url(#circle-mask);
}
.masked.diamond rect {
mask: url(#diamond-mask);
}
<svg width="0" height="0"
xmlns="http://www.w3.org/2000/svg">
<defs>
<mask id="circle-mask">
<circle cx="100" cy="100" r="80" fill="white" />
</mask>
<mask id="diamond-mask">
<polygon points="100,20 180,100 100,180 20,100" fill="white" />
</mask>
</defs>
</svg>
<svg id="svg1" class="masked circular" width="5cm" height="5cm" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="200" height="200" fill="red" />
</svg>
<svg id="svg2" class="masked circular" width="5cm" height="5cm" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="200" height="200" fill="red" />
</svg>
<svg id="svg1" class="masked diamond" width="5cm" height="5cm" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="200" height="200" fill="red" />
</svg>

Categories