I am making use of canvg to convert svg present in div into canvas(upto this it's working fine) and then copying the innerHTML of div to another div, but it's not working. Canvas is coming but nothing will be present in that canvas.
Thanks in advance
<div id="k">
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
Sorry, your browser does not support inline SVG.
</svg>
</div>
<div id="kk">
<p>Watch me</p>
</div>
var svgTag = document.querySelectorAll('#k svg');
svgTag = svgTag[0];
var c = document.createElement('canvas');
c.width = svgTag.clientWidth;
c.height = svgTag.clientHeight;
svgTag.parentNode.insertBefore(c, svgTag);
svgTag.parentNode.removeChild(svgTag);
var div = document.createElement('div');
div.appendChild(svgTag);
canvg(c, div.innerHTML);
setTimeout(function(){
var data = $("#k").html();
$("#kk").append($(''+data+''));
},5000);
JSFiddle
The content of a canvas element is held as binary data, much like the content of an image element. Canvas elements do not have an innerHTML text property that can be used to recreate the canvas.
The following example shows a method of cloning a canvas element using standard canvas 2D methods:
function canvasClone(c1) {
var c2 = document.createElement('canvas');
c2.width=c1.width;
c2.height=c1.height;
c2.getContext("2d").drawImage(c1, 0,0);
return c2;
}
You can demonstrate it by including the function in the fiddle and changing the timeout processing to:
setTimeout(function(){
kk.appendChild(canvasClone(c));
},5000);
Feel free to rewrite the timeout code in JQuery if you prefer.
Related
I am trying to build a browser-based SVG rasterizer. The SVG files can contain javascript code that affects the output (for example, randomly changes colors of elements) and use libraries (for example chroma.js) to do that.
The process I am trying to implement is:
load the SVG file
load libraries linked in the SVG file
execute the javascript included inside script tags
display on a canvas
save the canvas as PNG
This is necessary because appending the SVG to the HTML element does not run the JS included inside SVG.
Everything works well - except the loading of external libraries (point 2). The snippet that does that is as follows
$.get('/some_svg_file.svg', function(d) {
// this will be replaced with file drag/drop later
var getLibs = Array.from(d.querySelectorAll('script'))
.filter(p => p.getAttribute('xlink:href'))
.map(p => {
return axios.get(p.getAttribute('xlink:href'))
})
// only run embedded js after libraries have been downloaded
Promise.all(getLibs).then((values) => {
values.forEach(d => {
try {
eval(d.data);
} catch (e) {
console.log(e)
}
});
Array.from(d.querySelectorAll('script'))
.forEach(p => {
if (p.textContent.length) {
try {
eval(p.textContent)
} catch (e) {
console.log(e)
}
}
})
// code that writes to canvas etc goes here
})
})
So, if I put link chroma.js in the html file header, everything works ok. If I download it and eval it, the scripts inside the SVG file fail with ReferenceError: chroma is not defined
Another attempt I did was using the script tag technique like so:
$.get('/foo_with_js.svg', function(d) {
var getLibs = Array.from(d.querySelectorAll('script'))
.filter(p => p.getAttribute('xlink:href'))
.map(p => {
var s = document.createElement('script');
s.src = p.getAttribute('xlink:href');
s.type = "text/javascript";
s.async = false;
document.querySelector('head').appendChild(s);
})
// load scripts and save to canvas
})
which also failed in the same way (despite chroma.js being neatly included in the head section.
So how could I get this to work - so that I can load SVG, append it to HTML and run the scripts inside it without having to pre-link all the possible scripts in the HTML?
Ah - and if you're asking why not use some other conversion process, the answer is "lack of consistent support for SVG filters"
If you can make this work with any JavaScript library embedded in SVG is a good question, but here I have an example where I use chroma.js like you suggested.
A reference to the SVG document is added to the data attribute of <object>. Here it is static, but I guess it could be dynamically updated. Now the SVG is doing its thing, like setting a color on the <rect> and animating the cy attribute of the <circle>. At any point I can grab the contentDocument (contentDocument.documentElement.outerHTML) of <object>, that is the SVG document at a particular point. If values in the document is changing this will affect the contentDocument. Now I can take the string (outerHTML) and turn that into a data URI. The data URI can be set as src on an image object and the image can be rendered in the <canvas> element. In the outerHTML you can see that there are also references to the JavaScript library and the JavaScript code that I wrote, but this is not executed in the image when rendered into <canvas>.
SVG document:
<?xml version="1.0"?>
<svg viewBox="0 0 200 200" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="drop-shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
<feoffset in="blur" dx="4" dy="4" result="offsetBlur"/>
<feMerge>
<feMergeNode in="offsetBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<script href="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.2/chroma.min.js"/>
<rect x="0" y="0" width="200" height="200" />
<circle cx="40" cy="40" r="20" fill="blue" style="filter: url(#drop-shadow);"/>
<script>
// <![CDATA[
var cy = 40;
var speed = 5;
document.querySelector('rect').style.fill = chroma('hotpink');
setInterval(function(){
if(cy > 160 || cy < 40) speed *= -1;
cy += speed;
document.querySelector('circle').attributes['cy'].value = cy;
}, 500);
// ]]>
</script>
</svg>
HTML document:
<!DOCTYPE html>
<html>
<head>
<title>SVG</title>
<script type="text/javascript">
var object, canvas, img, loop;
document.addEventListener('DOMContentLoaded', e => {
object = document.querySelector('object');
canvas = document.querySelector('canvas');
img = new Image();
img.addEventListener('load', e => {
canvas.getContext('2d').drawImage(e.target, 0, 0);
});
object.addEventListener('load', e => {
loop = setInterval(function(){
var svg64 = btoa(e.target.contentDocument.documentElement.outerHTML);
var b64Start = 'data:image/svg+xml;base64,';
img.src = b64Start + svg64;
}, 500);
});
});
</script>
</head>
<body>
<object type="image/svg+xml" width="200" height="200" data="test.svg"></object>
<canvas width="200" height="200"></canvas>
</body>
</html>
These two documents need to run from a server, so not directly from the filesystem.
From what I have tested the SVG animations and CSS animations are not working in this setup. Only the "animations" in JavaScript where you manipulate the DOM is working.
Trying to make an svg rectangle that moves when button pushed. Right now I just want the x to be modified by a function.
function modX()
{
document.getElementById("rectangle").transform = 'translate(295 115)';
}
var x = 20;
var y = 20;
modX();
<svg width="1000" height="1000" >
<rect id="rectangle" x="0" y="20" width="100" height="100"
style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)"></rect>
</svg>
I'm fairly new to code so please avoid css or jquery.
you can change its x by using javaScript document.getElementById("rectangle").setAttribute('x', X_value)
set the X_value to the value you want it to change.
I'm trying to append a path element to some inline svg using javascript but I'm getting the error: "Uncaught NotFoundError: An attempt was made to reference a Node in a context where it does not exist"
Can anyone explain why this javascript is not working with my HTML?
<body>
<svg height='500' width='500'></svg>
<script>
var svg = document.getElementsByTagName("svg")[0];
var pathElement = svg.appendChild("path");
</script>
</body>
appendChild takes an element and not a string so what you need is something like this...
var svg = document.getElementsByTagName("svg")[0];
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
// Use path.setAttribute("<attr>", "<value>"); to set any attributes you want
var pathElement = svg.appendChild(path);
Working example for anyone passing by who wants to see it in action:
http://jsfiddle.net/huvd0fju/
HTML
<svg id="mysvg" width="100" height="100">
<circle class="c" cx="50" cy="50" r="40" fill="#fc0" />
</svg>
JS
var mysvg = document.getElementById("mysvg");
//uncomment for example of changing existing svg element attributes
/*
var mycircle = document.getElementsByClassName("c")[0];
mycircle.setAttributeNS(null,"fill","#96f");
*/
var svgns = "http://www.w3.org/2000/svg";
var shape = document.createElementNS(svgns, "rect");
shape.setAttributeNS(null,"width",50);
shape.setAttributeNS(null,"height",80);
shape.setAttributeNS(null,"fill","#f00");
mysvg.appendChild(shape);
If I have inline SVG, including an element which has been scaled...
<g transform="scale(sX,sY)">
<rect id="myElement" x="107" y="32" width="245" height="31" stroke="#C6C3C6" stroke-width="1px" />
</g>
... or which is in a <svg> element with a viewBox attribute:
<svg viewBox="20 20 5000 1230">
<g transform="scale(sX,sY)">
<rect id="myElement" x="107" y="32" width="245" height="31" stroke="#C6C3C6" stroke-width="1px" />
</g>
<svg>
... how can I programmatically find the new scaled width in pixels of myElement - without manually detecting the scaling and doing the math? So far myElement.getBBox().width returns 245 without accounting for the scaling.
please check this fiddle http://jsfiddle.net/T723E/. Click on the rectangles and note the firebug console.
Here i have hard coded a number .3 which is 300px width of div containing svg node / 1000 user units.
After seeing the bounty, this is the function i have return to get the scaled width without much (with no maths i'm not sure.)maths.Use matrix.d for getting scaled height
var svg = document.getElementById('svg');
function getTransformedWidth(el){
var matrix = el.getTransformToElement(svg);
return matrix.a*el.width.animVal.value;
}
var ele = document.getElementById('myElement_with_scale')
console.log("scale width----", getTransformedWidth(ele))
Please look this fiddle for complete code http://jsfiddle.net/b4KXr/
Have you investigated the parameter you can use with getBBox()?
http://raphaeljs.com/reference.html#Element.getBBox
How do I create an SVG anchor through JavaScript? Please see relevant section and an example from spec. How do I convert this example to JavaScript (basically, how to dynamically generate the container element a so that when I click the ellipse, it navigates away.
<?xml version="1.0"?>
<svg width="5cm" height="3cm" viewBox="0 0 5 3" version="1.2" baseProfile="tiny"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Example 17_01</title>
<desc>A simple link on an ellipse.</desc>
<rect x=".01" y=".01" width="4.98" height="2.98"
fill="none" stroke="blue" stroke-width=".03"/>
<a xlink:href="http://www.w3.org/">
<ellipse cx="2.5" cy="1.5" rx="2" ry="1"
fill="red" />
</a>
</svg>
This is just basic DOM:
var xlinkNS="http://www.w3.org/1999/xlink", svgNS="http://www.w3.org/2000/svg";
var a = document.createElementNS(svgNS, "a");
a.setAttributeNS(xlinkNS,"href","http://www.w3.org/");
var ellipse = document.createElementNS(svgNS, "ellipse");
ellipse.setAttributeNS(null,"cx","2.5");
ellipse.setAttributeNS(null,"cy","1.5");
ellipse.setAttributeNS(null,"rx","2");
ellipse.setAttributeNS(null,"ry","1");
ellipse.setAttributeNS(null,"fill","red");
a.appendChild(ellipse);
document.documentElement.appendChild(a);
Using my function below, it's as easy as this:
// Find the first SVG element
var svg = document.getElementsByTagName('svg')[0];
var a = createOn(svg,'a',{'xlink:href':'http://www.w3.org/'});
createOn(a,'ellipse',{cx:2.5,cy:1.5,rx:1,ry:1,fill:'red'});
function createOn(root,name,attrs,text){
var doc = root.ownerDocument,
svg = root.ownerSVGElement || root; // In case the root _is_ the <svg>
var svgNS = svg.getAttribute('xmlns');
var el = doc.createElementNS(svgNS,name);
for (var attr in attrs){
if (!attrs.hasOwnProperty(attr)) continue;
var parts = attr.split(':');
if (parts[1]) el.setAttributeNS(
svg.getAttribute('xmlns:'+parts[0]),parts[1],attrs[attr]
);
else el.setAttributeNS(null,attr,attrs[attr]);
}
if (text) el.appendChild(document.createTextNode(text));
return root.appendChild(el);
}
If you already have the ellipse and want to wrap it, then create the 'a' element and:
// Get a reference to the ellipse however you like
var ellipse = document.getElementsByTagName('ellipse')[0];
// Put the anchor node immediately preceding the ellipse
ellipse.parentNode.insertBefore(a,ellipse);
// Move the ellipse to be a child of the anchor
a.appendChild(ellipse);