Related
I have a simple SVG file that I need to load and iterate through shapes nodes to add an onclick event. The problem is that it is not finding the shape nodes in my SVG file after it loads. What am I doing wrong?
This is my SVG:
<?xml version="1.0" encoding="windows-1252"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="336.652px" height="379.156px" viewBox="0 0 336.652 379.156" enable-background="new 0 0 336.652 379.156"
xml:space="preserve">
<shapes id="shapes">
<shape id="shape1">
<path fill="#999999" d="M190.96,240.98c0,0.011-0.01,0.011-0.01,0.011c-3.16,2.14-8.869,3.069-12.561,2.069l-11.48-1.681
c-0.03-0.01-0.09-0.021-0.12-0.03c-0.03-0.01-0.05-0.029-0.07-0.05c-0.02,0-0.03-0.01-0.04-0.03c-0.14-0.119-0.2-0.33-0.12-0.51
l2.88-6.68l3.32-8.53l0.069-0.149l-0.061,0.149c4.68,2.11,13.771,6.261,18.9,8.63l-0.33,6.2
C191.341,240.6,191.181,240.87,190.96,240.98z"/>
</shape>
<shape id="shape2">
<path fill="#CCCCCC" d="M305.425,65.506c0.171,0.181,0.271,0.53,0.23,0.78c-0.302,3.109-1.83,8.561-3.201,11.37
c11.01,2.86,30.551,2.75,41.541-0.239c0.34-0.091,0.778,0.069,1,0.351L305.425,65.506z"/>
</shape>
</shapes>
</svg>
And this is my code:
<html>
<body>
<object data="test.svg" id="svgholder" type="image/svg+xml"></object>
<script>
var svgholder = document.getElementById("svgholder");
svgholder.onload = function () {
console.log("svg loaded");
var shapes = svgholder.getElementsByTagName("shape");
console.log(shapes.length)
for (var i = 0; i < shapes.length; i++) {
shapes[i].addEventListener("click", showshape, this.id, false);
}
}
function showshape(id) {
console.log(id);
}
</script>
</body>
</html>
Your code is missing some points:
you've already implemented to load the object data/src
But: you also need to parse the loaded content via svgholder.contentDocument to access child elements
your svg markup contains invalid tags/elemnts: <shape> is not a standard compliant element – unless you are creating/registering something like a web component
the click/hover area will be defined by a geometry element like <path> or primitives like <circle>, <rect> etc. The closest thing to your nested <shape> element would be a group <g> element.
Try this code:
HTML
<object data="test.svg" id="svgholder" type="image/svg+xml"></object>
object data/svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 336.652 379.156" >
<g id="shapesGroup0">
<g id="shapesGroup0-0">
<path class="shape" id="shape1" fill="#999999" d="M190.96,240.98c0,0.011-0.01,0.011-0.01,0.011c-3.16,2.14-8.869,3.069-12.561,2.069l-11.48-1.681
c-0.03-0.01-0.09-0.021-0.12-0.03c-0.03-0.01-0.05-0.029-0.07-0.05c-0.02,0-0.03-0.01-0.04-0.03c-0.14-0.119-0.2-0.33-0.12-0.51
l2.88-6.68l3.32-8.53l0.069-0.149l-0.061,0.149c4.68,2.11,13.771,6.261,18.9,8.63l-0.33,6.2
C191.341,240.6,191.181,240.87,190.96,240.98z"/>
</g>
<g id="shapesGroup1-0">
<path class="shape" id="shape2" fill="#CCCCCC" d="M305.425,65.506c0.171,0.181,0.271,0.53,0.23,0.78c-0.302,3.109-1.83,8.561-3.201,11.37 c11.01,2.86,30.551,2.75,41.541-0.239c0.34-0.091,0.778,0.069,1,0.351L305.425,65.506z"/>
</g>
</g>
</svg>
JS
let svgholder = document.getElementById("svgholder");
//load external object content
svgholder.onload = function () {
//get object's svg DOM
let doc = svgholder.contentDocument;
let shapes = doc.querySelectorAll(".shape");
//set click event listeners for each element
for (let i = 0; i < shapes.length; i++) {
shapes[i].addEventListener("click", function(e){
let current = e.currentTarget;
let parentGroup = current.parentNode.closest('g');
//if there is a parent group - get the id - otherwise return empty string
let parentGroupID = parentGroup ? parentGroup.id : '';
let grandParentGroup = parentGroup.parentNode.closest('g');
let grandParentGroupID = grandParentGroup ? grandParentGroup.id : '';
console.log(`clicked element: "#${current.id}" parent group: "#${parentGroupID}" grandparent group: "#${grandParentGroupID}"`)
}
);
}
}
Final thoughts:
Actually I strongly recommend to to switch to a more modern approach than <object>
like inlined svg (the HTML overhead can actually improve your sites performance),
a smart use of externally referenced <use> elements or
native web components. (I guess some other users could elaborate on this ...)
I have an SVG element and I want to add a matTooltip at some point in Angular. I observed that if I try to add matTooltip like this:
generate() {
var svgEle = document.getElementById("testSVG");
var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('id', "rect");
rect.setAttribute('x', "73");
rect.setAttribute('y', "95");
rect.setAttribute('class', "st01");
rect.setAttribute('width', "407");
rect.setAttribute('height', "328");
rect.setAttribute('matTooltip', "Info about the action");
svgEle.append(rect)
}
using html test code:
<div style="width:400px">
<svg version="1.1" id="testSVG" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;"
xml:space="preserve">
<style type="text/css">
.st01{fill:#F99191;}
.st11{fill:#92B1F7;}
</style>
<rect x="638.5" y="146" class="st11" width="236" height="219"
matTooltip="Info about the action"/>
</svg>
</div>
<button mat-stroked-button (click)="generate()">Generate</button>
it doesn't work.
What exactly is the problem in this situation?
It doesn't work because Angular in TS is a compiled language.
It means that [matTooltip] might mean something for the TS compiler, but at runtime in JavaScript it does not exist.
Angular doesn't rely on element attributes to display a tooltip. Instead (if I'm not mistaken), it uses dynamic components rendering to provide a rich component that is absolutely positioned.
If you append the tooltip like this, it's the same as doing nothing.
Your mileage may vary, but this is what I did to overcome this. Create one tooltip for the page and just re-position it when you roll over a certain object.
In your html file
<div #tooltip="matTooltip" matTooltip={{tooltipMessage}}></div>
In your .ts file
import {MatTooltip} from '#angular/material';
...
#ViewChild('tooltip') tooltip: MatTooltip;
tooltipMessage: string = '';
.on('mouseover', function(d: Location, event: MouseEvent){
me.tooltipMessage = "My tooltip message."
me.tooltip.disabled = false;
me.tooltip.show();
let tip = me.tooltip._overlayRef.overlayElement;
if(tip){
setTimeout(()=>{
tip.style.left = d.x+"px";
tip.style.top = y+"px";
})
}
})
.on('mouseout', function(d: Location, event: MouseEvent){
me.tooltip.hide();
})
Here is my code
var s = Snap("#mySVG");
var r = s.rect(0,0,100,200,20,20).attr({ stroke: '#FFFFFF', 'strokeWidth': 20, fill: 'red' });
var clip1 = s.rect(0,0,200,60).attr({ stroke: '#FFFFFF', 'strokeWidth': 1, fill: 'white' });
r.attr({ mask: clip1 });
The mask works as expected in IE 10 but not in Firefox 32 and Chrome 37.
What is wrong ?
EDIT
After user13500 answer. I turned the problem around, started with a blank page and import all elements from my non working page. It appears that a base tag in the header prevent it to work under FF and Chrome. That's looks crazy to me as everything else but masks works, so it's not a script path problem.
<base href="http://127.0.0.1/mySiteDev/">
Yes, as commented by #Robert Longson, the masks url change with the base tag. You can reproduce the sample by plain HTML/SVG as:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Snap base</title>
<style media="screen">
body {
background: #fff;
}
</style>
<base href="http://127.0.0.1/mySiteDev/">
</head>
<body>
<svg id="mySVG">
<defs>
<mask id="Si0fh161e4">
<rect x="0" y="0" width="200" height="60" stroke="#ffffff"
style="stroke-width: 1px;" fill="#ffffff">
</rect>
</mask>
</defs>
<rect x="0" y="0" width="100" height="200" rx="20" ry="20" stroke="#ffffff"
fill="#ff0000" style="stroke-width: 20px;" mask="url('#Si0fh161e4')">
<!-- Problem URL --^ -->
</rect>
</svg>
</body>
</html>
Here the url url('#Si0fh161e4') uses http://127.0.0.1/mySiteDev/, to correct it one have to include document. E.g.
mask="url('the_document.html#Si0fh161e4')"></rect>
or, the somewhat useless;
<base href="//127.0.0.1/mySiteDev/the_document.html">
<base href="the_document.html">
...
which defeats the usability
I have not found anything in the Snap documentation on setting base for URLs, but I'll look further.
Old answer:
Your code works fine here. Chrome 37, Firefox 32, Opera 12.
Result:
Sample snippet, (your code):
window.onload = function () {
var s = Snap("#mySVG");
var r = s.rect(0, 0, 100, 200, 20, 20).attr({
stroke : '#FFFFFF',
strokeWidth : 20,
fill : 'red'
});
var clip1 = s.rect(0, 0, 200, 60).attr({
stroke : '#FFFFFF',
strokeWidth : 1,
fill : 'white'
});
r.attr({
mask: clip1
});
};
<script src="//cdnjs.cloudflare.com/ajax/libs/snap.svg/0.3.0/snap.svg-min.js"></script>
<svg id="mySVG"></svg>
I encountered this mask url() issue in AngularJS template which contained similar svg structure like in this jsfiddle. That worked fine until I noticed that it wasn't working in "search" page. I finally found this Stackoverflow thread and did some extra research, in my site base tag is always something like this
<base href="/en/search/">
when you are in url "http ://example.com/en/search/?q=something"
In my AngularJS template I had to add "{{path}}" in to mask url. (check that fiddle for full svg)
<rect x="0" y="0" width="48" height="47" fill="#008FDB" mask="url({{path}}#arrowShape)"></rect>
In my research I noticed that I only needed to add "window.location.search" in to that "path" variable and everything started to work also in "search" page. BUT 100% working version needed also some js logic, because I noticed that if your url is like "http ://example.com/en/?"
Internet Explorer will return "?" from "window.location.search". Chrome, Safari, Firefox and Opera will return just "" which is not enough to that svg mask to work.
$scope.path = '';
var tmp = $window.location.search;
if(tmp !== "") {
$scope.path = tmp;
}
else if(/\?$/.test($window.location.href)) {
$scope.path = '?';
}
After this svg mask started working consistently. (As a sidenote I have html5mode turned off.)
Here's the fix (in svg.js and dist files): https://github.com/adobe-webplatform/Snap.svg/issues/325
URL = Snap.url = function (url) {
// return "url('#" + url + "')";
return "url('" + window.location.pathname + "#" + url + "')";
};
How would I go about creating an SVG DOM element from a String?
Example:
var svgStr = '<svg width="500" height="400" xmlns="http://www.w3.org/2000/svg"><!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ --><g><title>background</title><rect fill="#fff" id="canvas_background" height="402" width="502" y="-1" x="-1"/><g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid"><rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/></g></g><g><title>Layer 1</title><path id="svg_1" d="m118,242l64,-153l63,157c0,0 45,-71 49,-68c4,3 11,146 12,146c1,0 -173,-7 -173,-7c0,0 -61,-72 -61,-72c0,0 110,-156 46,-3z" fill-opacity="0.7" stroke-width="2" stroke="#995757" fill="#995757"/></g></svg>';
You can use DOMParser to parse an XML string.
var parser = new DOMParser();
var doc = parser.parseFromString(stringContainingXMLSource, "image/svg+xml");
The root element for the parsed string will be doc.documentElement
For this to work properly cross-browser you'll need to set the html namespace i.e. your string will need to look like this...
var svg2='<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" ...
Assuming you are using JavaScript, you can simply pass that string as the innerHTML of an existing element obtained via the DOM API:
var svg2 = "<svg ...> ... </svg>";
var container = document.getElementById("container");
container.innerHTML = svg2;
See: JSFiddle
I was building SVG chart and needed to enable user to pass any SVG to make it into a chart annotation. The solution was:
index.html - the root SVG element I am attaching child SVGs to
<svg id="chart_SVG" width="900" height="600" role="img" xmlns="http://www.w3.org/2000/svg"></svg>
api.ts - API to add annotation (written in TypeScript). x, y - coordinates where to place the annotation
function drawAnnotation(x: number, y: number, svgContent: string, svgId: string): SVGElement {
const svgRoot = document.getElementById("chart_SVG");
const svgNode = document.createRange().createContextualFragment(svgString);
svgRoot.appendChild(svgNode);
const newNode = this.svgRoot.lastChild as SVGElement;
newNode.id = svgId;
newNode.setAttribute("x", x.toString());
newNode.setAttribute("y", y.toString());
return newNode;
}
example.ts
drawAnnotation(
100,
100,
'<svg><g><rect x="0" y="0" width="100%" height="100%" stroke="red" stroke-width="10" fill="orange"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="Verdana" font-size="24" fill="blue">TEXT</text></g></svg>',
"myNewNode"
)
Reading and writing the innerHTML of an SVG within HTML seems to work well except in Internet Explorer (9-11): http://cs.sru.edu/~ddailey/svg/IframeSVG.htm . If one needs IE compatibility (as in for a real web app) then use DOM methods to create a suitable container (object, iframe or embed) and build the SVG, one childNode at a time, through DOM methods within that container. ) It's a bit of a chore, but the basics are covered at http://www.w3.org/Graphics/SVG/IG/resources/svgprimer.html#SVG_HTML.
instead of returning with parent, you can do the following trick.
const template = `
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 13L13 1M1 1L13 13" stroke="#111827" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`
const span = document.createElement('span')
span.innerHTML = template
return span.firstChild
Here is a JS function example to return a SVGELement instance:
function svgElementFromString(str) {
const div = document.createElement('DIV');
div.innerHTML = str;
const svg = div.querySelector('svg');
if (!svg) {
throw Error('<svg> tag not found');
}
return svg;
}
This is the code in my SVG file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X6 -->
<?xml-stylesheet href="style.css" type="text/css"?>
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="1176px" height="617px" version="1.1" style="shape-rendering:geometricPrecision; text- rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip- rule:evenodd"
viewBox="0 0 1176 617"
xmlns:xlink="http://www.w3.org/1999/xlink">
<script xlink:href="skrypty.js" type="text/ecmascript"/>
<defs>
<clipPath id="id0">
<rect x="2" y="1" width="1176" height="617"/>
</clipPath>
</defs>
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<g style="clip-path:url(#id0)">
<image id="mainMapa.jpg" x="2" y="1" width="1176" height="617" xlink:href="mapaMain_Images\mapaMain_ImgID1.png"/>
</g>
<polygon id="fil0" class="str0" onclick="changeColor()" points="638,226 638,269 686,269 670,242 675,242 677,237 673,229 669,229 663,228 660,228 655,229 647,228 "/>
</g>
</svg>
and here is the JavaScript Code
var worldMap = document.getElementById("mapaSwiata").getSVGDocument();
worldMap.getElementById("fil0");
function changeColor()
{
worldMap.style.fill="brown";
}
My intention is to call a javascript function, when someone click on a polygon in external SVG file. Link beetwen external CSS file and external SVG file works perfectly, but I can't figure out how to connect the external javascript file with the svg file.
Any idea?
dont use "onclick" in html/ svg, never use it!
start an eventlistener.
var el = document.getElementById('fil0');
el.addEventListener('click', function(){
this.style.fill="brown";
}, false);
working example: http://jsbin.com/oVoPECIm/1/edit
The svg is called into an <object>, but your javascript exists in the parent. functions called in the object, must reqest the parent function. Is there any reason you cannot call the svg inline?..It would simplify things.
If you have an HTML5 document, the simplest way to load svg inline is to create a DIV with the same width/height as the svg file. Then, since svg is xml, use XMLHttpRequest as follows:
//---html on load---
document.addEventListener("onload",loadSVG(),false)
function loadSVG()
{
var SVGFile="mySvgFile.svg"
var loadXML = new XMLHttpRequest;
function handler()
{
if(loadXML.readyState == 4)
{
if (loadXML.status == 200) //---loaded ok---
{
//---responseText---
var xmlString=loadXML.responseText
mySVGDiv.innerHTML=xmlString
}
}
}
if (loadXML != null)
{
loadXML.open("GET", SVGFile, true);
loadXML.onreadystatechange = handler;
loadXML.send();
}
}