Create SVG text with javascript does not work as expected - javascript

I'm having trouble making a SVG text element with javascript. The problem is that it is not scaling like it should when window resizes.
Look at this fiddle:
https://jsfiddle.net/cduL72mf/2/
Why is the javscript-generated "SVGtext 2" not behaving like "SVGtext 1", what I can see the output is exactly the same? What am I missing?
var xmlns = 'http://www.w3.org/2000/svg';
var svgelement = document.createElementNS(xmlns, 'svg');
svgelement.id='svg2';
svgelement.setAttribute('viewbox', '0 0 300 200');
document.body.appendChild(svgelement);
var svgtext = document.createElementNS(xmlns, 'text');
svgtext.id='text2';
svgtext.setAttribute('x', '56');
svgtext.setAttribute('y', '74');
svgtext.setAttribute('font-size', '33');
var textnode = document.createTextNode('SVGText 2');
svgtext.appendChild(textnode);
svgelement.appendChild(svgtext);
svg {
width: 100%;
height: 300px;
}
<svg id="svg1" viewbox="0 0 300 200">
<text id="text1" x="56" y="74" font-size="33">SVGText 1</text>
</svg>

viewbox should be viewBox. It is case-sensitive.
The lower-case version in the other SVG ("svg1") is accepted because the HTML parser is more forgiving. It corrects that attribute name for you.
var xmlns = 'http://www.w3.org/2000/svg';
var svgelement = document.createElementNS(xmlns, 'svg');
svgelement.id='svg2';
svgelement.setAttribute('viewBox', '0 0 300 200');
document.body.appendChild(svgelement);
var svgtext = document.createElementNS(xmlns, 'text');
svgtext.id='text2';
svgtext.setAttribute('x', '56');
svgtext.setAttribute('y', '74');
svgtext.setAttribute('font-size', '33');
var textnode = document.createTextNode('SVGText 2');
svgtext.appendChild(textnode);
svgelement.appendChild(svgtext);
svg {
width: 100%;
height: 300px;
}
<svg id="svg1" viewbox="0 0 300 200">
<text id="text1" x="56" y="74" font-size="33">SVGText 1</text>
</svg>

Related

SVG Shape Lighting

Summary: I'm trying to create a canvas of randomized rock climbing holds using vector graphics generated with properties such as color, rotation, size and path values.
To add depth to these I'm trying to add a sort of randomized shadow to them to show that one or more sides of the shape are raised from the background.
Question:
I've been able to apply this filter onto the svg however as you can see on the below image "Light Filter" I get this white effect bleeding out to the edge of the svg element.
I'd like to find a way to keep that raised effect and have the color show or find a new way to show shadow randomized to each edge of the svg path?
You can find the filter code in the function: addFilter
You can disable the filter effect by commenting out the function addFilter(); and applyFilter();
No Filter:
Light Filter:
//create a filter for the svg copying the rough paper filter and apply it to the svg
var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filter.setAttribute("id", "roughpaper");
filter.setAttribute("x", "0%");
filter.setAttribute("y", "0%");
filter.setAttribute("width", "100%");
filter.setAttribute("height", "100%");
var feDiffuseLighting = document.createElementNS("http://www.w3.org/2000/svg", "feDiffuseLighting");
feDiffuseLighting.setAttribute("in", "noise");
feDiffuseLighting.setAttribute("lighting-color", "#ffffff");
feDiffuseLighting.setAttribute("surfaceScale", "2");
var feDistantLight = document.createElementNS("http://www.w3.org/2000/svg", "feDistantLight");
feDistantLight.setAttribute("azimuth", "45");
feDistantLight.setAttribute("elevation", "60");
feDiffuseLighting.appendChild(feDistantLight);
filter.appendChild(feDiffuseLighting);
document.getElementById("svg-0").appendChild(filter);
body {
background-color: #333;
overflow: hidden;
}
#svg-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.svg-element {
position: absolute;
width: 150px;
height: 150px;
}
<div id="svg-container">
<svg
viewBox="0 0 200 200"
preserveAspectRatio="none"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
class="svg-element"
id="svg-0"
filter="url(#roughpaper)"
>
<path
d="M27.8,-30.1C31.8,-29.8,27.8,-17,30.3,-5C32.9,7,42,18.2,38.3,19.7C34.5,21.1,18,12.7,4.6,21.4C-8.9,30.1,-19.3,55.9,-25.4,58.5C-31.5,61.2,-33.3,40.7,-44.1,24.4C-54.8,8,-74.4,-4.3,-75.5,-15.9C-76.6,-27.6,-59.1,-38.6,-43.4,-36.8C-27.7,-34.9,-13.9,-20.3,-1,-19.1C11.9,-17.9,23.9,-30.3,27.8,-30.1Z"
transform="translate(100, 100)"
class="path"
id="path-0"
style="fill: rgb(106, 76, 147)"
></path>
</svg>
</div>
Diffuse light effects should be multiplied with the original, otherwise you'll see the the lighting color rather than the combination of original color + lighting. So just add a feBlend with a multiply - like so. Update: and then add a feComposite/in to "clip to self" - so you don't see the background lit with the light as well.
//create an array with multiple svg paths
var svgPaths = [
{
path: "M27.8,-30.1C31.8,-29.8,27.8,-17,30.3,-5C32.9,7,42,18.2,38.3,19.7C34.5,21.1,18,12.7,4.6,21.4C-8.9,30.1,-19.3,55.9,-25.4,58.5C-31.5,61.2,-33.3,40.7,-44.1,24.4C-54.8,8,-74.4,-4.3,-75.5,-15.9C-76.6,-27.6,-59.1,-38.6,-43.4,-36.8C-27.7,-34.9,-13.9,-20.3,-1,-19.1C11.9,-17.9,23.9,-30.3,27.8,-30.1Z",
},
{
path: "M36.5,-45.3C49.8,-32.4,64.8,-23.2,69,-10.5C73.3,2.2,66.7,18.5,58.3,34.1C49.8,49.8,39.4,64.8,23.8,74.4C8.1,84,-12.7,88.2,-22.9,77.9C-33,67.7,-32.3,43,-35.4,26.1C-38.5,9.1,-45.3,0,-46.2,-10.5C-47.2,-21,-42.3,-32.7,-33.6,-46.4C-24.9,-60.1,-12.5,-75.7,-0.4,-75.2C11.6,-74.7,23.2,-58.1,36.5,-45.3Z",
}
];
var colors = ["#FF595E", "#FFCA3A", "#8AC926", "#1982C4", "#6A4C93"];
//create a function to apply properties to each svg element
function holdProps() {
//based on the id of the svg element, re-position the svg element on the screen
var svgElements = document.getElementsByClassName("svg-element");
//set the position of the svg element to the top left
for (var i = 0; i < svgElements.length; i++) {
svgElements[i].style.position = "absolute";
svgElements[i].style.top = "0";
svgElements[i].style.left = "0";
}
//console log the bounding box of each svg element
for (var i = 0; i < svgElements.length; i++) {
var svg = svgElements[i];
console.log(svg.getBoundingClientRect());
}
}
//create a function to apply properties to each svg elements path value
function holdPaths() {
//create a path and append it to each svg element
$(".svg-element").each(function () {
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
//set attributes to the above created path
path.setAttribute(
"d",
svgPaths[Math.floor(Math.random() * svgPaths.length)].path
);
path.setAttribute("transform", "translate(100, 100)");
path.setAttribute("class", "path");
path.setAttribute("id", "path-" + $(this).attr("id").split("svg-")[1]);
this.appendChild(path);
});
//If a path id is clicked change the path to a random path with a random color
$(".svg-element").click(function () {
var path = svgPaths[Math.floor(Math.random() * svgPaths.length)].path;
var color = colors[Math.floor(Math.random() * colors.length)];
$(this).find("path").attr("d", path);
$(this).find("path").css("fill", color);
});
}
//create a function to apply random colors to each svg element
function colorHold() {
$(".svg-element path").each(function () {
var color = colors[Math.floor(Math.random() * colors.length)];
$(this).css("fill", color);
//stroke white width 5 if the svg is hoverd over
$(this).hover(function () {
$(this).css("stroke", "white");
$(this).css("stroke-width", "5");
}
//reset stroke to black and stroke width to 1 if the svg is not hovered over
, function () {
$(this).css("stroke", "black");
$(this).css("stroke-width", "0");
}
);
});
}
//create feDistantLight and a fePointLight to each svg element
function addFilter() {
var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filter.setAttribute("id", "roughpaper");
filter.setAttribute("x", "0%");
filter.setAttribute("y", "0%");
filter.setAttribute("width", "100%");
filter.setAttribute("height", "100%");
var feGauss= document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
feGauss.setAttribute("stdDeviation", "4");
feGauss.setAttribute("result", "blur");
filter.appendChild(feGauss);
var feDiffuseLighting = document.createElementNS("http://www.w3.org/2000/svg", "feDiffuseLighting");
feDiffuseLighting.setAttribute("in", "blur");
feDiffuseLighting.setAttribute("lighting-color", "#ffffff");
feDiffuseLighting.setAttribute("surfaceScale", "6");
var feDistantLight = document.createElementNS("http://www.w3.org/2000/svg", "feDistantLight");
feDistantLight.setAttribute("azimuth", "235");
feDistantLight.setAttribute("elevation", "50");
feDiffuseLighting.appendChild(feDistantLight);
filter.appendChild(feDiffuseLighting);
var feBlend= document.createElementNS("http://www.w3.org/2000/svg", "feBlend");
feBlend.setAttribute("mode", "multiply");
feBlend.setAttribute("in2", "SourceGraphic");
filter.appendChild(feBlend);
var feComp= document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
feComp.setAttribute("operator", "in");
feComp.setAttribute("in2", "SourceGraphic");
filter.appendChild(feComp);
document.getElementsByTagName("svg")[0].appendChild(filter);
}
//apply the filter to each svg element
function applyFilter() {
var svgElements = document.getElementsByClassName("svg-element");
for (var i = 0; i < svgElements.length; i++) {
svgElements[i].setAttribute("filter", "url(#roughpaper)");
}
}
//on load run the functions
$(document).ready(function () {
//create a path for each svg element
holdPaths();
//apply properties to each svg element
holdProps();
//apply random colors to each svg element
colorHold();
//Disable these two to remove the filter
//create a filter to each svg element
addFilter();
//apply the filter to each svg element
applyFilter();
});
body {
background-color: #333;
overflow: hidden;
}
#svg-container {
/* full size of page*/
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.svg-element {
position: absolute;
}
svg path {
transition: 0.2s;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<div id="svg-container">
<!-- Create SVG -->
<svg id="svg-01" class="svg-element" width="100%" height="100%" viewBox="0 0 200 200" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
</svg>
</div>

How to dynamically create SVG text elements using JS

So, I have an SVG element that is a text. I would like to dynamically create more SVG text elements of exactly the same kind using javascript. (preferably using a for loop of some kind). One option would just to hardcode in the values, but I rather not do that. Here is my code:
var overlapThreshold = "50%";
var name_count = 0;
Draggable.create(".seat_name", {
bounds: "svg",
onDrag: function(e) {
if (this.hitTest("#test1", overlapThreshold)) {
document.getElementById("test1").setAttribute('fill', 'url(#gradRed)');
} else {
document.getElementById("test1").setAttribute('fill', 'url(#gradGreen)');
}
}
});
function change_name(event) {
var name = prompt("Enter a New Name:");
if (name != null && name != "") {
event.target.textContent = name;
}
}
<button id="test_button" onclick="create_name_tags()">Test</button> <svg height="1000" width="1000">
<defs>
<lineargradient id="gradGreen" x1="0%" x2="100%" y1="0%" y2="0%">
<stop offset="0%" style="stop-color:rgb(152, 251, 152);stop-opacity:1"></stop>
<stop offset="100%" style="stop-color:rgb(0, 128, 0);stop-opacity:1"></stop>
</lineargradient>
<lineargradient id="gradRed" x1="0%" x2="100%" y1="0%" y2="0%">
<stop offset="0%" style="stop-color:rgb(255, 0, 0);stop-opacity:1"></stop>
<stop offset="100%" style="stop-color:rgb(178, 34, 34);stop-opacity:1"></stop>
</lineargradient>
</defs>
<g class="circle_seat" id="circle_seats">
<circle cx="70" cy="200" fill="url(#gradGreen)" id="test1" id="seat1" r="40" stroke="black" stroke-width="1"></circle>
</g>
<g class="seat_name" id="seat_name1">
<text fill="#black" font-family="Verdana" font-size="20" id="seat1_details" ondblclick="change_name(event)" x="250" y="210">
BLANK
</text>
</g>
</svg>
Simplified example (optionally using a Google Font)
addText("Your Text Here", 20, 50);
function addText(txt, x, y) {
var el = document.createElementNS("http://www.w3.org/2000/svg", "text");
el.textContent = txt;
el.setAttributeNS(null, 'x', x);
el.setAttributeNS(null, 'y', y);
document.getElementById('svg').appendChild(el);
}
#svg{ width:100vw; height:100vw; font-family:'Crafty Girls'; font-size:50px;
stroke:red; fill:yellow; }
<link href="https://fonts.googleapis.com/css2?family=Crafty+Girls&display=swap" rel="stylesheet">
<svg id='svg'></svg>
Documentation:
Document.createElementNS()
Node.textContent
Element.setAttributeNS()
This is how I create text dynamically. You will need to define an object with the text properties and the text content.
const SVG_NS = "http://www.w3.org/2000/svg";
// an object to define the properties and text content of the text element
let o = {
props: {
x: 50,
y: 15,
"dominant-baseline": "middle",
"text-anchor": "middle"
},
txtConent: "test text"
};
// a function to create a text element
function drawText(o, parent) {
// create a new text element
let text = document.createElementNS(SVG_NS, "text");
//set the attributes for the text
for (var name in o.props) {
if (o.props.hasOwnProperty(name)) {
text.setAttributeNS(null, name, o.props[name]);
}
}
// set the text content
text.textContent = o.txtConent;
// append the text to an svg element of your choice
parent.appendChild(text);
return text;
}
drawText(o, theSvg);
svg{border:1px solid}
<svg id="theSvg" viewBox="0 0 100 30"></svg>
If you also need a way to change the text content dynamically this is how I would do it:
const SVG_NS = "http://www.w3.org/2000/svg";
// an object to define the initial properties and text content of the text element
let o = {
props: {
x: 50,
y: 15,
"dominant-baseline": "middle",
"text-anchor": "middle"
},
txtConent: "your name"
};
// a function to create a text element
function drawText(o, parent) {
var text = document.createElementNS(SVG_NS, "text");
for (var name in o.props) {
if (o.props.hasOwnProperty(name)) {
text.setAttributeNS(null, name, o.props[name]);
}
}
text.textContent = o.txtConent;
parent.appendChild(text);
return text;
}
// a function to update the text
function updateText(text,txtConent){
text.textContent = txtConent;
}
//you save the text in a variable
let txt = drawText(o, theSvg);
// you update the text content when the user is changing the value of the input
theName.addEventListener("input", ()=>{updateText(txt,theName.value)})
svg{border:1px solid}
<p>The name: <input type="text" id="theName" /></p>
<svg id="theSvg" viewBox="0 0 100 30"></svg>
I hope it helps.
var shape = document.createElementNS("http://www.w3.org/2000/svg", "circle");
shape.setAttribute("cx", 25);
shape.setAttribute("cy", 25);
shape.setAttribute("r", 20);
shape.setAttribute("fill", "url(#gradGreen)");
shape.setAttribute("stroke","black");
shape.setAttribute("class","circle_seat");
document.getElementById("circle_seats").appendChild(shape);

How to make sure that a 'path' holds the same x and y position when dragged into a different div

I have a SVG container (Canvas), which has an SVG path (leftLeg) in it. When I drag the SVG path into another div (save), the path goes somewhere else and doesnt hold the position where I dragged it? How Can I solve this? I am using the following libraries:
http://svgdiscovery.com/SVGjs/Plugins/svg.draggable.js
http://svgdiscovery.com/SVGjs/Plugins/svg.connectable.js
I have the following code:
<style>
#canvas {
overflow: hidden;
background-color: #cccccc;
}
<svg id = "canvas" width="100%" height="100%" viewBox="0 0 400 400" z-index="100">
<svg id="dropzone" pointer-events="all" overflow="visible" width="400" height="350" x="90" y="10" fill="white">
<text x="102" y="-2" font-family="sans-serif" font-size="5px" font-weight="bold" fill="black">Drop here</text>
<rect id="dropzoneBox" x="100" y="0" width="50%" height="50%" stroke="black" stroke-dasharray="3"></rect>
</svg>
<svg id = "save" x="190" y="10" width="200" height="175" overflow="visible">
<rect width="100%" height="100%" fill="red" fill-opacity="0" />
</svg>
</svg>
var canvas = SVG('canvas');
save = SVG('save');
function dragIntoBoard(event) {
console.log(event);
//http://svgjs.dev/referencing/#circular-reference
var node = event.target,
svg = node.instance;
var e = event.detail.event,
posX = e.clientX,
posY = e.clientY,
box = save.node.getBoundingClientRect();
//If the element is dropped on top of #save,
//we move the element from the #canvas wrapper into #save:
if( (box.left <= posX) && (posX <= box.right) &&
(box.top <= posY) && (posY <= box.bottom) ) {
save.add(svg);
//Make the circle's position relative to #save, not #canvas:
svg.cx(svg.cx() - save.x());
svg.cy(svg.cy() - save.y());
//No need to listen for more dragging:
svg.off('dragend.namespace');
}
}
function leftLeg()
{
var lines = canvas.group();
lines.attr({"stroke-width":1})
var markers = canvas.group();
var nodes = canvas.group();
var g1 = nodes.group().translate(50, 100).draggable();
g1.circle(1.5).fill("#000");
var g2 = nodes.group().translate(50, 120).draggable();
g2.circle(1.5).fill("#000");
var g3 = nodes.group().translate(50, 140).draggable();
g3.circle(1.5).fill("#000");
g1.connectable({ //---main, central element---
container: lines,
markers: markers
}, g2).setLineColor("#000");
g2.connectable({
}, g3).setLineColor("#000")
var group = canvas.group()
group.add(lines)
group.add(g1)
group.add(g2)
group.add(g3)
group.draggable();
group.on('dragend.namespace', dragIntoBoard);
}

How to add hyperlinks in text in SVG with Javascript?

I thought that my code here would work when a user sends a message that includes a http://, but it doesn't:
function showMessage(nameStr, contentStr, textColor) {
var node = document.getElementById("chatbox");
var nameNode = document.createElementNS("http://www.w3.org/2000/svg", "tspan", textColor);
nameNode.setAttribute("x", 100);
nameNode.setAttribute("dy", 20);
nameNode.setAttribute("fill", textColor);
nameNode.appendChild(document.createTextNode(nameStr));
node.appendChild(nameNode);
var contentNode = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
contentStr = contentStr.replace(/((http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?)/g,
'<a target="blank" href="$1">$1</a>');
contentNode.setAttribute("x", 200);
contentNode.setAttribute("fill", textColor);
contentNode.innerHTML = contentStr;
// Add the name to the text node
node.appendChild(contentNode);
}
Can anyone find an error within this code?
nameStr is the name of the person sending the message,
contentStr is what the user input, and which the program should automatically change so any hyperlinks become clickable links, and
textColor is just the color of the message.
To make hyperlinks work inside an svg element, you should set up the XLink namespace, in addition to the default one for svg:
<svg width="500" height="500"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
Then you can use the xlink:href attribute:
<a xlink:href="http://www.example.com">click here</a>
Taking it all together in this snippet:
function showMessage(nameStr, contentStr, textColor) {
var node = document.getElementById("chatbox");
var nameNode = document.createElementNS("http://www.w3.org/2000/svg", "tspan", textColor);
nameNode.setAttribute("x", 100);
nameNode.setAttribute("dy", 20);
nameNode.setAttribute("fill", textColor);
nameNode.appendChild(document.createTextNode(nameStr));
node.appendChild(nameNode);
var contentNode = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
contentStr = contentStr.replace(/((http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?)/g,
'<a fill="purple" target="blank" xlink:href="$1">$1</a>'); // "xlink:" added!
contentNode.setAttribute("x", 200);
contentNode.setAttribute("fill", textColor);
contentNode.innerHTML = contentStr;
// Add the name to the text node
node.appendChild(contentNode);
}
// Sample call:
showMessage('John Doe', 'click this http://www.example.com', 'blue');
a {
text-decoration: underline;
}
<svg width="500" height="500"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1">
<text id="chatbox"></text>
</svg>

Measure not yet created SVG text in javascript

I'm trying to create a function that will measure how big a text element will be in a SVG element. The code examples I found at Stack Overflow does not work and gives a width of zero. If I delay the measurement I can get the text, but not right away. How is this solved?
var messureSVGtext = function(text, svg, options){
var text = document.createElementNS(svgns, 'text');
text.style.fontFamily = options.font;
text.setAttribute("style",
"font-family:" + options.font + ";" +
"font-size:" + options.fontSize + "px;"
);
var textNode = document.createTextNode(text);
text.appendChild(textNode);
svg.appendChild(text);
// This does not work
console.log(text.clientWidth);
//This does
setTimeout(function(){
console.log(text.clientWidth);
}, 100);
}
You can get the "computed style" of an element and then check the width & height from that.
Give the element an id attribute and after it is appended to the DOM, try this:
var elem1 = document.getElementById("text_elem");
var style = window.getComputedStyle(elem1, null);
console.log(style.width, style.height);
Working example
SVG
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="640"
height="480"
viewBox="0 0 640 480"
style="display:block">
<text id="svg_text" x="20" y="50" style="font-family:Arial; font-size:36px; fill:#BADA55">Hello World!</text>
</svg>
JavaScript
function getCompStyle(oid, cbf)
{
var obj, stl, itv;
obj = document.getElementById(oid);
itv = setInterval(function()
{
stl = window.getComputedStyle(obj, null);
if (stl && (stl.width.indexOf('0') != 0))
{
clearInterval(itv);
cbf(stl);
}
},0);
}
getCompStyle('svg_text', function(style)
{
console.log(style.width);
});
To use the example, place the SVG in your HTML <body> and the JavaScript in a <script> tag below the SVG - also in the <body>.

Categories