Animate reused svg independently - javascript

I want some SVGs to animate on click. The SVG is defined once and has been reused multiple times with the <use> tag.
I wrote the following code:
svg {
border: 1px solid #0af;
stroke: black;
}
<svg display="none">
<symbol id="tick" viewBox="0 0 100 100">
<polyline points="15,50 40,75 85,25" style="stroke-width:10; fill:none; stroke-linecap:round; stroke-linejoin:round;" stroke-dasharray="120">
<animate attributeName="stroke-dashoffset" from="120" to="0" dur="0.5s" fill="freeze" repeatCount="1" begin="click" />
</polyline>
</symbol>
</svg>
<svg><use href="#tick"/></svg>
<svg><use href="#tick"/></svg>
<svg><use href="#tick"/></svg>
<svg><use href="#tick"/></svg>
The problem is, if I click on a single SVG, all the instances of that SVG are getting animated, which is not what I want. I want them to animate independently. What is the best way to do this other than embedding the complete SVG multiple times?

you can put your svg in a file and access it in document tree like this:
<object data="tick.svg"></object>

Your problem is duplicate ids
create a Web Component (supported in all modern Browsers) that injects your once defined SVG
<svg-tick id=ONE stroke="red"></svg-tick>
<svg-tick id=TWO></svg-tick>
<svg-tick id=THREE></svg-tick>
<svg-tick id=FOUR stroke="blue"></svg-tick>
You can add more Web Component goodies, like attributes and a single clickhandler...
I changed the polyline to path, makes it easier with a pathLength=100, no need for calculations
id not required, you can also generate a unique value with "ID"+new Date()/1
<style>
svg {
border: 1px solid #0af;
stroke: green;
width: 120px;
cursor:pointer;
}
#TWO svg { stroke: goldenrod; }
</style>
<svg-tick id=ONE stroke="red"></svg-tick>
<svg-tick id=TWO></svg-tick>
<svg-tick id=THREE></svg-tick>
<svg-tick id=FOUR stroke="blue"></svg-tick>
<script>
customElements.define('svg-tick', class extends HTMLElement {
connectedCallback() {
let id = this.id;
let stroke = this.hasAttribute("stroke")
? ` stroke=${this.getAttribute("stroke")} `
: "";
this.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path id="${id}" d="M15,50 40,75 85,25" pathLength="100"
stroke-width="10" fill="none" stroke-linecap="round" stroke-linejoin="round"
stroke-dasharray="100" ${stroke}/>
<animate id="${id}" attributeName="stroke-dashoffset" from="100" to="0"
dur="0.5s" fill="freeze" repeatCount="1" begin="click" />
</svg>`;
this.onclick = (evt) => console.log('clicked',id);
}
});
</script>
svga

Related

SVG tag and SVG image don't load when i use parcel build

I have a problem with my site, once i build the project with parcel, when i host the site with github pages some SVG imported Images (for example as background image in css) or some SVG tags (in html file) don't load.
For example the following SVG is not loaded:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" id= "clickPad1" height="210px" width="210px" class="" style="position: absolute; top:-5px; left:-5px;>
<circle id="circleClick" r ="5" fill="white"></circle>
</svg>
Instead the following is loaded:
<svg style="position: fixed; top:35px; left:36px" width="20" height="20">
<circle r="2" fill="black">
<animate attributeName="cx" values="2;8;3;5;4;2" dur="8s" repeatCount="indefinite" />
<animate attributeName="cy" values="2;3;2;8;5;2" dur="10s" repeatCount="indefinite" />
</circle>
</svg>
In the first svg the circle is missing the attributes cx and cy. Now, I don't know the context, but make sure that the fill color sticks out. I changed it to black. The style attribute in <svg> was missing a quote-mark ("). The attributes with and height don't need the length type (px).
<svg xmlns="http://www.w3.org/2000/svg" id="clickPad1" height="210" width="210" style="position: absolute; top:-5px; left:-5px;">
<circle id="circleClick" cx="20" cy="20" r="5" fill="black"/>
</svg>

How do I style one of the parameters in a circle inside an SVG?

I have a SVG with circles inside it. And I want them to be increasing and decreasing in radius for ever (like a pulsating circle).
My problem is, can I do it with #keyframes? Or do I need jquery? And if so, how?
Here is my code:
<div class="mapa">
<svg (svg code here......)
<circle opacity="0.3" cx="842" cy="451.814" r="25.582" id="1"/>
<circle opacity="0.3" cx="542" cy="405.814" r="25.582" id="1"/>
</svg>
</div>
How do I style the 'r' parameter?
I read I cannot style the 'r' parameter, but this worked:
<circle cx="168" cy="179" r="59"
fill="white" stroke="black"
onmouseover="evt.target.setAttribute('r', '72');"
onmouseout="evt.target.setAttribute('r', '59');"
/>
However, I want to do it with continuous increase and decrease in radius? And not on mouseover/mouseleave. Something like (r=25, then r=30, then back to 25, and goes on forever). How do I do this?
Thanks for your time, if you can give me any tips I'd apreciate it a lot!
Try to use svg smil animate
<svg width="150" height="150">
<circle opacity="0.3" cx="84%" cy="45%" r="3" id="1">
<animate attributeName="r" values="3; 10; 3" keyTimes="0; 0.5; 1" dur="1s" repeatCount="indefinite" />
</circle>
<circle opacity="0.3" cx="50%" cy="50%" r="10" id="2">
<animate attributeName="r" values="10; 3; 10" keyTimes="0; 0.5; 1" dur="1s" repeatCount="indefinite"/>
</circle>
</svg>
The easiest solution in CSS with a little hack around SVG containers. You change the container, not the svg. The circle element just fills 100% of the container. And the container artificially makes a circle with a border-radius.
svg {
border-radius: 50%;
transition: all 1s;
}
svg:hover {
width: 200px;
height: 200px;
}
<svg width="100" height="100">
<circle cx="50" cy="50" r="100%"
fill="green" />
</svg>
You can figure out how to implement your own keyframes, if this solution works for you.
And just to be clear, JQuery is a framework. You shouldn't bring up JQuery unless this question is about JQuery's framework. The language you're looking for is "Javascript" and it's in all major browsers by default. You can use Javascript to do this.
const grow = function(radius) {
var circle = document.getElementsByTagName("circle")[0];
circle.setAttribute('r', radius);
}
setTimeout(function() {
grow(100);
setTimeout(function() {
grow(40);
}, 2000);
}, 2000);
circle {
transition: all 1s;
}
<svg width="200" height="200">
<circle cx="100" cy="100" r="40"
fill="green" />
</svg>

How to use and style (selector) nested symbols in SVG

I have something like the following:
<html>
<head>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
grove > tree > triangle {
/* some triangle styles */
}
#grove1 > tree > triangle {
/* some triangle styles for grove 1 */
}
#grove2 > tree > triangle {
/* some triangle styles for grove 2 */
}
#grove3 > tree > triangle {
/* some triangle styles for grove 3 */
}
#grove3 > tree {
/* animate the tree */
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3000 3000">
<defs>
<symbol id="triangle" viewBox="0 0 100 100">
<polygon points="0,100 50,0 100,100" class="triangle" />
</symbol>
<symbol id="tree" viewBox="0 0 100 100">
<use href="#triangle" width="100" height="100" />
</symbol>
<symbol id="grove" viewBox="0 0 100 100">
<use href="#tree" width="10" height="10" />
<use href="#tree" width="10" height="10" x="20" />
<use href="#tree" width="10" height="10" x="40" />
<use href="#tree" width="10" height="10" x="60" />
<use href="#tree" width="10" height="10" x="80" />
</symbol>
</defs>
<use id="grove1" href="#grove" x="10" y="10" height="10"/>
<use id="grove2" href="#grove" x="30" y="100" height="100"/>
<use id="grove3" href="#grove" x="50" y="600" height="600"/>
</svg>
</body>
</html>
Notice that symbols are nested 3 levels deep. And the sizing definitions change at each symbol. For example, in the grove symbol, the viewBox is 100x100, but the trees which it contains also have their own view box of 100x100. The trees are sized to 10x10 in the grove, so they have a different scaling system.
Basically though, I compose these symbols into the grove symbol, then add those using use in 3 different places. Each one appears larger/closer than the one before. Or that's how it should work.
The question is how I can change the colors on these symbols efficiently and properly (best-practice). If I can do something like the example CSS above:
grove > tree > triangle {
/* some triangle styles */
}
Or if I have to use JavaScript somehow to change the styles in this situation, or otherwise not really use symbols at all, or create symbol for every color variation I want (somehow).
Please let me know how I should structure this SVG system so I can properly style each chunk of stuff (each "grove" for example, and its contents).
Another example I'd like for this to handle is, say each tree has branches. I want to have 5 rows of trees, as if it's parallax and each row is further in the distance than the one before, and as such they get lighter and lighter. So I would like to set the color of the branches and the "leaves" (main tree triangle) to be lighter and lighter as they move back. In addition, maybe I want to slightly change the color of the trees at a specific row. So I should be able to do something like this:
#grove1 > tree > branch { /* style */ }
#grove1 > #tree1 { /* style */ }
#grove1 > #tree2 { /* style */ }
#grove1 > #tree2 > #branch1 { /* style */ }
#grove1 > #tree2 > #branch2 { /* style */ }
#grove1 > etc...
Basically how to style the nested symbols individually. If it's not possible, then what the design pattern is I should be using instead.
Styles can inherit into the children of a <use> see below:
<html>
<head>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
#grove {
/* some triangle styles */
}
#grove1 {
/* some triangle styles for grove 1 */
fill: red;
}
#grove2 {
/* some triangle styles for grove 2 */
fill: green;
}
#grove3 {
/* some triangle styles for grove 3 */
fill: gold;
}
#grove3 > tree {
/* animate the tree */
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3000 3000">
<defs>
<symbol id="triangle" viewBox="0 0 100 100">
<polygon points="0,100 50,0 100,100" class="triangle" />
</symbol>
<symbol id="tree" viewBox="0 0 100 100">
<use href="#triangle" width="100" height="100" />
</symbol>
<symbol id="grove" viewBox="0 0 100 100">
<use href="#tree" width="10" height="10" />
<use href="#tree" width="10" height="10" x="20" />
<use href="#tree" width="10" height="10" x="40" />
<use href="#tree" width="10" height="10" x="60" />
<use href="#tree" width="10" height="10" x="80" />
</symbol>
</defs>
<use id="grove1" href="#grove" x="10" y="10" height="10"/>
<use id="grove2" href="#grove" x="30" y="100" height="100"/>
<use id="grove3" href="#grove" x="50" y="600" height="600"/>
</svg>
</body>
</html>
You can style the <symbol> and it's children.
You can style the <use> element.
However you can't have selectors that cross the <use> "boundary".
#myuse > #mysymbol-child
will not work. You cannot use a selector to target a specific instance of that symbol. Except by inheritance from the <use> as I have done in my example.
A symbol is a definition. If you give it a direct style, then that symbol with whatever style it has, will be used everywhere. That includes things like animation. If you animate the symbol, all instances of it will be animated the same way.
If I remember correctly, the trick to do that is to set the properties you want to set differently for each instance of <use> to "inherit" or "currentColor" (or if they're inherited by default, don't set them, if you don't need to) and then place each <use> in a <g> and apply the style declarations to each <g>.
SVG - inherit multiple colors

Click events stop working after replacing attribute of <use> element in <svg> (Win7/IE11)

We are using multiple svg symbols for displaying icons.
<!-- defining them at the start of the page -->
<div id="icon-container">
<svg xmlns="http://www.w3.org/2000/svg">
<symbol xmlns="http://www.w3.org/2000/svg"
id="rect" ...>
<rect... />
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg"
id="circle" ...>
<circle... />
</symbol>
</svg>
</div>
<!-- using them in the page somewhere -->
<svg>
<use xlink:href="#rect"></use>
</svg>
In some cases we need to replace them later on with another icon (like on a collapse control), therefore I created a little helper function to change the xlink:href to the new symbol name.
$.fn.replaceSVGIcon = function(id) {
$(this).find('svg')
.andSelf()
.filter('svg')
.find('use')
.attr('xlink:href', '#' + id);
}
This works in every browser except for IE10 + IE11 on Windows 7 (but weirdly enough it works with Windows 8).
When you open the snippet below in IE11 and click on the red box everything is fine, but as soon as you start clicking on the element within, it breaks the whole page after the icon is changed for the first time.
(function($){
$.fn.replaceSVGIcon = function(id) {
$(this).find('svg').andSelf().filter('svg').find('use').attr('xlink:href', '#' + id);
}
})(jQuery);
clickFunction = function() {
var $elem = $('#icon');
if ($elem.find('use').attr('xlink:href') == '#rect')
{
$elem.replaceSVGIcon('circle');
} else {
$elem.replaceSVGIcon('rect');
}
};
#icon-container {
visibility: collapse;
display: none;
}
#icon {
height: 40px;
width: 40px;
fill: #454545;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="icon-container">
<svg xmlns="http://www.w3.org/2000/svg">
<symbol xmlns="http://www.w3.org/2000/svg" id="rect" viewBox="0 0 50 50">
<rect x="15" y="15" width="20" height="20"></rect>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="circle" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="10"></circle>
</symbol>
</svg>
</div>
<svg id="icon" onclick="clickFunction()">
<rect fill="red" height="40" width="40"></rect>
<use xlink:href="#rect"></use>
</svg>
Why is this happening and is this a known Internet Explorer bug? What are my options to work around this issue?
Yes, this is a known IE bug. https://connect.microsoft.com/IE/feedback/details/796745/mouse-events-are-not-delivered-at-all-anymore-when-inside-an-svg-a-use-is-removed-from-the-dom
If you can, you should set pointer-events: none; for the use tag in your CSS. It's crazy, but it should work.

Accessing SVG file directly from Javascript code

I have this HTML code, which is invoking my javascript code. The code is for a gauge. In the javascript code, I am trying to access a SVG file, and modifying the needle (of the gauge) to display the desired value. The code is working fine. However, I do not wish to call "object id" in HTML. I want to access SVG file through javascript directly, instead of using object id in HTML. I tried using el.setAttribute('data', 'gauge.svg'); But then svg_doc isn't able to retrieve the SVG image and modify the needle. Any help would be highly appreciated.
PS : I tried my best to be as thorough in explaining the problem. However, please let me know if I am unclear somewhere.
This is Gauge.png image which is embedded in the svg code I have pasted below https://sphotos-b.xx.fbcdn.net/hphotos-snc6/179594_10150982737360698_1827200234_n.jpg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g name="gauge" width="122px" height="127px">
<image xlink:href="gauging.png" width="122" height="127"/>
<circle id="led" cx="39" cy="76" r="5" style="fill: #999; stroke: none">
<animateColor id="ledAnimation" attributeName="fill" attributeType="css" begin="0s" dur="1s"
values="none;#f88;#f00;#f88;none;" repeatCount="0"/>
</circle>
<g id="needle" transform="rotate(0,62,62)">
<circle cx="62" cy="62" r="4" style="fill: #c00; stroke: none"/>
<rect transform="rotate(-130,62,62)" name="arrow" x="58" y="38" width="8" height="24" style="fill: #c00; stroke: none"/>
<polygon transform="rotate(-130,62,62)" points="58,39,66,39,62,30,58,39" style="fill: #c00; stroke: none"/>
</g>
<text id="value" x="51" y="98" focusable="false" editable="no" style="stroke:none; fill:#fff; font-family: monospace; font-size: 12px"></text>
</g>
</svg>
HTML+Javascript code
<head>
<title>SVG Gauge example</title>
<script>
function update1(){
var scale=100;
var value;
var value1 = 69;
var el=document.getElementById('gauge1');
if (!el) return;
/* Get SVG document from HTML element */
var svg_doc = el.contentDocument;
if (!svg_doc) return;
/* Rotate needle to display given value */
var needle_el = svg_doc.getElementById('needle');
if (!needle_el) return;
/* Calc rotation angle (0->0%, 260->100%) */
value = parseInt(value1);
scale = parseInt(scale);
if (value > scale) value = scale;
var angle = value / scale * 260;
/* On-the-fly SVG transform */
needle_el.setAttribute('transform','rotate('+angle+',62,62)');
}
document.addEventListener('load', update1, true);
</script>
</head>
<div>
<object id="gauge1" type="image/svg+xml" data="gauge.svg" width="127" height="122"/>
</div>
</html>
As robertc already mentioned, you can embed the javascript code into your SVG file:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g name="gauge" width="122px" height="127px">
<image xlink:href="gauging.png" width="122" height="127"/>
<circle id="led" cx="39" cy="76" r="5" style="fill: #999; stroke: none">
<animateColor id="ledAnimation" attributeName="fill" attributeType="css" begin="0s" dur="1s"
values="none;#f88;#f00;#f88;none;" repeatCount="0"/>
</circle>
<g id="needle" transform="rotate(0,62,62)">
<circle cx="62" cy="62" r="4" style="fill: #c00; stroke: none"/>
<rect transform="rotate(-130,62,62)" name="arrow" x="58" y="38" width="8" height="24" style="fill: #c00; stroke: none"/>
<polygon transform="rotate(-130,62,62)" points="58,39,66,39,62,30,58,39" style="fill: #c00; stroke: none"/>
</g>
<text id="value" x="51" y="98" focusable="false" editable="no" style="stroke:none; fill:#fff; font-family: monospace; font-size: 12px"></text>
</g>
<script type="text/javascript">
var scale=100;
var value;
var value1 = 69;
/* Rotate needle to display given value */
var needle_el = document.getElementById('needle');
/* Calc rotation angle (0->0%, 260->100%) */
value = parseInt(value1);
scale = parseInt(scale);
if (value > scale) value = scale;
var angle = value / scale * 260;
/* On-the-fly SVG transform */
needle_el.setAttribute('transform','rotate('+angle+',62,62)');
</script>
</svg>
I've put the code below the actual SVG contents so that the document is already loaded when the script is executed.
Then, you can view the SVG file directly e.g. in Firefox (I've tested it right now).

Categories

Resources