Javascript memory leaks when using canvas and blobs - javascript
I'm writing an SVG editor. I have placed a kind of 'Magic Eye' on the page where the user can see the entire SVG draw and the zoomed area around the mouse cursor. Of course the problem is Memory usage and fast rendering. For this reason, step of modification or zooming the software create a reduced Image of the svg draw and it will use it for the Magic Eye rendering. The result is very nice but.... I am facing a problem, I discovered that the garbage collector doesn't free the images created and also the blobs. So after a few time I have the memory filled with Images. This is the routine I wrote for this job:
var RenderPosition = function(obj) {
try{
var clearCanvas = function(context, canvas) {
context.clearRect(0, 0, canvas.width, canvas.height);
var w = canvas.width;
canvas.width = 1;
canvas.width = w;
};
var PrepareBlob = function(blob){
glb._ThumbUrl = glb._DOMURL.createObjectURL(blob);
glb._MagicImg = new Image();
glb._MagicImg.src = glb._ThumbUrl;
};
var PosizViewFilling = function(e){
obj.pDC.drawImage(this,
obj.srt.x,
obj.srt.y,
obj.dms.width,
obj.dms.height);
obj.canvas.toBlob(PrepareBlob);
this.removeEventListener('load',PosizViewFilling,true);
this.src='';
delete this;
};
clearCanvas(obj.pDC,obj.canvas);
if (glb._MagicImg!==null) delete(glb._MagicImg);
glb._DOMURL.revokeObjectURL(glb._ThumbUrl);
var Big_img = new Image();
Big_img.addEventListener('load',PosizViewFilling, true);
Big_img.src = 'data:image/svg+xml;base64,'+btoa(obj.dw); //data from a svg draw
}
catch(err){
console.log(err.message);
}
};
As you can see the routine creates in first the Big_image with the SVG draw. After it creates a resized image in memory. I tried a different approach but also the only Big_image and the obj.dw is enough to live memory leaks. What is wrong? It may be I'm not able to see my bug. I hope I can get a suggestion from different perspectives.
You may also want to consider letting the SVG scale itself.
#main {
width: 400px;
height: 400px;
}
svg {
width: 100%;
height: 100%;
}
#thumb, #zoom {
width: 40px;
height: 40px;
border: solid 1px black;
overflow: hidden;
}
#zoom svg {
width: 400px;
height: 400px;
position: relative;
top: -140px;
left: -210px
}
<div id="main">
<svg id="mainsvg" viewBox="0 0 1000 1000">
<rect x="100" y="100" width="500" height="500" fill="green"
transform="rotate(10,350,350)"/>
<rect x="400" y="400" width="500" height="500" fill="orange"
transform="rotate(-10,650,650)"/>
</svg>
</div>
<div id="thumb">
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="#mainsvg" />
</svg>
</div>
<div id="zoom">
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="#mainsvg" />
</svg>
</div>
You need to have an explicit
delete glb._MagicImg;
When you no longer require this object.
Also see:
Deleting Objects in JavaScript
For further info.
To get the best out of javascript it is always to good idea to reuse resources if you can.
Your code is exceedingly wasteful.
As I see it you want to create a smaller version of a large (complex?) SVG image. It looks like you dump any previous copies when you create the new one.
A possible Solution for you that will not chew memory.
You need two images. One for the SVG and one for the magicEye (thumb). The thumb image can be a canvas, create it once and draw the SVG to it when needed. The other image for the SVG also only needs to be created once, only add the load listener once set its URL = "" when you don't need it any more. Keep it for next time you need it.
The following code will load one of 3 SVG images and convert it to an image (canvas) then do it again in 100ms.
It will not chew more resources than what is required for the two images (ignoring pending GC dumps).
var thumbImage = {
width:100,
height:100,
}
var SVG_images = [
'<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256" id="testSVG" ><defs></defs><path d="M169.5,9Q184.7,10.5,186,23Q187.9,7.2,250,16.5Q252.5,29.3,243.5,36Q238.9,38.5,228,35Q227.1,86.5,231.5,107Q198.9,110.6,198,104.5Q211.3,64.1,209,34.5Q189.3,31.3,187,25Q189.3,39.3,176.5,38Q179.3,24.7,171.5,21Q154.2,20.1,156,39.5Q156.6,50.9,187,63.5Q194,79.1,191,92.5Q183.8,107.2,167.5,110Q147.1,112.3,142,101.5Q137.6,87,147.5,82Q159.4,95.9,160.5,95Q169.1,96,173,86.5Q178.2,73.6,140,48.5Q138.7,29.4,144,20.5Q155.2,7.7,169.5,9Z M37.5,13Q54.2,14.8,75,15.5Q76.1,30.5,49,30Q62.5,97.7,58.5,102Q48.5,102.7,24,98.5Q40.5,60.1,30,33Q6.7,36.9,4,27.5Q.8,8.8,37.5,13Z M91.5,15Q136.5,14.6,136,19.5Q138,38.5,104,31L104,49Q126.7,40.1,127,58.5Q97.6,72.3,101,83Q118.5,80.7,128.5,83Q139.4,95,126.5,104Q81.5,107.5,79,97.5Q84.9,14,91.5,15Z M121.5,130Q141,128.2,157,153.5Q178.3,206,162.5,232Q155.3,234.7,150,227.5Q147.7,219.5,149,192Q124.5,197.1,106.5,191Q102.8,198.8,96,225.5Q86.5,237.5,78,224.5Q90.4,147.8,121.5,130Z M124,149.5Q120.7,154.4,112,172.5Q120,176.9,143,174Q140.8,156.3,124,149.5Z" fill="#000000" fill-rule="evenodd" ></path></svg>',
'<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256" id="testSVG" ><defs></defs><path d="M162.5,9Q171.4,8.5,179.5,12Q186.2,17.8,186,23Q186.8,14,191.5,13Q192,13.6,250,16.5Q252.5,29.3,243.5,36Q238.9,38.5,228,35Q227.1,86.5,231.5,107Q198.9,110.6,198,104.5Q211.3,64.1,209,34.5Q189.3,31.3,187,25Q189.3,39.3,176.5,38Q179.3,24.7,171.5,21Q162.8,20.1,159,25.5Q155.2,30.4,156,39.5Q156.6,50.9,187,63.5Q194,79.1,191,92.5Q185.4,104,172.5,109Q151.2,113.2,144,104.5Q135.6,89.6,147.5,82Q159.4,95.9,160.5,95Q169.1,96,173,86.5Q173.8,80.1,169,70.5Q157.6,60.5,146.5,63Q141.6,57.7,140,48.5Q138.7,29.4,144,20.5Q153.2,11.1,162.5,9Z M37.5,13Q54.2,14.8,75,15.5Q74.6,21.7,71.5,25Q58.8,29.6,49,30Q62.5,97.7,58.5,102Q48.5,102.7,24,98.5Q23,97.5,35,60.5Q36.2,59.8,30,33Q6.7,36.9,4,27.5Q2.9,17.9,9.5,15Q37.6,13.9,37.5,13Z M91.5,15Q136.5,14.6,136,19.5Q136.6,28.8,129.5,32Q129.5,32.2,104,31L104,49Q113.1,46.7,120.5,47Q127.7,50.6,127,58.5Q125,67.9,103.5,69Q101.4,70.3,101,83Q118.5,80.7,128.5,83Q139.4,95,126.5,104Q81.5,107.5,79,97.5Q84.9,14,91.5,15Z M78.5,117Q97.7,121.6,159.5,129Q164.2,130.1,174,144.5Q176.2,144.4,176,212.5Q170,236.9,163.5,244Q113.5,247.9,65,241.5Q58.3,227.7,76,214.5Q72.8,199.7,72,144Q57,146.3,59,131.5Q58.8,128.3,78.5,117Z M106,146.5Q95.5,150.6,98,170Q132.5,179.7,150,168.5L149.5,153Q128.8,145.8,106,146.5Z M98,193.5Q102.4,222.6,106.5,225Q122.3,231.2,133.5,233Q148.3,229.2,153,222.5Q157.6,207.3,152.5,196Q130.7,198.5,98,193.5Z" fill="#000000" fill-rule="evenodd" ></path></svg>',
'<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256" id="testSVG" ><defs></defs><path d="M162.5,9Q183.7,7.2,186,23Q187.9,7.2,250,16.5Q252.5,29.3,243.5,36Q238.9,38.5,228,35Q227.1,86.5,231.5,107Q198.9,110.6,198,104.5Q211.3,64.1,209,34.5Q189.3,31.3,187,25Q189.3,39.3,176.5,38Q179.3,24.7,171.5,21Q154.2,20.1,156,39.5Q156.6,50.9,187,63.5Q194,79.1,191,92.5Q185.4,104,172.5,109Q151.2,113.2,144,104.5Q135.6,89.6,147.5,82Q159.4,95.9,160.5,95Q169.1,96,173,86.5Q178.2,73.6,140,48.5Q135.3,15.1,162.5,9Z M37.5,13Q54.2,14.8,75,15.5Q76.1,30.5,49,30Q62.5,97.7,58.5,102Q48.5,102.7,24,98.5Q40.5,60.1,30,33Q6.7,36.9,4,27.5Q.8,8.8,37.5,13Z M91.5,15Q136.5,14.6,136,19.5Q138,38.5,104,31L104,49Q126.7,40.1,127,58.5Q97.6,72.3,101,83Q118.5,80.7,128.5,83Q139.4,95,126.5,104Q81.5,107.5,79,97.5Q84.9,14,91.5,15Z M121.5,129Q151.9,129.3,159.5,135Q167.3,139.1,165,163.5Q164,172.2,143.5,177Q131.8,176.1,134.5,151Q124.6,153.1,107.5,155Q89.6,186.1,98,210.5Q113.7,214.2,136.5,210Q141,195.7,146.5,194Q168.3,192.4,168,210.5Q165.1,227.3,147.5,237Q125.6,242.4,103.5,242Q85.1,237.3,73,212.5Q67,196.5,70,173.5Q75,154.4,88.5,138Q98.1,130.5,121.5,129Z" fill="#000000" fill-rule="evenodd" ></path></svg>',
]
var createThumb = function(svgData) {
var loadImg = function(){
if(thumbImage.image === undefined){ // check if the image exists?
thumbImage.image = document.createElement("canvas"); // create it if not
}
// resize it. Could test if this is neede but keeping it simple.
thumbImage.width = thumbImage.width;
thumbImage.height = thumbImage.height;
// is there a 2d context
if(thumbImage.image.ctx === undefined){
// no context so create it.
thumbImage.image.ctx = thumbImage.image.getContext("2d");
}
// the resize may be the same and thus no free clear so clear
thumbImage.image.ctx.clearRect(0,0,thumbImage.width,thumbImage.height);
// draw the SVG image onto the magicImg.
thumbImage.image.ctx.drawImage(this, 0,0,thumbImage.width,thumbImage.height);
this.src = "";
};
// does the thumb image exist.
if( thumbImage.tempImage === undefined){
thumbImage.tempImage = new Image(); // create it
// add listener that can be reused for all other loads.
thumbImage.tempImage.addEventListener('load',loadImg);
}
thumbImage.tempImage.src = 'data:image/svg+xml;base64,'+btoa(svgData);
};
var currentSvg = 0;
function justDoIt(){
createThumb(SVG_images[currentSvg % SVG_images.length]);
currentSvg += 1;
setTimeout(justDoIt,100);
}
justDoIt();
It only creates the canvas and image once when first needed, then reuses them while they exist. When the first original SVG is rasterized to the canvas there is no extra memory needed as the already allocated canvas is has its memory (Unless the thumb size changes).
I have run it for an hour now (10 images a second), and gave it the full suit of Dev tool checks. Everything it allocates end up back in the GC so no memory leaks.
You should be able to adapt it to your needs. Remember reuse rather than delete and reasign. Also the Canvas is an image so there is no need to convert anything to a dataURL unless you need to transport it outside the Javascript immediate context.
Related
Manipulating an SVG file from Illustrator to change Path names to text within that path
I currently have a file on Illustrator that needs to be manipulated. In the file (SVG), there are many rectangles or 'paths' which are named as such (paths). On top of these paths, there is text which I want to use in order to rename all of the paths within the document to their specific (ID). enter image description here ID is simply the text that sits on top of the path in the document. Hopefully you are able to see in the image above what I mean. Currently that text and those boxes are separate, and I need to rename the boxes with the corresponding text that sits on top of them. I am currently doing this by manually copy and pasting the text and renaming the boxes. Does anyone know how to write something which would automate this for me? I am very new to the programming world. I don't even know where to start. I was thinking a library on JS like Snap.svg would be good? Any help would be greatly appreciated as this document is very very big (not just that picture) and it takes a long time to do this. I have tried looking at Snap.svg but due to my lack of experience I dont know where to start. someone with more experience might find this very simple and take them a few minutes to come up with a solution.
Search for the underlying elements via document.elementsFromPoint() Keep in mind: svg <text> elements can't be nested within shapes like <rects> – so we can't query for child elements. document.elementsFromPoint() returns an array of elements at specific point coordinates and is natively supported by all major browsers - no need for snap.svg or othe libraries. query for all <text> elements get center coordinates: let bb = label.getBoundingClientRect(); let x = bb.left + bb.width/2; let y = bb.top + bb.height/2; find elements let els = document.elementsFromPoint(x, y) let svg = document.querySelector('svg'); let labels = document.querySelectorAll('text'); labels.forEach(label => { let bb = label.getBoundingClientRect(); let x = bb.left + bb.width / 2; let y = bb.top + bb.height / 2; // sanitize id string let id = label.textContent.replaceAll(' ', '-').replaceAll('#', '').toLowerCase(); // filter only geometry elements let els = document.elementsFromPoint(x, y).filter(el => el instanceof SVGGeometryElement); // optional: select only first underlying element let onlyFirstUnderlying = false; if (onlyFirstUnderlying) { els = [els[0]]; } els.forEach((el, i) => { el.id = id // add incremental suffix to prevent duplicate ids if (els.length > 1 && i > 0) { el.id += '_' + i } }) let newSvg = new XMLSerializer().serializeToString(svg); output.value = newSvg; }) svg { max-width: 10em; height: auto; border: 1px solid #ccc; } text { font-size: 10px } #output { display: block; width: 100%; min-height: 20em; } <svg id="svg" viewBox="0 0 100 100"> <rect x="0" y="0" width="50" height="50" fill="yellow" /> <text x="10" y="25">Label 01</text> <rect x="0" y="50" width="50" height="50" fill="orange" /> <rect x="0" y="50" width="40" height="50" fill="purple" /> <rect x="0" y="50" width="30" height="50" fill="pink" /> <text x="10" y="75">Label 02</text> </svg> <h3>Output</h3> <textarea id="output"></textarea>
How to convert svg element coordinates to screen coordinates?
Is there a way to get the screen/window coordinates from a svg element ? I have seen solutions for the other way around like: function transformPoint(screenX, screenY) { var p = this.node.createSVGPoint() p.x = screenX p.y = screenY return p.matrixTransform(this.node.getScreenCTM().inverse()) } But what i need in my case are the screen coordinates. Sory if it's an obvious question, but i'm new to svg. Thanks.
The code you included in your question converts screen coordinates to SVG coordinates. To go the other way, you have to do the opposite of what that function does. getScreenCTM() returns the matrix that you need to convert the coordinates. Notice that the code calls inverse()? That is inverting the matrix so it does the conversion in the other direction. So all you need to do is remove the inverse() call from that code. var svg = document.getElementById("mysvg"); function screenToSVG(screenX, screenY) { var p = svg.createSVGPoint() p.x = screenX p.y = screenY return p.matrixTransform(svg.getScreenCTM().inverse()); } function SVGToScreen(svgX, svgY) { var p = svg.createSVGPoint() p.x = svgX p.y = svgY return p.matrixTransform(svg.getScreenCTM()); } var pt = screenToSVG(20, 30); console.log("screenToSVG: ", pt); var pt = SVGToScreen(pt.x, pt.y); console.log("SVGToScreen: ", pt); <svg id="mysvg" viewBox="42 100 36 40" width="100%"> </svg>
I was playing around with this snippet below when I wanted to do the same (learn which screen coordinates correspond to the SVG coordinates). I think in short this is what you need: Learn current transformation matrix of the SVG element (which coordinates you are interested in), roughly: matrix = element.getCTM(); Then get screen position by doing, roughly: position = point.matrixTransform(matrix), where "point" is a SVGPoint. See the snippet below. I was playing with this by changing browser window size and was altering svg coordinates to match those of the div element // main SVG: var rootSVG = document.getElementById("rootSVG"); // SVG element (group with rectangle inside): var rect = document.getElementById("rect"); // SVGPoint that we create to use transformation methods: var point = rootSVG.createSVGPoint(); // declare vars we will use below: var matrix, position; // this method is called by rootSVG after load: function init() { // first we learn current transform matrix (CTM) of the element' whose screen (not SVG) coordinates we want to learn: matrix = rect.getCTM(); // then we "load" SVG coordinates in question into SVGPoint here: point.x = 100; // replace this with the x co-ordinate of the path segment point.y = 300; // replace this with the y co-ordinate of the path segment // now position var will contain screen coordinates: position = point.matrixTransform(matrix); console.log(position) // to validate that the coordinates are correct - take these x,y screen coordinates and apply to CSS #htmlRect to change left, top pixel position. You will see that the HTML div element will get placed into the top left corner of the current svg element position. } html, body { margin: 0; padding: 0; border: 0; overflow:hidden; background-color: #fff; } svg { position: fixed; top:0%; left:0%; width:100%; height:100%; background:#fff; } #htmlRect { width: 10px; height: 10px; background: green; position: fixed; left: 44px; top: 132px; } <body> <svg id="rootSVG" width="100%" height="100%" viewbox="0 0 480 800" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="init()"> <g id="rect"> <rect id="rectangle" x="100" y="300" width="400" height="150"/> </g> </svg> <div id="htmlRect"></div> </body>
Not sure why it hasn't been suggested before, but `Element.getBoundingClientRect() should be enough: const { top, // x position on viewport (window) left, // y position on viewport (window) } = document.querySelector('rect').getBoundingClientRect() I think other answers might be derived from a method promoted by Craig Buckler on SitePoint, where he explains using the SVGElement API (instead of getBoudingClientRect, from the - DOM - Element API) to convert DOM to SVG coordinates and vice-versa. But 1. only DOM coordinates are required here 2. he claims that using getBoundingClientRect when transformations (via CSS or SVG) are applied will return incorrect values to translate to SVG coordinates, but the current specification for getBoundingClientRect takes those transformations into account. The getClientRects() method, when invoked, must return the result of the following algorithm: [...] If the element has an associated SVG layout box return a DOMRectList object containing a single DOMRect object that describes the bounding box of the element as defined by the SVG specification, applying the transforms that apply to the element and its ancestors. Specification: https://drafts.csswg.org/cssom-view/#extension-to-the-element-interface Support: https://caniuse.com/#feat=getboundingclientrect
2020 ⚠️ Safari currently has several bugs that make this pretty difficult if you're working with SVGs (or SVG containers) that are transitioning, rotated, or scaled. getScreenCTM() does not include ancestor scale and rotation transforms in the returned matrix. (If your svgs are neither rotated or scaled, then this is the way to go though.) However, if you know the ancestor elements that are being scaled and/or rotated, and their transformation values, you can manually fix the matrix provided by getScreenCTM(). The workaround will look something like this: let ctm = ele.getScreenCTM(); // -- adjust let ancestorScale = // track yourself or derive from window.getComputedStyle() let ancestorRotation = // track yourself or derive from window.getComputedStyle() ctm = ctm.scale(ancestorScale) ctm = ctm.rotate(ancestorRotation) // !! Note: avoid ctm.scaleSelf etc. On some systems the matrix is a true blue SVGMatrix (opposed to a DOMMatrix) and may not support these transform-in-place methods // -- // repeat 'adjust' for each ancestor, in order from closest to furthest from ele. Mind the order of the scale/rotation transformations on each ancestor. If you don't know the ancestors... the best I've come up with is a trek up the tree looking for transformations via getComputedStyle, which could be incredibly slow depending on the depth of the tree... getBoundingClientRect() may return incorrect values when transitioning. If you're not animating things but you are transforming things, then this may be the way to go, though I'm pretty sure it's notably less performant than getScreenCTM. Ideally, insert a very small element into the SVG such that its bounding rect will effectively be a point. window.getComputedStyles().transform has the same issue as above.
Playing with innerWidth, screenX, clientX etc... I'm not sure about what you are searching for, but as you question is arround screenX, screenY and SVG, I would let you play with snippet editor and some little tries. Note that SVG bounding box is fixed to [0, 0, 500, 200] and show with width="100%" height="100%". The last line of tspan with print x and y of pointer when circle is clicked. function test(e) { var sctm=new DOMMatrix(); var invs=new DOMMatrix(); sctm=e.target.getScreenCTM(); invs=sctm.inverse(); document.getElementById("txt1").innerHTML= sctm.a+", "+sctm.b+", "+sctm.c+", "+sctm.d+", "+sctm.e+", "+sctm.f; document.getElementById("txt2").innerHTML= invs.a+", "+invs.b+", "+invs.c+", "+invs.d+", "+invs.e+", "+invs.f; document.getElementById("txt3").innerHTML= e.screenX+", "+e.screenY+", "+e.clientX+", "+e.clientY; var vbox=document.getElementById("svg").getAttribute('viewBox').split(" "); var sx=1.0*innerWidth/(1.0*vbox[2]-1.0*vbox[0]); var sy=1.0*innerHeight/(1.0*vbox[3]-1.0*vbox[0]); var scale; if (sy>sx) scale=sx;else scale= sy; document.getElementById("txt4").innerHTML= e.clientX/scale+", "+e.clientY/scale; } <svg id="svg" viewBox="0 0 500 200" width="100%" height="100%" > <circle cx="25" cy="25" r="15" onclick="javascript:test(evt);" /> <text> <tspan x="10" y="60" id="txt1">test</tspan> <tspan x="10" y="90" id="txt2">test</tspan> <tspan x="10" y="120" id="txt3">test</tspan> <tspan x="10" y="150" id="txt4">test</tspan> </text> </svg>
TypeMismatchError in drawImage()
I'm using drawImage(). This is my code. imDiv holds an inline svg. var c =document.getElementById( 'cvs' ); var ctx =c.getContext( '2d' ); var img =document.getElementById( 'imDiv' ); ctx.drawImage( img, 0, 0 ); //TypeMismatchError I'm getting a TypeMismatchError error. What might be the reason and How can I fix this?
You must convert the SVG to an image first, then draw the image to canvas. There are some things you need to take into consideration: The SVG must be well-formed (think XML) The first element in the SVG must be xmlns attributed There are variable security restrictions depending on browser when it comes to foreignObject (to embed HTML etc.). It will work in some (Firefox, without external references), others not so much (f.ex. Chrome). This is currently in a state of vacuum, and we can't do much about it on client side Here is one way of doing this: // Inline SVG: var svg = document.querySelector('svg').outerHTML, // make sure SVG tags are included canvas = document.querySelector('canvas'), // target canvas ctx = canvas.getContext('2d'); // convert to image (see function below) // converting to image is an asynchronous process, so we need a callback svgToImage(svg, function(img) { // HERE you could insert the image to DOM: // var myElement = document.getElementById(elementID); // myElement.appendChild(img); // set canvas size = image canvas.width = img.width; canvas.height = img.height; // draw SVG-image to canvas ctx.drawImage(img, 0, 0); }); // this will convert an inline SVG to a DATA-URI function svgToImage(svg, callback) { var url = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg), img = new Image; // handle image loading, when done invoke callback img.onload = function() { callback(this); }; img.src = url; } <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> <linearGradient id="gradient"> <stop offset="0%" stop-color="#00f" /> <stop offset="100%" stop-color="#f70" /> </linearGradient> <rect fill="url(#gradient)" x="0" y="0" width="100%" height="100%" /> </svg> <br> <canvas></canvas><br>
As of now, it's impossible to draw div or svg elements onto the canvas. The first argument passed to context.drawImage() must be an Image, canvas, or even a video element (using the svg is probably causing the TypeMismatchError). So you'll have retrieve an actual Image on the document with getElementById() or create one with new Image().
SVG element inserted into DOM is ignored (its type is changed)
I am using the VivaGraph.js library to render a graph in SVG. I am trying to display an image cropped to a circle, for which I am using a clipPath element - as recommended in this post. However, when I create a new SVG element of type that has a capital letter in it, e.g. clipPath in my case, the element that is inserted into the DOM is lowercase, i.e. clippath, even though the string I pass in to the constructor is camelCase. Since SVG is case sensitive, this element is ignored. Everything else seems to be okay. I also tried to change the order in which I append the child elements, in hopes of changing the 'z-index', but it didn't have an impact on this. I am using the following code inside of the function that creates the visual representation of the node in the graph (the 'addNode' callback) to create the node: var clipPhotoId = 'clipPhoto'; var clipPath = Viva.Graph.svg('clipPath').attr('id', clipPhotoId); var ui = Viva.Graph.svg('g'); var photo = Viva.Graph.svg('image').attr('width', 20).attr('height', 20).link(url).attr('clip-path', 'url(#' + clipPhotoId + ')'); var photoShape = Viva.Graph.svg('circle').attr('r', 10).attr('cx', 10).attr('cy', 10); clipPath.append(photoShape); ui.append(clipPath); ui.append(photo); return ui; Thank you!
There is a bit of tweaking needed on top of the post you provided. General idea to solve your issue is this one: We create a VivaGraph svg graphics (which will create an svg element in the dom) Into this svg graphic we create only once a clip path with relative coordinates When we create a node we refer to the clip path Code is: var graph = Viva.Graph.graph(); graph.addNode('a', { img : 'a.jpg' }); graph.addNode('b', { img : 'b.jpg' }); graph.addLink('a', 'b'); var graphics = Viva.Graph.View.svgGraphics(); // Create the clipPath node var clipPath = Viva.Graph.svg('clipPath').attr('id', 'clipCircle').attr('clipPathUnits', 'objectBoundingBox'); var circle = Viva.Graph.svg('circle').attr('r', .5).attr('cx', .5).attr('cy', .5); clipPath.appendChild(circle); // Add the clipPath to the svg root graphics.getSvgRoot().appendChild(clipPath); graphics.node(function(node) { return Viva.Graph.svg('image') .attr('width', 30) .attr('height', 30) // I refer to the same clip path for each node .attr('clip-path', 'url(#clipCircle)') .link(node.data.img); }) .placeNode(function(nodeUI, pos){ nodeUI.attr('x', pos.x - 15).attr('y', pos.y - 15); }); var renderer = Viva.Graph.View.renderer(graph, { graphics : graphics }); renderer.run(); The result in the dom will be like this: <svg> <g buffered-rendering="dynamic" transform="matrix(1, 0, 0,1,720,230.5)"> <line stroke="#999" x1="-77.49251279562495" y1="-44.795726056131116" x2="6.447213894549255" y2="-56.29464520347651"></line> <image width="30" height="30" clip-path="url(#clipCircle)" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="a.jpg" x="-92.49251279562495" y="-59.795726056131116"></image> <image width="30" height="30" clip-path="url(#clipCircle)" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="b.jpg" x="-8.552786105450746" y="-71.2946452034765"></image> </g> <clipPath id="clipCircle" clipPathUnits="objectBoundingBox"> <circle r="0.5" cx="0.5" cy="0.5"></circle> </clipPath> </svg> Notice the clipPathUnits="objectBoundingBox", since it's the main trick for this solution.
How to use the SVG checkintersection() function correctly?
I'm having a problem with the SVG checkintersection() function. All I want to do is to check whether a small SVG-rectangle intersects the area of an SVG-path, but I can't figure out what to call the function on (I already tried to call it on the SVG DOM object, among several other things google turned up). So what I need to know is what to put in for the placeholder ("foo") in this snippet: var closedPath = document.getElementById(closedPath); var rectangle = document.getElementById(rectangle); if (foo.checkIntersection(closedPath, rectangle)) { //do stuff }; with the HTML being something along the lines of <html> <body> <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svgroot"> <g> <path id="closedPath" fill="{$c5}" d="M-250179-46928l-5051 1351l-867-1760l-33-146l-12-99l-82-678l-17-249l86-644l305-1800l158-2882l75-1425l-47-280l-22-131l-137-411l-300-892l1273 620l931-109l1957-734l1860-1096l292-192l884 547l2690 2153l480 963l36 244l-948 1878l-376 591l-60 567l-72 1147l97 847l-222 334l-122 117l-2403 2093l-353 76z"/> <rect id="rectangle" fill="white" x="-126828" y="0" width="45000" height="45000"/> </g> </svg> </body> </html> Any help would be much appreciated! Edit: Just wanted to add that I now use a workaround, which consists of converting the SVG path to an array of point coordinates using a parser function I wrote, which is then put into a simple coordinate-test function. Also this may have been a solution Hit-testing SVG shapes?
checkIntersection is a method on the <svg> element so you'd want something like this... var svg = document.getElementById("svgroot"); var closedPath = document.getElementById(closedPath); var rectangle = document.getElementById(rectangle); var rect = svg.createSVGRect(); rect.x = rectangle.animVal.x; rect.y = rectangle.animVal.y; rect.height = rectangle.animVal.height; rect.width = rectangle.animVal.width; svg.checkIntersection(closedPath, rect) { // do stuff } Note also how the second argument has to be an SVGRect and not an element. SVG elements support SMIL animation, you could equally well write rectangle.baseVal.x etc but that wouldn't necessarily reflect the rectangle's current position if you were animating the rectangle. If you're not using SMIL then rectangle.baseVal.x = rectangle.animVal.x Because a <rect> can have things like rounded corners it doesn't have an SVGRect interface so you have to convert from the interface it does have (SVGRectElement) to the one you need (SVGRect)
<svg width="390" height="248" viewBox="-266600, -68800, 195000, 124000" version="1.1" xmlns="http://www.w3.org/2000/svg"> <path id="closedPath" fill="#ff9966" d="M-250179-46928l-5051 1351l-867-1760l-33-146l-12-99l-82-678l-17-249l86-644l305-1800l158-2882l75-1425l-47-280l-22-131l-137-411l-300-892l1273 620l931-109l1957-734l1860-1096l292-192l884 547l2690 2153l480 963l36 244l-948 1878l-376 591l-60 567l-72 1147l97 847l-222 334l-122 117l-2403 2093l-353 76z"/> <rect id="rectangle" fill="#66ff66" x="-126828" y="0" width="45000" height="45000"/> </svg> <script> var rectangle = document.getElementById('rectangle'); var closedPath = document.getElementById('closedPath'); var svgRoot = closedPath.farthestViewportElement; var rect = svgRoot.createSVGRect(); rect.x = rectangle.x.animVal.value; rect.y = rectangle.y.animVal.value; rect.height = rectangle.height.animVal.value; rect.width = rectangle.width.animVal.value; var hasIntersection = svgRoot.checkIntersection(closedPath, rect); console.log(hasIntersection); </script>