SVG injection with template literals doesn't work in browser - javascript
My idea is to inject inline SVG to my HTML using template literals.
The purpose is simply use benefits of styling inline SVG icons with CSS but avoid bloating html code with SVG stuff. Also it's very easy to reuse repeating icons by just adding corresponding class. Also all icons or vector stuff like logo can be stored in single JS file. I know, that's hacky weird idea, but I found it's useful for my project.
So the problem is that my script won't work in browser from my local files.
But it works well in CodePen and JSFiddle.
Local file restrictions are disabled, JS is enabled.
Also any ideas how to optimize it and make it work better are welcome.
var iconLogo = document.querySelectorAll(".ric-logo");
var i;
for (i = 0; i < iconLogo.length; i++) {
iconLogo[i].innerHTML = `
<svg width="120px" height="18px" viewBox="0 0 120 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="ric-logo" stroke="none" stroke-width="1" fill="black" fill-rule="evenodd">
<path d="M23.326087,0 C23.2252174,0 23.1275,0 23.0266304,0 L23.0266304,0 L2.52173913,0 C1.12902107,-8.82200545e-17 1.70558772e-16,1.16795283 0,2.60869565 L0,15 L5.04347826,15 L5.04347826,5.2173913 C5.04347826,4.8572056 5.32573353,4.56521739 5.67391304,4.56521739 L22.9730435,4.56521739 C23.631932,4.55997742 24.1901947,5.0660023 24.2717391,5.7423913 C24.306535,6.10977596 24.1892199,6.47520901 23.948885,6.74807191 C23.7085502,7.02093481 23.3677046,7.17567223 23.0108696,7.17391304 L7.56521739,7.17391304 L7.56521739,11.7391304 L23.0266304,11.7391304 L23.0266304,11.7391304 C23.1275,11.7391304 23.2252174,11.7391304 23.326087,11.7391304 C26.4597026,11.7391304 29,9.11123658 29,5.86956522 C29,2.62789386 26.4597026,1.98495123e-16 23.326087,0 Z" id="Path"></path>
<path d="M64.9565217,1.21154527e-06 L61.173913,1.21154527e-06 C60.170401,-0.000815107822 59.2077021,0.410915432 58.4977174,1.14456624 L49.8796739,10.0532605 C49.6430123,10.2978107 49.3221127,10.4350542 48.9876087,10.4347821 L46.673913,10.4347821 C46.3257335,10.4347821 46.0434783,10.142794 46.0434783,9.78260833 L46.0434783,1.21154527e-06 L41,0 L41,12.3913036 C41,13.8320462 42.1290211,14.9999988 43.5217391,14.9999988 L49.826087,14.9999988 C50.829599,15.0008151 51.7922979,14.5890846 52.5022826,13.8554338 L61.1108696,4.95000041 C61.3493224,4.70184137 61.674158,4.56319679 62.0123913,4.56521787 L64.326087,4.56521787 C64.6742665,4.56521787 64.9565217,4.85720602 64.9565217,5.21739167 L64.9565217,14.9999988 L70,15 L70,2.60869644 C70,1.16795385 68.8709789,1.21154527e-06 67.4782609,1.21154527e-06 L64.9565217,1.21154527e-06 Z" id="Path"></path>
<path d="M114,10 L120,10 L120,16 C120,17.1045695 118.992641,18 117.75,18 L114,18 L114,10 Z" id="Path"></path>
<path d="M106,0 L106,9.7826087 C106,10.1427944 105.720178,10.4347826 105.375,10.4347826 L87.625,10.4347826 C87.279822,10.4347826 87,10.1427944 87,9.7826087 L87,0 L82,0 L82,12.3913043 C82,13.8320472 83.1192881,15 84.5,15 L108.5,15 C109.880712,15 111,13.8320472 111,12.3913043 L111,0 L106,0 Z" id="Path"></path>
</g>
</svg>
`;
}
/* Styling all instances of SVG */
.ric-logo * {
fill: blue;
width: 120px;
height: 15px;
};
<body>
<header class="header-menu">
<div class="ric-logo">
</div>
<div class="menu-content">
<ul>
<li>Item-1</li>
<li>Item-2</li>
<li>Item-3</li>
<li>Item-4</li>
</ul>
</div>
</header>
<main>
<!-- Test repeating SVG class -->
<div class="ric-logo">
</div>
</main>
<footer></footer>
</body>
I'm not sure how you've structured your files, so I might not be reproducing your problems correctly. Let me know if I've made any false assumptions.
Firstly, codepen and the snippets on Stack Overflow automatically include CSS and JS, while local files do not. If you haven't already, you need to include appropriate <script> and <style> tags containing your css and your js.
If you are already doing that and you've just edited them out for the purposes of this question, then another possible problem is that the javascript you've provided you've included at the top of your page. document.querySelectorAll(".ric-logo") won't work until the page has been loaded, and if you put the javascript you've supplied at the top of your page, it will run before the page has finished loading. The easiest way to fix this is to put the javascript at the bottom of your page.
If for some reason you don't want your javascript at the bottom of your page, you need something like jQuery's $(document).ready, or one of the vanilla alternatives from this question.
#Nicholas was right, and the problem was that document.querySelectorAll not working until all the DOM stuff loaded.
So one solution is to place document.querySelectorAll to the bottom of the page.
Another is to use function that run your scripts when pages is loaded. It's explained here.
I applied it to my script and it works well. So if you interested I post the snipped here. It may be helpful for templating using string literals or like in my case dynamically inject Inline SVG.
// Icons SVG injection
// Function that run code when DOM is loaded
function ready(callback){
// in case the document is already rendered
if (document.readyState!='loading') callback();
// modern browsers
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
// IE <= 8
else document.attachEvent('onreadystatechange', function(){
if (document.readyState=='complete') callback();
});
}
ready(function(){
// All inside tht function executes when DOM is loaded
// Logo SVG
var mySvg = document.querySelectorAll(".my-svg");
var i;
for (i = 0; i < mySvg.length; i++) {
mySvg[i].innerHTML = `
<svg width="188px" height="188px" viewBox="0 0 188 188" version="1.1">
<circle id="Oval" fill="#FC4903" cx="94" cy="94" r="94"></circle>
</svg>
`
};
});
/* styling svg */
.my-svg * {
fill: red;
width: 200px;
height: 200px;
};
<!-- creating container where you like to insert SVG -->
<div class="my-svg">
<div></div>
</div>
Related
gsap animation - import only one svg or multiple svgs
I would like to do "complex" animation with gsap and svgs. but I don't know what is the best approach to this. it is better to create and to import an unique svg with all the elements or maybe it is better 4 different svgs? I have 4 different characters: a tree, a lamp, a desk and a man. basically my animation is move the objects on the x, and appearing and to disappearing stuff.
If the elements of the animation are part of one complex animation, you can use one single SVG for this. To control the DOM of the SVG via CSS and JavaScript you need to add the SVG directly inline into your HTML page. Not embed via img tag or object tag or similar. <body> <h1>My SVG Animation</h1> <svg width="100" height="100" viewBox="0 0 300 100"> <circle class="animation-element-01" cx="50" cy="50" r="40"/> <rect class="animation-element-01" x="150" y="20" width="150" height="150"/> <!-- etc --> </svg> </body> Another advantage of this method is, that there is no additional html requests. Plus the whole animation can be made responsive via the viewBox.
SVG <use> tags within Polymer dom-repeat
I'm iterating over an object using dom-repeat, and want to reference different icons from an SVG sprite sheet with each iteration. To add an icon, I'm trying to use the <use xlink:href="sprite.svg#id"> method, mixed with Polymer's computed bindings. Here's the code inside the dom-repeat block: <template is='dom-repeat' items="{{currentPlan.functionalities}}"> <div class="resourceRow rowParent"> <div class="functionIconContainer columnParent"> <svg><use xlink:href$="{{ _getIconURL(item.id) }}"/></svg> </div> </div> </template> & the JS logic: _getIconURL: function(iconID){ var url = "sprite.svg#" + iconID; return url; } This code outputs what I want it to, as far as I can see in Dev Tools, but still the icon doesn't show up. For reference, here's an example of what is written to the DOM: <svg class="style-scope"> <use class="style-scope" xlink:href="sprite.svg#id"/> </svg> Is this a bug or my misunderstanding?
I had exactly the same problem before and ended up using iron-iconset-svg (https://elements.polymer-project.org/elements/iron-icons?active=iron-iconset-svg), which in my opinion provides a cleaner/easier solution. Its simply a wrapper for your SVG sprite sheet, so you define your icons almost the same way and use them with iron-icon. Defining a custom iconset (put it directly into the page or wrap it inside an element + set a name that describes the icons, here: 'your-iconset-name') <iron-iconset-svg name="your-iconset-name" size="24"> <svg> <defs> <g id="your-icon-name"> <rect x="12" y="0" width="12" height="24" /> <circle cx="12" cy="12" r="12" /> </g> </defs> </svg> </iron-iconset-svg> If you wrap them, lets say in 'your-custom-iconset', you can include the iconset like this: <your-custom-iconset></your-custom-iconset> Using icons When you need an icon you just include iron-icons (https://elements.polymer-project.org/elements/iron-icons) and place it like this: <iron-icon icon="your-iconset-name:your-icon-name"></iron-icon> You can then give it a class to apply styling and don't need to use 'fill' for its color, just use 'color'. Applied to your example: <template is='dom-repeat' items="{{currentPlan.functionalities}}"> <div class="resourceRow rowParent"> <div class="functionIconContainer columnParent"> <iron-icon icon$="your-iconset-name:{{item.id}}"></iron-icon> </div> </div> </template>
Ok, not sure if this really counts as an answer, but it fixes my immediate problem. I've attached an on-dom-change event handler to the dom-repeat block, which gets called on well, DOM changes. Then I loop through each icon container and set its innerHTML to itself. I don't know what it does, but it somehow forces a re-evaluation of that part of the DOM, causing my icons to show up. It's the simplest of code: _forceRedraw: function(){ var functionIcons = document.querySelectorAll('div.functionIconContainer'); _.each(functionIcons, function(iconContainer){ iconContainer.innerHTML = iconContainer.innerHTML; }) } 'Lo, it works!
A workaround is to add the attribute statically in addition to the binding <svg><use xlink:href="" xlink:href$="{{ _getIconURL(item.id) }}"/></svg> Polymer has issues with creating the attribute, updating it works fine though.
This is a duplicate of: Polymer (1.0) - dynamic use xlink:href$ inside template not working properly This is a Polymer bug, filed as issue #3060.
Inline SVG and HTML DOM Scope for .getElementById
The problem space: I have an SVG map that I embed inline to html using JQUERY Ajax. Then I use java script to address many elements in the svg object to change attributes and add event listeners based on data series. The data and the maps are interactive. Users can change state over many permutations. I am also using JQUERY UI 1.10.3 Tabs There are numerous categories. Therefore depending on the presentation there could be multiple copies of the svg map on the same Tab and many different Tabs having the same svg map. Each copy of the svg document has the same element IDs. The problem I am experiencing is that the element IDs are conflicting with each other in the HTML DOM. So that the only elements that are addressable are the elements from the svg object that occurs first in the HTML document. My question is: Do I need to make the elements IDs unique for each instance of the svg object? Or is there a way to set scope and context to the svg object in the HTML DOM? Example: Each containing div has a unique ID that I use to insert the svg document and retrieve the SVG document. <div id="svgMap_div_1" class="cIG" style="height: 500px; width: 750px"></div> Code used to load the svg document that is received from ajax (mimeType = "image/svg+xml"): document.getElementById('svgMap_div_1').appendChild(svgXMLObject.documentElement) Code used to retrieve the svg document. The declaration for svgDoc is local to the function it is implemented in and is unique. var svgDoc = document.getElementById('svgMap_div_1').ownerDocument; Code used to update the individual elements. When the identical svg objects (child elements have the same IDs) are in the same HTML DOM, then this javascript will always address the svg object that occurs first in the HTML DOM even if it is retrieved from a discrete div for example id='svgMap_div_2'. var svgElem = svgDoc.getElementById('elemID'); svgElem.setAttribute("fill", '#0000FF'); I believe that the root of the problem is that svgDoc is retrieved using ownerDocument. Rather than create a discrete object with scope and context, it reaches through to the entire DOM so that the DOM is the context. See the code reference from the IDE: HTMLDocument Node.ownerDocument Property ownerDocument http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html See Also: Document Since: Standard ECMA-262 3rd. Edition Level 2 Document Object Model Core Definition. #type Document I can develop a working algorithm to make all of the ids unique if I have to but I sure would like to find a better way. I have experimented with other methods and properties to get a working svg document object but none of these are working for me. Below is a working example to demonstrate what happens when both svg objects are in different div on the same page. All four combinations change the color of the first rectangle. If you change the ID for the rect in the second object to 'box-b' and address it accordingly ('bbox_div', 'box-b'), then both rectangles are operated on independent from one another. <script type='text/javascript'> function colorBox(divID, svgElem, colorHex) { /* DOM This is the original solution that did not work !!!*/ // var svgDoc = document.getElementById(divID).ownerDocument. // var svgBox = svgDoc.getElementById(svgElem); // svgBox.setAttribute("fill", colorHex); /* querySelector */ // document.querySelector("#"+divID + " ."+svgElem).setAttribute("fill", colorHex); /* jquery div */ // var svgBox = $('#'+divID+' svg').find('#'+svgElem); // svgBox.css("fill", colorHex); /* jquery class */ $('#'+divID+' svg').find('.'+svgElem).css("fill", colorHex); } </script> <div style="height: 100px; width: 100px"> Box A Red<br> Box A Blue<br> Box B Red<br> Box B Blue<br --> </div> <div id="abox_div" style="height: 100px; width: 100px"> <svg xmlns="http://www.w3.org/2000/svg" width="0.990919in" height="0.597218in" viewBox="0 0 71.3461 42.9997"> <style type="text/css"> <![CDATA[ .allboxes {fill:#00FF00} ]]> </style> <g class="box-a allboxes" transform="translate(0.24,-0.24)" fill="#e8eef7"> <rect x="0" y="0.48" width="70.8661" height="42.5197" ><title>Box A</title></rect> </g> </svg> </div> <div id="bbox_div" style="height: 100px; width: 100px"> <svg xmlns="http://www.w3.org/2000/svg" width="0.990919in" height="0.597218in" viewBox="0 0 71.3461 42.9997"> <g class="box-a allboxes" transform="translate(0.24,-0.24)" fill="#e8eef7"> <rect x="0" y="0.48" width="70.8661" height="42.5197" ><title>Box B</title></rect> </g> </svg> </div>
I believe it's strictly speaking invalid HTML if you have duplicate IDs. I'd suggest using some other mechanism of identifying the elements, either using classes instead of IDs or data- attributes. Then you could move from .getElemntById() to either .querySelector() (try on JS Bin): <html> <head> <title></title> </head> <body> <div id="div1"> <svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px"> <rect class="rect1" width="50px" height="50px"/> </svg> </div> <div id="div2"> <svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px"> <rect class="rect1" width="50px" height="50px"/> </svg> </div> <script type="text/javascript"> document.querySelector("#div1 .rect1").setAttribute("fill","red"); document.querySelector("#div2 .rect1").setAttribute("fill","green"); </script> </body> </html> or .getElementsByClassName() (try on JS Bin): <html> <head> <title></title> </head> <body> <div id="div1"> <svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px"> <rect class="rect1" width="50px" height="50px"/> </svg> </div> <div id="div2"> <svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px"> <rect class="rect1" width="50px" height="50px"/> </svg> </div> <script type="text/javascript"> document.getElementById("div1").getElementsByClassName("rect1")[0].setAttribute("fill","red"); document.getElementById("div2").getElementsByClassName("rect1")[0].setAttribute("fill","green"); </script> </body> </html> Probably .querySelector() would also work if you leave the ID attributes as they are, like: document.querySelector("#div1 [id=rect1]") but I'd still suggest using data- attributes as this avoids any potential ID pitfalls.
Since you are already using jQuery, you can use jQuery's selectors, which don't care about duplicate ids. function colorBox(divID, svgElem, colorHex) { var svgBox = $('#'+divID+' svg').find('#'+svgElem); svgBox.css("fill", colorHex); } Demo here: http://jsfiddle.net/w9KA3/1/
SVG / JS Interaction - THE SIMPLE VERSION
I'm trying to create an SVG-based menu. I'm completely new to SVG, and have been searching for 1.5 days for a simple example of interaction between JS and SVG. My document structure is: /LOCAL_FOLDER (not on a server yet) +index.html /CSS +global.css /JS +navigation.js /IMAGES +navigation.svg I have a simple html body <body> <div id="outer-container"> <div id="navigation-container" onclick="javascript:changeColor();" > <object id="navigation" type="image/svg+xml" data="images/test.svg" >Your browser does not support SVG</object> </div> </div> </body> My SVG looks like this (for now) <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <circle id="test" cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/> </svg> This is the general structure I want to use. As you can see, it has JS from the html head (linked) interacting with my SVG #test. For now, I'd deal with having it change opacity on load just so I can start figuring out HOW to interact. I've tried JQuerySVG, Raphael, straight JS, JS within SVG, etc, and I can't seem to connect. What I'm looking for (and can't seem to find an operational snippet of) is a super basic example that I can then learn from...
From what I've tried, I rarely had success using data="", however using something like container.load(your.svg), I could then modify to my hearts content. another issue is to make sure that the svg data was standard. from there (I used jQuery) jQuery('#test').attr('style','stroke:#ff0000') should change the stroke to red.
Cannot set opacity to element
I am trying to make and interactive svg which would react to some actions with javascript functions. My SVG looks like this (this is example of one of many svg I am generating, I deleted some irrelevant elements to make the code more readable): <svg contentScriptType="text/ecmascript" onmouseover="myOpacity('msg0', 0.5)" onclick="svgClick('Some example text')" width="760" xmlns:xlink="http://www.w3.org/1999/xlink" zoomAndPan="magnify" onmouseout="myOpacity('msg0', 1)" contentStyleType="text/css" height="30" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.0"> <text fill="black" x="10" id="msg0" font-size="10" y="20">Some text</text> <script xlink:href="script.js" xlink:actuate="onLoad" xlink:type="simple" xlink:show="other" type="text/ecmascript" xmlns:xlink="http://www.w3.org/1999/xlink"/> </svg> This is my script.js file with onClick and opacity functions: function svgClick(text) { alert(text); } function myOpacity(element_id, op_value) { element = document.getElementById(element_id); element.setAttribute('opacity', op_value); } The problem is that myOpacity function does not work and nothing happens when I hover over my objects (despite the id should correspond to the argument of the function). However, the onCLick function works perfectly, so the problem is probably with identifying the element by id. I am quite stuck here, could you take a look in the code and tell me where did I go wrong? EDIT: this is a followup from this answer: Interactive SVG - how to choose element to react on mouseover action? That code works there but it somehow does not do anything in the code I posted here. So my question is why? I know I could do this via attributes, but in that case, I do not know how to handle scenario, when I want to set opacity to one element when mouseover action is triggered on another one...
I pasted your code into a jsFiddle (making the JavaScript inline), and it works without problems in Firefox and Chrome: http://jsfiddle.net/wpZs6/ However, the hover part could be considerably easier with just a CSS hover selector: <svg width="760" height="30" xmlns="http://www.w3.org/2000/svg" version="1.0"> <style type="text/css"> svg:hover #msg0 {opacity:.5} </style> <text fill="black" x="10" id="msg0" font-size="10" y="20">Some text</text> </svg> See here: http://jsfiddle.net/L58z6/
try this : var divtmp = document.getElementById(element_id); var newStyle = "filter:alpha(opacity=85);-moz-opacity:0.85; opacity: 0.85;"; divtmp.setAttribute("style", newStyle );