I have a text tag that I'm adding to SVG via JS script and would like to make that text editable.
Found a couple of solutions on StackOverflow:
creating a css class and applying it to my SVG text element. This works perfectly, at least in Safari, but isn't recommended it by MDN https://developer.mozilla.org/en-US/docs/Web/CSS/user-modify
Here is the class that is actually someone's answer, unfortunately I can't remember whose:
.editable {
font-size: 0.3em;
user-modify: read-write;
-moz-user-modify: read-write;
-webkit-user-modify: read-write;
}
wrapping the svg in a contenteditable div (answer given by Erik). This also works but results in bad cursor behavior, at least in Safari.
<div contenteditable="true">
<svg id="svgArea" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
</svg>
</div>
The JS part
function foo(x, y, w, h) {
var rect, group;
var svgPlace = document.getElementById('svgArea');
var xmlns = "http://www.w3.org/2000/svg";
group = document.createElementNS(xmlns, 'g');
svgPlace.appendChild(group);
var txtElem = document.createElementNS(xmlns, 'text');
txtElem.setAttributeNS(null, 'x', x);
txtElem.setAttributeNS(null, 'y', y * 1.25);
txtElem.setAttributeNS(null, 'fill', 'LightBlue');
txtElem.setAttributeNS(null, 'contentEditable', true); //does nothing!
txtElem.setAttributeNS(null, 'class', 'editable'); //this is the best working option at the moment
var txtVal = document.createTextNode('test');
txtElem.appendChild(txtVal);
group.appendChild(txtElem);
}
I'd love to be able to set the contentEditable as an attribute, if that's possible. But most importantly, what's the best way to make SVG text editable?
#Raymond comment is the correct answer but since you still have problem I wrote down an example.
document.getElementById("svgWrapper").contentEditable = "true";
<div id="svgWrapper">
<svg id="svgArea" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
</svg>
</div>
I ended up solving this by placing an input tag inside a foreign object, which is added to an SVG group. That works on Safari so I wanted to post it here, in case anyone else is struggling with the same thing.
<svg>
<g>
<foreignobject>
<input></input>
</foreignobject>
</g>
</svg>
If you need to add using JS:
function addSvg(x, y, w, h) {
var rect, group;
var svgPlace = document.getElementById('svgArea');
var xmlns = "http://www.w3.org/2000/svg";
group = document.createElementNS(xmlns, 'g');
svgPlace.appendChild(group);
// solution
let foreigner = document.createElementNS(xmlns, "foreignObject");
foreigner.setAttributeNS(null, "x", x);
foreigner.setAttributeNS(null, "y", y);
foreigner.setAttributeNS(null, "width", w);
foreigner.setAttributeNS(null, "height", h);
group.appendChild(foreigner);
let txt = document.createElement('input');
foreigner.appendChild(txt);
}
Related
Greetins everybody,
First of all, I'm not an expert in JS, or web dev in general.
My goal is to overlay an image with a SVG, so that when you hover certain parts of it, it highlights.
The problem is as stated in the title: SVG element cretaed via createElementNS() are present in the DOM, but not visible on screen.
Here are the relevant code snippets I created:
<span class="image featured" id="tapestry_1_wrapper">
<svg width="0" height="0" viewBox="0 0 0 0" id="draw_t1" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"></svg>
<img src="images/4525_low_800px.jpg" alt="[redacted]" id="t1">
</span>
The (truncated) javascript part:
svg1 = document.getElementById("draw_t1");
t1 = document.getElementById("t1");
svg1.setAttribute("width", t1.clientWidth);
svg1.setAttribute("height", t1.clientHeight);
var new_rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
new_rect.setAttributeNS(null, "id", "my_id");
new_rect.setAttributeNS(null, 'x', x);
new_rect.setAttributeNS(null, 'y', y);
new_rect.setAttributeNS(null, 'width', w);
new_rect.setAttributeNS(null, 'height', h);
new_rect.setAttributeNS(null, "style", "stroke: #000000; fill:#FFFF00;");
svg1.appendChild(new_rect);
I can see the rectangle in the DOM, if I overlay it in the DOM it highlights it on the SVG, but it never shows otherwise (just a blank space on the page).
If you could give me any pointers to what's wrong, or ways to do it another way, I would be glad.
Cheers!
After loading an svg file from InkScape I want to add new elements to the drawing, created under program control. This seems to work fine if we just change the attributes of already present elements, but new created ones, even if they appear when inspecting the DOM, do not show!
A hand-simplified SVG test file:
<svg id="svg8" width="1e3" height="750" version="1.1"
viewBox="0 0 264.58333 198.43751" xmlns="http://www.w3.org/2000/svg">
<g id="layer"><circle id="cc0" cx="20" cy="20" r="10"/></g>
</svg>
The javascript/html file:
<!doctype html><html><head><meta charset="UTF-8"/>
<script>
function addCircle() {
var svgDoc = document.getElementById("test");
var svg = svgDoc.contentDocument;
var svgns = svgDoc.namespaceURI;
// Ok, this changes the circle to red
var c0 = svg.getElementById("cc0");
c0.setAttribute("fill", "#ff0000");
var layer = svg.getElementById("layer");
// Create a circle inside layer "g"
var cc = document.createElementNS(svgns, "circle");
cc.setAttribute("cx", "50");
cc.setAttribute("cy", "50");
cc.setAttribute("r", "20");
layer.appendChild(cc);
// However it's not updating the screen
// even if DOM shows the circle is there, inside the "g"
}
</script></head>
<body>
<object id="test" data="test.svg" type="image/svg+xml"></object>
<script>
document.onreadystatechange = function(){
if(document.readyState === 'complete') addCircle(); }
</script>
</body>
</html>
What am I doing wrong? Or what am I missing? Thank you!
Its the issue with your svg namespace that you are passing to create the circle. Try this
var cc = document.createElementNS("http://www.w3.org/2000/svg", "circle");
You are getting the namespaceURI of the <object> element.
What you need is the one of the inner document's documentElement :
function addCircle() {
// this is the <object>
var svgDoc = document.getElementById("test");
var svg = svgDoc.contentDocument;
// here get the real svg document
var svgns = svg.documentElement.namespaceURI;
//...
As a plunker since stacksnippets® won't allow the modification of inner frames...
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>
I have a script that adds elements to an inline SVG image via jQuery, but the new elements don't seem to be showing up. The output is perfectly valid; I can copy it into the original file, reload it, and it will render just fine. But when the script generates it, the new elements aren't visible.
Here is a snippet that replicates the problem: http://tinkerbin.com/7OmDWlsz
Thanks in advance!
You will not see any svg output if the elements are not in the svg namespace.
Try replacing your script snippet with this:
var slices = 10;
for(i = 0; i < 360; i += 360 / slices) {
var element = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
element.setAttribute("points", "0,0 -10,100 10,100");
element.setAttribute("transform", "rotate(" + i + ")");
$('#rotate').append(element);
}
Thank you to much #Erik Dahlström, I may add a contribution to this. Here is the function I define in order to build svg trees :
Node.prototype.svg_grow = function(node_name, node_attr) {
n = document.createElementNS("http://www.w3.org/2000/svg", node_name);
this.appendChild(n);
if (typeof node_attr !== 'undefined') {
for (key in node_attr) {
n.setAttribute(key, node_attr[key]);
}
}
return n;
}
So you can just use svg_grow() on any node like this :
s_g = document.getElementById("parent");
s_rect = s_g.svg_grow('rect', {x:0, y:0, width:480, height:640});
which just do :
<g id="parent">
<rect x="0" y="0" width="480" height="640" />
</g>
I'm currently working with SVG. I need to know the string length in pixels in order to do some alignment. How can I do to get the length of a string in pixel ?
Update: Thanks to nrabinowitz. Based on his help, I can now get the length of dynamic-added text. Here is an example:
<svg id="main"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="1020"
height="620"
viewBox="0 0 1020 620"
onload="startup(evt)">
<script>
<![CDATA[
var startup = function (evt) {
var width;
var svgNS = "http://www.w3.org/2000/svg";
var txtNode = document.createTextNode("Hello");
text = document.createElementNS(svgNS,"text");
text.setAttributeNS(null,"x",100);
text.setAttributeNS(null,"y",100);
text.setAttributeNS(null,"fill","black");
text.appendChild(txtNode);
width = text.getComputedTextLength();
alert(" Width before appendChild: "+ width);
document.getElementById("main").appendChild(text);
width = text.getComputedTextLength();
alert(" Width after appendChild: "+ width)
document.getElementById("main").removeChild(text);
}
//]]>
</script>
</svg>
I've been wondering this too, and I was pleasantly surprised to find that, according to the SVG spec, there is a specific function to return this info: getComputedTextLength()
// access the text element you want to measure
var el = document.getElementsByTagName('text')[3];
el.getComputedTextLength(); // returns a pixel integer
Working fiddle (only tested in Chrome): http://jsfiddle.net/jyams/
Having read various similar threads with interest and benefitted from some of the ideas, I've created a page which compares three of the Javascript methods side-by-side. I've noted results in
IE9
Firefox 29.0.1 and
Chrome 34.0.1847.131 m
You can load it in your browser and see what works for you:
http://bl.ocks.org/MSCAU/58bba77cdcae42fc2f44.