Not able to select paths in SVG picture - javascript
I'm trying to animate a flower SVG image so that it will look like the flower is drawing itself, as described in this article. I've included my current code in JSFiddle here.
I can't seem to access each path element, and I'm not too sure why. I used the Javascript code provided in the article, and I downloaded/included the svg HTML for an SVG flower I found online. When I run the code, the console in Firefox, Safari, and Chrome logs 0, telling me that there are no elements with the tag name path. I'm not sure why this is; can anyone explain why/what I should fix?
Here is the javascript code:
var paths = document.getElementsByTagName('path');
console.log(paths.length);
for (var i = 0; i < paths.length; i++) {
var path = paths[i];
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition =
'none';
// Set up the starting positions
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 2s ease-in-out';
// Go!
path.style.strokeDashoffset = '0';
}
Here is some of my HTML code:
<body>
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="800px"
height="800px"
id="svg2385">
<!-- <defs
id="defs2387" />
<g
transform="translate(-163.06366,-372.39925)"
id="layer1"> -->
<path
d="M 368.89962,527.3412 C 359.4568,464.38904 307.36168,439.5961 286.4323,430.39488 C 267.82968,422.21658 263.76952,407.10258 263.76952,407.10258 C 263.76952,407.10258 258.73335,424.09966 258.73335,432.28344 C 258.73335,440.46722 256.84479,478.86804 271.9533,510.97363 C 287.06182,543.07923 333.01689,554.41062 346.23685,570.14866 C 359.4568,585.8867 354.42063,613.58565 354.42063,613.58565 C 354.42063,613.58565 353.79111,591.55239 338.68259,578.33244 C 323.57407,565.11249 286.4323,565.11249 265.65809,527.3412 C 244.88388,489.5699 243.11868,419.10884 261.25144,392.62358 C 270.14221,379.63744 261.94361,399.48194 275.73043,412.13875 C 288.52321,423.88299 327.25144,435.53708 350.01398,458.72335 C 375.97069,485.16325 369.52915,528.60024 368.89962,527.3412 z"
id="path2395"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="M 339.98723,553.05793 C 345.42186,528.07876 345.25711,516.73109 330.15128,500.99047 C 314.74039,484.93195 299.71749,490.29179 282.61153,461.86813 C 291.50944,496.40407 313.58336,493.78902 325.88728,512.78659 C 337.91025,531.35036 338.75504,552.37736 339.98723,553.05793 z"
id="path3199"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
Solved this. Because my Javascript code was loading before my HTML code, it was registering 0 paths. I put the script tag between and and it worked.
Related
Adjust height of SVG within JS/CSS
I have an svg file that has all the countries in the world. Each country has their own individual paths for example: <path inkscape:connector-curvature="0" id="PH" data-name="Philippines" data-id="PH" d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z" style="fill:#1abc9c;fill-rule:evenodd;" /> <path inkscape:connector-curvature="0" id="PL" data-name="Poland" data-id="PL" d="m 1069.4,228.3 -4.6,-0.1 -0.5,-1.4 -4.8,-1.1 -5.7,2.1 -7.1,2.8 -3.1,1.7 1.4,3.1 -1.2,1.6 2,2.2 1.4,3.3 -0.1,2.1 2.3,3.9 2.4,1.9 3.7,0.6 -0.1,1.7 2.7,1.2 0.6,-1.5 3.4,0.6 0.7,2 3.6,0.3 2.6,3.1 0.3,0.4 1.9,-0.9 2.7,2.2 2.8,-1.3 2.4,0.6 3.4,-0.8 4.9,2.3 1.1,0.4 -1.6,-2.8 3.8,-5.1 2.3,-0.7 0.3,-1.8 -3.1,-5.3 -0.5,-2.7 -1.9,-2.9 2.7,-1.2 -0.3,-2.4 -1.7,-2.3 -0.6,-2.7 -1.4,-1.9 -2.5,-0.6 -8.7,0.1 -5.9,-0.7 z" style="fill:#f2f2f2;fill-rule:evenodd" /> Now I want to access each country through a function in JS. So I added a href before the country: <a href = "#" onclick = "SelectCountry('ph');" id = 'country_ph'> <path inkscape:connector-curvature="0" id="PH" data-name="Philippines" data-id="PH" d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z" style="fill:#1abc9c;fill-rule:evenodd;" /> </a> and access them here: var phCountry = document.getElementById("PH"); function SelectCountry(country) { alert(country); } However I want to scale the country the user clicks on. I tried phCountry.setAttribute("height", "100px"); phCountry.style.height = "100px"; I'm very new to svg and js so I may probably be doing this in the wrong way. How can I fix this? Thanks a ton!
<path> elements do not have a width or height attribute you can change. What you have to do is apply a transform that scales the country paths when you click on them. Also, you don't need to add <a> tags to your SVG. Just add a click handler to each path. See the example below. I've described how it works in the code comments. // Look for all <path> elements in the SVG and add a click handler to each one document.querySelectorAll("svg path").forEach(function(item) { item.addEventListener("click", countryClick); }); function countryClick(e) { // e.target is the path we clicked on. // classList.toggle("enlarge") will cause the "enlarge" class to be added then removed on alternate clicks e.target.classList.toggle("enlarge"); } .enlarge { /* Use the bounds of the path to calculate the transform origin */ transform-box: fill-box; /* Set the origin for scaling to the centre of the path bounds */ transform-origin: 50% 50%; /* Scale up by 2 in both the x and y directions */ transform: scale(2, 2); } <svg viewBox="1000 200 800 400"> <path inkscape:connector-curvature="0" id="PH" data-name="Philippines" data-id="PH" d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z" style="fill:#1abc9c;fill-rule:evenodd;" /> <path inkscape:connector-curvature="0" id="PL" data-name="Poland" data-id="PL" d="m 1069.4,228.3 -4.6,-0.1 -0.5,-1.4 -4.8,-1.1 -5.7,2.1 -7.1,2.8 -3.1,1.7 1.4,3.1 -1.2,1.6 2,2.2 1.4,3.3 -0.1,2.1 2.3,3.9 2.4,1.9 3.7,0.6 -0.1,1.7 2.7,1.2 0.6,-1.5 3.4,0.6 0.7,2 3.6,0.3 2.6,3.1 0.3,0.4 1.9,-0.9 2.7,2.2 2.8,-1.3 2.4,0.6 3.4,-0.8 4.9,2.3 1.1,0.4 -1.6,-2.8 3.8,-5.1 2.3,-0.7 0.3,-1.8 -3.1,-5.3 -0.5,-2.7 -1.9,-2.9 2.7,-1.2 -0.3,-2.4 -1.7,-2.3 -0.6,-2.7 -1.4,-1.9 -2.5,-0.6 -8.7,0.1 -5.9,-0.7 z" style="fill:#f2f2f2;fill-rule:evenodd" /> </svg> However there is one extra step you'll inevitably need to do. If you clicked on the square I have added in the first example, then you will have discovered it already. Elements in an SVG have a draw order. Earlier paths in the file are drawn before later ones. So you will find when you enlarge one country, it may be behind another un-enlarged country. That's probably not what you want. So to fix that, you also need to move the country you are enlarging to the front. You do that by moving it to the end of the SVG. In the next example, I've added the code to do that. // Look for all <path> elements in the SVG and add a click handler to each one document.querySelectorAll("svg path").forEach(function(item) { item.addEventListener("click", countryClick); }); function countryClick(e) { // e.target is the path we clicked on. // classList.toggle("enlarge") will cause the "enlarge" class to be added then removed on alternate clicks e.target.classList.toggle("enlarge"); // If we are enlarging, then also move the path to the end of the SVG if (e.target.classList.contains("enlarge")) { var svg = e.target.ownerSVGElement; // appendChild() adds an element to the end of the SVG. // If it is already in the SVG, it gets moved from it's current position in the order. svg.appendChild(e.target); } } .enlarge { /* Use the bounds of the path to calculate the transform origin */ transform-box: fill-box; /* Set the origin for scaling to the centre of the path bounds */ transform-origin: 50% 50%; /* Scale up by 2 in both the x and y directions */ transform: scale(2, 2); } <svg viewBox="1000 200 800 400"> <path inkscape:connector-curvature="0" id="PH" data-name="Philippines" data-id="PH" d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z" style="fill:#1abc9c;fill-rule:evenodd;" /> <path inkscape:connector-curvature="0" id="PL" data-name="Poland" data-id="PL" d="m 1069.4,228.3 -4.6,-0.1 -0.5,-1.4 -4.8,-1.1 -5.7,2.1 -7.1,2.8 -3.1,1.7 1.4,3.1 -1.2,1.6 2,2.2 1.4,3.3 -0.1,2.1 2.3,3.9 2.4,1.9 3.7,0.6 -0.1,1.7 2.7,1.2 0.6,-1.5 3.4,0.6 0.7,2 3.6,0.3 2.6,3.1 0.3,0.4 1.9,-0.9 2.7,2.2 2.8,-1.3 2.4,0.6 3.4,-0.8 4.9,2.3 1.1,0.4 -1.6,-2.8 3.8,-5.1 2.3,-0.7 0.3,-1.8 -3.1,-5.3 -0.5,-2.7 -1.9,-2.9 2.7,-1.2 -0.3,-2.4 -1.7,-2.3 -0.6,-2.7 -1.4,-1.9 -2.5,-0.6 -8.7,0.1 -5.9,-0.7 z" style="fill:#f2f2f2;fill-rule:evenodd" /> <path d="M 1100,228 h 30 v 30 h -30 z" fill="#ccaa88"/> </svg>
How to change bg color of dynamic CSS class from JS
I have a bar chart and I want to be able to assign individual fill colors to bars depending on a condition. I don't think it matters, but I am using angular-nvd3 linePlusBarChart to draw bars. UPDATE There seemingly is an issue with my local environment, possibly with NVD3 or angular libraries, I am not sure what's going on yet. I put together jsfiddle and everything worked perfectly. See the link below, the bars can have different colors. On my local computer though, when the execution reaches the point var y = document.querySelector(...., I am getting angular error in console ->>>>> y is null. http://jsfiddle.net/p0g9Lqu8/ Here is the HTML. <nvd3> <g class="nv-bars"> <rect x="0" y="419" height="1" fill="LimeGreen" class="nv-bar positive nv-bar-0-0" width="267"></rect> <rect x="0" y="252" height="168" fill="LimeGreen" class="nv-bar positive nv-bar-0-1" width="267"></rect> <rect x="0" y="294" height="126" fill="LimeGreen" class="nv-bar positive nv-bar-0-2 hover" width="267"></rect> <rect x="0" y="252" height="168" fill="LimeGreen" class="nv-bar positive nv-bar-0-3" width="267"></rect> </g> </nvd3> As you can see, each rect tag has a dynamic CSS class nv-bar-0-1, nv-bar-0-2.. that I would like to be able to point to from JS. NOTE: from HTML, if I use CSS, all works, but from JS loop, does not. Any help is appreciated. This perfectly works. <style> nvd3 .nv-bars rect { fill:white; } nvd3 .nv-bars rect.nv-bar-0-1{ fill:yellow; } </style> But this, does not: for(var i = 0; i < 4; i++) { var y = document.querySelector('nvd3 .nv-bars rect.nv-bar-0-' + i); y.style.fill = "#ffff00"; } OR this one, same result var y = document.querySelector('nvd3 .nv-bars rect.nv-bar-0-1); // y is always null y.style.fill = "#ffff00"; // triggers null reference error here I call that JS fragment inside Angular controller in this block: angular.element(document).ready(function () { var y = document.querySelector('nvd3 .nv-bars rect.nv-bar-0-1'); console.log('>>>>>>>>>>>===' + y); // this outputs [object HTMLUnknownElement] y.style.fill = "#ffff00"; });
I thank everybody for time and input. I finally found where the problem was and what the solution would be, so I would like to share it with the community in case anybody comes across a similar problem. My final output into the browser is a result of combining multiple HTML/JS/CSS fragments. The issue was in timing: the JS code was attempting to refer to a tag, but it appeared the dynamic construction of that tag with its HTML/CSS was not complete at the time of JS reference. angular.element(document).ready(.... could not do the job, but a pure JS approach from the link below - did. https://github.com/jfriend00/docReady I enclosed the JS block that was to check some conditions and assign colors to bars, into the docReady block. See the simplified code sample below. docReady(function () { for (var i = 0; i < 4; i++) { var k = document.querySelector('nvd3 .nv-bars rect.nv-bar-0-' + i); // some condition goes here..... k.style.fill = "green"; } var y = document.querySelector('nvd3 .nv-bars rect.nv-bar-0-1'); // this too changes color y.style.fill = "#ff0000"; }); Worked in all major browsers: Firefox, Chrome, Safari and IE
Animate custom vector shape
I'd like to animate the following vector shape, unfortunately due to the edges converting it into a path is not possible. (If anyone knows a way to preserve the shape as a path, kudos for that!) The goal would be to have an animation that follows the shape: I was playing around with SVG animations, but it seems to be not possible to animate a shape. Path animations are possible. My question is, is it possible to use a <canvas> element like in the attached fiddle and animate it there? http://jsfiddle.net/Na6X5/
I recreated the shape in Illustrator so it's not quite perfect, but it's very close. I then saved it as an SVG path. Here is the working code to do what I think you want. SVG Shape: <?xml version="1.0" encoding="utf-8"?> <svg id="myshape" 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" viewBox="0 0 571.1 437.5" style="enable-background:new 0 0 571.1 437.5;" xml:space="preserve" stroke="#000" stroke-width="90" stroke-miterlimit="10"> <g> <path id="mypath" d="M519,300.4l-76.4,75.9c-14,13.9-36.7,13.9-50.7-0.1l-83.5-83.5c-13.2-13.2-34.6-13.3-48-0.2 l-93.9,92.1c-11.2,11-29.4,10.4-39.9-1.4l-73.5-82.6c-11.4-12.8-10.8-32.2,1.3-44.3L255.7,55.5c14-14,36.6-14,50.7-0.1l212.5,210.7 C528.5,275.6,528.5,291,519,300.4z" /> </g> </svg> JavaScript drawTime = 2000; //2 seconds path = document.getElementById("mypath"); length = path.getTotalLength(); path.style.strokeDashoffset = length; //starting position path.style.strokeDasharray = length + ', ' + length; path.style.fill = "none"; //make it have no fill to begin with path.style.transition = path.style.WebkitTransition = 'none'; path.getBoundingClientRect(); path.style.transition = path.style.WebkitTransition = 'stroke-dashoffset ' + (drawTime / 1000) + 's ease-in-out'; path.style.strokeDashoffset = '0'; //finishing position JSFiddle (pure JavaScript): https://jsfiddle.net/900nayr2/4/ JSFiddle (with my jQuery plugin I wrote): https://jsfiddle.net/vL5bz5mn/1/ For the jQuery one... I wrote the DrawSVG plugin approximately a year ago for jQuery 1.10 or something like that. I hope this helps! You could just use the JavaScript one if you like.
SVG HTML Not Drawing SVG Pattern
Im creating an SVG pattern in HTML and the user will be able to change colour and size etc. But its not working. I get an error regarding the body onload function. and then when appending the SVG Diagram to the svg placeholder that i have. Here is the Script: <script> var SVG = document.getElementById("svgArea"); document.content.appendChild(SVG); function drawCircle() { var svgLink = "http://www.w3.org/2000/svg"; var Centre = document.createElementNS(svgLink,"circle"); Centre.setAttributeNodeNS(null,"id","Centre"); Centre.setAttributeNodeNS(null,"cx",230); Centre.setAttributeNodeNS(null,"cy",0); Centre.setAttributeNodeNS(null,"r",75); Centre.setAttributeNodeNS(null,"fill","centreColour"); document.getElementById("svgArea").appendChild(Centre); var group = document.getElementById("svgArea"); group.setAttribute("transform","translate(230,0)"); } </script> Then for the body tag i have the following: <body onload="drawCircle()"> And for the contents of the page i have the following code: <div class="content"> <!-- SVG DIAGRAM --> <svg id="SVG" style="background:pink" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="690" height"400"> <g id="svgArea"> </g> </svg> </div> Errors: [Error] TypeError: 'undefined' is not an object (evaluating 'document.content.appendChild') global code (SVG.html, line 33) [Error] TypeMismatchError: DOM Exception 17: The type of an object was incompatible with the expected type of the parameter associated to the object. drawCircle (SVG.html, line 57) onload (SVG.html, line 107) Where and what am i doing wrong? Thanks This doesnt Work: //Drawing Petals for (var i = 0; i < numberOfPetals; i++) { var svgLink = "http://www.w3.org/2000/svg"; var flowerPettle = document.createElementNS(svgLink,"ellipse"); flowerPettle.setAttributeNS(null,"id","flowerPettle"); flowerPettle.setAttributeNS(null,"ry", 230); flowerPettle.setAttributeNS(null,"rx",0) flowerPettle.setAttributeNS(null,"fill",petalColour); var rotate = "rotate(" + (i*(360 / numberOfPetals)) + " " + 300 + "," + 30 + ")"; flowerPettle.setAttribute("transform",rotate); document.getElementById("FlowerArea").appendChild(flowerPettle); }
There are a few things wrong with your code. One is the createAttributeNode() calls. Another is the document.content reference. Plus there are things like the strange colour value "centreColour". I've put together a working fiddle. Hopefully it helps you get things working in your code. Demo here
How to draw non-scalable circle in SVG with Javascript
I'm developing a map, in Javascript using SVG to draw the lines. I would like to add a feature where you can search for a road, and if the road is found, a circle appears on the map. I know i can draw a circle in SVG, but my problem is that, the size of the circle should not change depending on the zoom-level. In other words the circle must have the same size at all times. The roads on my map have this feature, all i had to do was add vector-effect="non-scaling-stroke" to the line attributes.. A line looks like this. <line vector-effect="non-scaling-stroke" stroke-width="3" id = 'line1' x1 = '0' y1 = '0' x2 = '0' y2 = '0' style = 'stroke:rgb(255,215,0);'/> The circle looks like this. <circle id = "pointCircle" cx="0" cy="0" r="10" stroke="red" stroke-width="1" fill = "red"/> Is it possible to define the circle as "non-scaling" somehow?
It took me a while, but I finally got the math clean. This solution requires three things: Include this script in your page (along with the SVGPan.js script), e.g. <script xlink:href="SVGPanUnscale.js"></script> Identify the items you want not to scale (e.g. place them in a group with a special class or ID, or put a particular class on each element) and then tell the script how to find those items, e.g. unscaleEach("g.non-scaling > *, circle.non-scaling"); Use transform="translate(…,…)" to place each element on the diagram, not cx="…" cy="…". With just those steps, zooming and panning using SVGPan will not affect the scale (or rotation, or skew) of marked elements. Demo: http://phrogz.net/svg/scale-independent-elements.svg Library // Copyright 2012 © Gavin Kistner, !#phrogz.net // License: http://phrogz.net/JS/_ReuseLicense.txt // Undo the scaling to selected elements inside an SVGPan viewport function unscaleEach(selector){ if (!selector) selector = "g.non-scaling > *"; window.addEventListener('mousewheel', unzoom, false); window.addEventListener('DOMMouseScroll', unzoom, false); function unzoom(evt){ // getRoot is a global function exposed by SVGPan var r = getRoot(evt.target.ownerDocument); [].forEach.call(r.querySelectorAll(selector), unscale); } } // Counteract all transforms applied above an element. // Apply a translation to the element to have it remain at a local position function unscale(el){ var svg = el.ownerSVGElement; var xf = el.scaleIndependentXForm; if (!xf){ // Keep a single transform matrix in the stack for fighting transformations // Be sure to apply this transform after existing transforms (translate) xf = el.scaleIndependentXForm = svg.createSVGTransform(); el.transform.baseVal.appendItem(xf); } var m = svg.getTransformToElement(el.parentNode); m.e = m.f = 0; // Ignore (preserve) any translations done up to this point xf.setMatrix(m); } Demo Code <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <title>Scale-Independent Elements</title> <style> polyline { fill:none; stroke:#000; vector-effect:non-scaling-stroke; } circle, polygon { fill:#ff9; stroke:#f00; opacity:0.5 } </style> <g id="viewport" transform="translate(500,300)"> <polyline points="-100,-50 50,75 100,50" /> <g class="non-scaling"> <circle transform="translate(-100,-50)" r="10" /> <polygon transform="translate(100,50)" points="0,-10 10,0 0,10 -10,0" /> </g> <circle class="non-scaling" transform="translate(50,75)" r="10" /> </g> <script xlink:href="SVGPan.js"></script> <script xlink:href="SVGPanUnscale.js"></script> <script> unscaleEach("g.non-scaling > *, circle.non-scaling"); </script> </svg>
If you are looking for a fully static way of doing this, you might be able to combine non-scaling-stroke with markers to get this, since the markers can be relative to the stroke-width. In other words, you could wrap the circles in a <marker> element and then use those markers where you need them. <svg width="500" height="500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000"> <marker id="Triangle" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="strokeWidth" markerWidth="4" markerHeight="3" orient="auto"> <path d="M 0 0 L 10 5 L 0 10 z" /> </marker> <path d="M 100 100 l 200 0" vector-effect="non-scaling-stroke" fill="none" stroke="black" stroke-width="10" marker-end="url(#Triangle)" /> <path d="M 100 200 l 200 0" fill="none" stroke="black" stroke-width="10" marker-end="url(#Triangle)" /> </svg> The same can also be viewed and tweaked here. The svg spec isn't fully explicit about what should happen in this case (since markers are not in SVG Tiny 1.2, and vector-effect isn't in SVG 1.1). My current line of thinking was that it should probably affect the size of the marker, but it seems no viewers do that at the moment (try in a viewer that supports vector-effect, e.g Opera or Chrome).
Looks like some work was done in webkit (maybe related to this bug: 320635) and the new transform doesn't stick around when simply appended like that transform.baseVal.appendItem This seems to work better. Even works in IE 10. EDIT: Fixed the code for more general case of multiple translate transformations in the front and possible other transformations after. First matrix transformation after all translates must be reserved for unscale though. translate(1718.07 839.711) translate(0 0) matrix(0.287175 0 0 0.287175 0 0) rotate(45 100 100) function unscale() { var xf = this.ownerSVGElement.createSVGTransform(); var m = this.ownerSVGElement.getTransformToElement(this.parentNode); m.e = m.f = 0; // Ignore (preserve) any translations done up to this point xf.setMatrix(m); // Keep a single transform matrix in the stack for fighting transformations // Be sure to apply this transform after existing transforms (translate) var SVG_TRANSFORM_MATRIX = 1; var SVG_TRANSFORM_TRANSLATE = 2; var baseVal = this.transform.baseVal; if(baseVal.numberOfItems == 0) baseVal.appendItem(xf); else { for(var i = 0; i < baseVal.numberOfItems; ++i) { if(baseVal.getItem(i).type == SVG_TRANSFORM_TRANSLATE && i == baseVal.numberOfItems - 1) { baseVal.appendItem(xf); } if(baseVal.getItem(i).type != SVG_TRANSFORM_TRANSLATE) { if(baseVal.getItem(i).type == SVG_TRANSFORM_MATRIX) baseVal.replaceItem(xf, i); else baseVal.insertItemBefore(xf, i); break; } } } } EDIT2: Chrome killed getTransformToElement for some reason, so the matrix needs to be retrieved manually: var m = this.parentNode.getScreenCTM().inverse().multiply(this.ownerSVGElement.getScreenCTM());
It's discussed here and here It looks like current browsers don't do the expected thing, so one needs to apply the inverse transform of the zoom (scale) on the contents of the <marker>, eg. transorm: scaleX(5) on the user of the <marker> etc. will need to be accompanied by a transform: translate(...) scaleX(0.2) inside the <pattern>, also factoring in possible x/y/width/height/transform-origin values inside the pattern if needed