Call a callback during animation [duplicate] - javascript

I have an embedded SVG in an HTML document. An (SVG) circle is animated using <animate>. I was trying to find a way to put some kind of event listener on that circle only when it moves horizontally.
Upon being moved (horizontally), I'd like to find the x-coordinates of the circle shape and set a third (outside) rect shape width to the relative position of the circle. This third rect would be like a progress bar tracking the horizontal progress of the circle.
Does the SVG circle (by the way, the circle is inside an SVG g-group) being moved by trigger some kind of event I can set a listener so that then I can change the width attribute of the sort of progress bar?
I have thought that if either the <animate> or the element moved/changed triggers some kind of event I could try to catch it and then change the width on the bar.
I have found that it is not much good use an "independent" animate on the rect as the pace of growth is very different when the circle moves upwards. I am not using the canvas element because I am trying to keep the scalability and the shapes semantics. (I would rather prefer a javascript solution but I would be grateful for other approaches.)
EDIT after answer: The anser have ben very much to the piint and (I think) helpful. I am very new to SVG and I may have misinterpreted something. Fot that reason I am including code.
I have tried to implement your recommendations and I seem to have been unsuccessful. .cx.animVal.value applied to the circle does not seem to get me what I need. I will include a chopped version of my code which should move a ball along a path which itself is being moved horizontally; two rects (inBar and outBar) should be tracking the horizontal displacement growing horizontally more or less at the same rate as the ball. In order to make sure setInterval works and the position is correctly gathered, a line has been added to list oBall..animVal and oball..baseVal. In FF 21.0, there is no change for animVal along the displacement. Have I understood your suggestions correctly? here follow the code (including headers etc. as I am a noob in SVG in particular):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head><title>Motion</title>
<script>function beginAnim(anim,sPos){anim.beginElement();}</script>
</head>
<body>
<div id="here">
<button onclick="beginAnim(document.getElementById('anim'),'out');">START</button>
</div>
<div style="height:350px;">
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
<script type="text/ecmascript">
<![CDATA[
function trckngBars(){
oDiv=document.getElementById('here');
var oBall=document.getElementById('ball');
var oBar=[document.getElementById('inBar'),document.getElementById('outBar')];
idTimer=self.setInterval(function(){updtBars(oBall,oBar);},100);
}
function updtBars(oBall,oBar){
var xCoor=String(oBall.cx.animVal.value);
oDiv.innerHTML+='==>'+oBall.cx.animVal.value+'..'+oBall.cx.baseVal.value;
oBar[0].setAttribute("width",xCoor);
oBar[1].setAttribute("width",xCoor);
}
// ]]>
</script>
<defs>
<path id="throw" d="M0,0 q 80,-55 200,20" style="fill: none; stroke: blue;" />
</defs>
<g>
<g>
<rect x="2" y="50" width="400" height="110" style="fill: yellow; stroke: black;"></rect>
</g>
<g>
<!-- show the path along which the circle will move -->
<use id="throw_path" visibility="hidden" xlink:href="#throw" x="50" y="130" />
<circle id="ball" cx="50" cy="130" r="10" style="fill: red; stroke: black;">
<animateMotion id="ball_anim" begin="anim.begin+1s" dur="6s" fill="freeze" onbegin="trckngBars();" onend="window.clearInterval(idTimer);">
<mpath xlink:href="#throw" />
</animateMotion>
</circle>
<rect id="inBar" x="50" y="205" width="20" height="30" style="fill: purple;stroke-linecap: butt;">
<!-- <animate attributeType="XML" attributeName="width" from="0" to="200" begin="ball_anim.begin" dur="6s" fill="freeze" /> -->
</rect>
</g>
<animateTransform id="anim" attributeType="XML" attributeName="transform" type="translate" from="0" to="200" begin="indefinite" dur="10s" fill="freeze" />
</g>
<rect id="outBar" x="50" y="235" width="10" height="30" style="fill: orange;stroke-linecap: butt;">
<!-- <animate attributeType="XML" attributeName="width" from="0" to="400" begin="anim.begin+1s" dur="10s" fill="freeze" /> -->
</rect>
</svg>
</div>
</body>
</html>
If the code is run, it seems that animVal for the moving #ball remains at the same x-coordinat (50) while clearly it is moving.

An event is fired when animations begin, end or repeat but not (as you want) whenever there is a change of animation value.
As animations are deterministic though you can just start the rect shape animation so many seconds after the circle animation starts.
var cx = myCircle.cx.animVal.value;
will give you the animated value if you need it, provided that's the attribute you're animating.
You're using animateMotion rather than animating the cx and cy attributes on their own though. I'm think the only way to get the circle position post that transform is to call getBBox.

#Robert Thank you very much for your help. Your answer has been a good plunge into SVG and SMIL (and let me add cold). I have not been able to use getBBox, but inspecting the specification on paths ([link] http://www.w3.org/TR/SVG11/paths.html) and animateMotion (same site), it apears that can be achieved as SMIL animations are deterministic as suggested in your answer.
An animation has very few event triggers and by design seem as much concerned with the base state of the animation target as it is with the current position (theseem to be referred as "base values" and "presentation values"). (All the following works in javascript run by FF 21.) We can poll the current time of the animation applying getCurrentTime on the animateMotion object. I am assuming that the animation does it at constant velocity, so with that, we determine how much the object has moved along the path and obtain the length traversed (as we can get the total length of the whole path with method getTotalLength).
Then knowing the length, we can determine the current position on the path (using method getPointAtLength). Note, that the values returned, both time and position are relative to the container object, and thus they are scalable and/or require transformation).
For a (simple) working example, the javascript code in the Question sample code can be replaced by the following. It appears to work with the very few tests I have made:
function trckngBars(){
/* Upon beginning an animation (onbegin event), the required objects are gathered
and an interval is set */
var oBall=[document.getElementById('throw'),document.getElementById('ball_anim')];
var oBar=document.getElementById('inBar');
/* idTimer is set as a global variable so that it can be accessed from anywhere
to clear the interval*/
idTimer=self.setInterval(function(){updtBars(oBall,oBar);},50);
}
function updtBars(oBall,oBar){
/* This function, whose purpose is only to illustrate path method getPointLength
and animateMotion method getCurentTime, is quick and dirty. Note that oBall[0] is
the path and oBall[1] is the animate(Motion) */
//Calculates the amount of time passed as a ratio to the total time of the animation
var t_ratio=((oBall[1].getCurrentTime()-oBall[1].getStartTime())/oBall[1].getSimpleDuration());
// As mentioned, it assumes that animateMotion performs uniform motion along path
var l=oBall[0].getTotalLenth()*t_ratio;
// Gets (relative referred as user in documentation) horizontal coordinate
var xCoor=oBall[0].getPointAtLength(l).x;
oBar.setAttribute("width",xCoor);
}
function endTAnim(){
/* This function can be triggered _onend_ of an animation to clear the interval
and leave bars with the exact last dimensions */
window.clearInterval(idTimer);
var oBar=[document.getElementById('inBar'),document.getElementById('outBar')];
oBar[0].setAttribute("width",200); //hardcoded for convenience
}
Thus the simplest method I have been able to find requires the animation object (to obtain the time) and the path object (to "predict" the position) and it does not involve the actual element being moved by the animation. (It is somewhat simplifiedfrom the initial question to avoid discussing different coordinate systems when composed animations are used - this might be better discussed ia a stand-alone way.)
Though I have not noticed any lag (as the actual SVG is not much more complicated), I would be interested in knowing computationally cheaper methods as I was considering using this approach to find and draw a distance segment between two SMIL animated objects.
Of course all this relies on the assumption of a uniform movement aong the path, if that were not so and in larger images one might notice and offset I would also be grateful for any pointers on that (short of better do the animation directly in javascript/programming language and so you have total control). Thank you for all te edits you did avoiding getting into a quagmire - the only thing I knew about SVG three days ago is that it was XML.

A while ago I ran into the same problem you are describing. I wanted to be able to stop animations halfway, based on events triggered by the user and keep elements at their reached position. Unable to do so with SMIL I decided to forge my own animation system for svg.js, a small javascript library I have been working on:
http://documentup.com/wout/svg.js#animating-elements
It might be useful for what you are trying to achieve.

Related

how to create d3 radial with dynamic radios

I created a radial with two tiers of options. I did in a way that isn't really dynamic and isn't really responsive to screen size. I now need it to be both of those things. Here is what it looks like when on the screen size I designed it for.
I created a working demo on sandbox that has the dimensions set how I need to use it on. This is what it looks like.
Here is link WORKING DEMO
any help is appreciated. Also keep in mind the outer tiers can have less or more options. it would be great if the blue toggle button would always align at the bottom of the radial like under the En of Energy Loss
I would consider using an SVG ViewBox in order to maintain consistency. What this basically does is create a consistent scalable SVG, mapping the size and coordinates of its container into a consistent range inside the SVG.
For example:
<div height="400px" width="400px">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="100%" height="100%" stroke="red" fill-opacity="0"/>
<circle r="4" cx="10" cy="10"/>
</svg>
</div>
So it basicalley creates a mapping from the 400x400 dimensions of the div, into the 100x100 of the svg, so the circle positioned at (10, 10) inside the svg will actually be in coordinates (40, 40) of the div

Changing the colour of regions on an SVG circle tiled world map when hovered

I am attempting to make a world map made from an svg comprising many circles. I based this from a codepen I found here: https://codepen.io/mvaneijgen/pen/NRzENO
E.g.
<svg viewBox="0 0 845.2 458">
<circle class="st0" cx="826.1" cy="110.3" r="1.9"/>
<circle class="st0" cx="819.3" cy="110.3" r="1.9"/>
<circle class="st0" cx="819.3" cy="117.1" r="1.9"/>
<circle class="st0" cx="812.6" cy="90" r="1.9"/>
The map is great. I have been dividing it up into coloured continent regions using classes. These change colour when hovered over. All good so far. Most of my functionality is there.
The issue is that you have to be hovering directly on a circle to make the colour change happen. I am using a javascript mouseover event to change the colour.
Is there any way of increasing the area of effect around the circle elements? Maybe putting an invisible square either behind or in front? I am still getting to grips with front-end stuff and any pointers here would be great.
That’s the right idea: transparent rects behind each circle. (Or transparent continent-shaped paths based on geo data, depending on what you’re going for.)
The trick is to use the SVG CSS property pointer-events. Setting it to fill or all should do the trick.

clipPath circle not inheriting the correct position in Firefox

I'm using a circular clip path for my nodes (in d3.js) as follows:
<g class="node" id="140" transform="translate(392.3261241288836,64.3699106556645)">
<image class="mainImage" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="images/manual-story-140.jpg" width="100" height="116.66705555685185" x="-50" y="-50" clip-path="url(#140)">
</image>
<clipPath class="clipPath" id="140">
<circle class="clipPathCircle" stroke-width="1" r="42"></circle>
</clipPath>
<circle class="outlinecircle" stroke="#0099FF" fill="url(#myLinearGradient1)" stroke-width="3" r="42"></circle>
</g>
But in Firefox the images don't load because the circle element within the clipPath element doesn't inherit the position of the node (i.e. from the g element).
In Chrome/Safari, everything works great and when I open up the console and hover over the circle element that's within the clipPath element it clearly shows the circle in the correct place (with dimensions of 84x84 since the radius is 42).
But in Firefox I see no images, and when I hover over the circle using the console I see it's positioned at the top left of the screen with dimensions 0x0.
Any ideas what I'm doing wrong here? Do I have to give an absolute position of the circle for firefox or should it already understand from the g-element it's in?
Apologies for the false alarm, but the problem here (as you can see in my original code) was that I was using the same id on my parent g element as I was to reference my clipPath! I changed the "id" attribute for my clip path to start with the string "clipPath-" and now it works on Firefox. Not sure why that would affect different browsers differently (which is why I kinda went 'round the houses trying to troubleshoot the bug), but thankfully enough it's quite a trivial fix!

Cross-platform SVG rotation animation:

Hi I am currently working with some SVG animations that are to work on multiple platforms.
However I have an issue with Internet Explorer (of course).
I use Keith Woods, Jquery SVG extension.
I have mainly been using layers and show, hide to animate my pages but now I need to do a rotation of a line:
$('#topBar').stop().animate({
svgTransform: 'rotate('+angle+','+(545+amplification)+',-260)'}, time);
The SVG object:
<g
id="g3953">
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="topBar"
d="m 545,-260 322,0"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="-260"
x="545"
height="5"
width="5"
id="centerPoint"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
This partly works but the line called top bar rotates and translates in another manor than on Chrome and Firefox. I.e. it jumps around a little.
Anybody have a solution to this? can i do the translation differently or just use CSS- or other JQuery rotate method to get better support??
I have seen JQuery rotate but does it support translation?
See how the image jumps, below, this occurs when I just moved the point of rotation...
You may fiddle with the code...
IE does not support animations in SVG. The basic way to rotate across all browsers is to use the getBBox() for the container and rotate from the center of the bounding box. As Follows:
<svg id="mySVG" width=400" height="400">
<g id="containerG">
<polygon id="myPolygon" fill="yellow" stroke="blue" stroke-width="1" points="380,80 200,10 40,80 100,320 300,350" />
<circle id="myCircle" cx="170" cy="200" r="40" fill="lime" />
</g>
var deg=30
var bb=containerG.getBBox()
var bbx=bb.x
var bby=bb.y
var bbw=bb.width
var bbh=bb.height
var cx=bbx+.5*bbw
var cy=bby+.5*bbh
containerG.setAttribute("transform","rotate("+deg+" "+cx+" "+cy+")")
From personal experience, animations are not meaningfull to me in most svg applications. However, what is important, are transitions. By transitions, I mean the smooth movement of a graphic element's attributes as they visually change. I think currently the best cross-browser handling of svg transitions is by D3. The D3 api may be more than you want to get into at this time...But if you plan to work seriously with svg, you should give it a try.

enclosing the nodes of a d3 force directed graph in a circle or a polygon or a cloud

I have built a d3 force directed graph with grouped nodes. I want to enclose the groups inside cloud like structure. How can I do this?
Js Fiddle link for the graph: http://jsfiddle.net/Cfq9J/5/
My result should look similar to this image:
This is a tricky problem, and I'm not wholly sure you can do it in a performative way. You can see my static implementation here: http://jsfiddle.net/nrabinowitz/yPfJH/
and the dynamic implementation here, though it's quite slow and jittery: http://jsfiddle.net/nrabinowitz/9a7yy/
Notes on the implementation:
This works by masking each circle with all of the other circles in its group. You might be able to speed this up with collision detection.
Because each circle is both rendered and used as a mask, there's heavy use of use elements to reference the circle for each node. The actual circle is defined in a def element, a non-rendered definition for reuse. When this is run, each node will be rendered like this:
<g class="node">
<defs>
<circle id="circlelanguages" r="46" transform="translate(388,458)" />
</defs>
<mask id="masklanguages">
<!-- show the circle itself, as a base -->
<use xlink:href="#circlelanguages"
fill="white"
stroke-width="2"
stroke="white"></use>
<!-- now hide all the other circles in the group -->
<use class="other" xlink:href="#circleenglish" fill="black"></use>
<use class="other" xlink:href="#circlereligion" fill="black">
<!-- ... -->
</mask>
<!-- now render the circle, with its custom mask -->
<use xlink:href="#circlelanguages"
mask="url(#masklanguages)"
style="fill: #ffffff; stroke: #1f77b4; " />
</g>
I put node circles, links, and text each in a different g container, to layer them appropriately.
You'd be better off including a data variable in your node data, rather than font size - I had to convert the fontSize property to an integer to use it for the circle radius. Even then, because the width of the text isn't tied to the data value, you'll get some text that's bigger than the circle beneath it.
Not sure why the circle for the first node isn't placed correctly in the static version - it works in the dynamic one. Mystery.

Categories

Resources