How to add hyperlinks in text in SVG with Javascript? - 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>

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>

Draw svg using fonts on canvas without using defs

I need to draw a svg which use external resources css in a canvas but cannot (or should not) include the style in defs since the svg will be use a lot of times in the canvas.
When I convert the SVGElement to a HTMLImageElement I loss the reference to the style.
Is it possible to obtain the render of a SVGElement in the browser and then draw it in a canvas ?
If not is there an other method to resolve my issue ?
Here is the case that I am trying to resolve:
var svgToParse = '\
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">\
<circle cx="15" cy="15" r="14" fill="red" />\
<text class="testCss" x="50%" y="65%" text-anchor="middle"></text>\
</svg>\
';
var domParser = new DOMParser();
var svgDoc = domParser.parseFromString(svgToParse, "image/svg+xml");
var container = document.getElementById('test_container_id');
var svg1 = svgDoc.documentElement.cloneNode(true);
// Result to obtain in canvas
container.appendChild(
svg1
);
// Canvas
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg2 = new Blob([svgToParse], {type: 'image/svg+xml'});
var url = DOMURL.createObjectURL(svg2);
img.onload = function () {
document.getElementById('canvas_id')
.getContext('2d').drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
}
img.src = url;
body {
font-family: FontAwesome;
}
.testCss {
fill: white;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<div id="test_container_id">
<h3>
Needed in canvas:
</h3>
</div>
<h3>
Canvas:
</h3>
<canvas id="canvas_id" width="100" height="100">
</canvas>

Create SVG text with javascript does not work as expected

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>

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>.

Why is no shape created?

I am trying to learn SVG and have the following code. The point clicked is printed ok, but no shape is created. Can someone plese point out what I am doing wrong.
<?xml version='1.0' standalone='no'?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 20001102//EN' 'http://www.w3.org/TR/2000/CR-SVG-20001102/DTD/svg-20001102.dtd'>
<svg width='100%' height='100%' xmlns='http://www.w3.org/2000/svg' onload='Init(evt)' onmousedown='Grab(evt)' >
<title>Drag And Drop</title>
<script><![CDATA[
var SVGDocument = null;
var SVGRoot = null;
var BackDrop = null;
function Init(evt)
{
SVGDocument = evt.target.ownerDocument;
SVGRoot = SVGDocument.documentElement;
BackDrop = SVGDocument.getElementById('BackDrop');
}
function Grab(evt)
{
var targetElement = evt.target;
if ( BackDrop == targetElement )
{
alert ( 'point: ' + evt.clientX + ' ' + evt.clientY);
var c1 = SVGDocument.createElementNS("http://www.w3.org/2000/svg", "circle");
c1.setAttribute("cx", evt.clientX);
c1.setAttribute("cy", evt.clientY);
c1.setAttribute("r", "100");
c1.setAttribute("fill", "#336699");
BackDrop.appendChild(c1);
}
};
]]></script>
<rect id='BackDrop' x='-10%' y='-10%' width='110%' height='110%' fill='none' pointer-events='all' />
</svg>
BackDrop is a <rect> and <rect> elements are not containers that can have graphic element children. If you create the circles as children of the root element instead i.e. change
BackDrop.appendChild(c1);
to
SVGRoot.appendChild(c1);
the circles will appear.

Categories