animateMotion Controlling with script - javascript

i've made a Path with SVG, and i'm making a ball to move along it.
But is there anyway i can control de movement of the ball?
Like, the ball starts stoped, while i press the Right Arrow on the keyboard, the ball moves Clockwise, and when i press the left arrow, the ball moves Counter-Clockwise...
is it possible?
here is the svg code:
<svg width="800" height="800" viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Draw the outline of the motion path in grey, along
with 2 small circles at key points -->
<path d="M66,89.32c18,143,154,82,97,118s-187,38-152,102s114,105,163,74
s89-111,89-127s18-128,13-155s-29-67-57-80s-47-14-62-17s-17-8-32,0s-40,23-47,30s-11,20-16,24s-14,29-14,29L66,89.32z" stroke="lightgrey" stroke-width="2" fill="none" id="theMotionPath"/>
<!-- Here is a red circle which will be moved along the motion path. -->
<circle cx="" cy="" r="5" fill="red">
<!-- Define the motion path animation -->
<animateMotion dur="6s" repeatCount="indefinite">
<mpath xlink:href="#theMotionPath"/>
</animateMotion>
</circle>
I put my code here: http://jsfiddle.net/fPQv2/
Thanks!!

The best thing i can do, is that i've add this script, so i can pause and unpause the animation... but still not what im looking for....
var pause = document.getElementById('draw');
var unPause = document.getElementById('draw');
function parar(){
pause.pauseAnimations();
}
function voltar(){
unPause.unpauseAnimations();
}
$(window).keydown(function (e) {
if (e.keyCode == 39) {
setTimeout(voltar,10);
setTimeout(parar,500);
}
});

You can use accessKey but that only works with keys that translate to a character. For instance changing to this...
<animateMotion begin="accessKey(1)" end="accessKey(2)" dur="6s" repeatCount="indefinite">
<mpath xlink:href="#theMotionPath"/>
</animateMotion>
Means that you can start the animation by pressing 1 and stop it by pressing 2. If you wanted to run the animation backwards you'd need another path which runs back to front and another animateMotion which refers to that backwards path. That animateMotion could start and stop with accessKeys reversed. I've only tested this on Firefox but I believe this is implemented on Opera and probably elsewhere too.
Note that without scripting you can't pause an animation so when you press the access keys you'll see the animation restart from the beginning again. If you want pausing you'll need to call pauseAnimations() on the parent <svg> element when a key is pressed.
If you want something more interactive then SMIL is not the best solution, just position the ball at some x/y location using javascript. You can use getPointAtLength to figure out where on the path it should go.

if you want the animate as soon as element append page, set 'begin' to 'indefinite' and begin the animate call its .beginElement();
var animateMotion = document.createElementNS(SVG_NS, 'animateMotion');
animateMotion.setAttribute('begin', 'indefinite');
//append animateMotion to the page
animateMotion.beginElement();

You can use pauseAnimations() and control it using setCurrentTime(time). Those are methods for the svg element.

Related

Save end of SVG animation as png (quickly)

The following animated svg shows a point that is translated from left to right:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="432px" height="574px" viewBox="0 0 432 574">
<path d="M217.074,165.651c-36.607,0-66.268,29.589-66.268,66.307c0,36.597,29.66,66.274,66.268,66.274c36.642,0,66.367-29.678,66.367-66.274C283.441,195.241,253.716,165.651,217.074,165.651" fill="#CC0000">
<animateTransform attributeName="transform" type="translate" from="0" to="90" dur="0.01s" fill="freeze"/>
</path>
</svg>
I need to save the svg in the form it looks like at the end of the animation as png/jpg.
My current approach is to set a very short duration for the animation, stop it in the end using fill="freeze" and then take an automated screenshot in Chrome using Javascript. However, one screenshot takes around 0.3 seconds and I need to do it for thousands of svg's so I need to accelerate this. Is there another way to achieve that? Thank you!
There does not seem to be a way to achieve this.
My workaround in the end was to write a function that interpolates the animateTransform statements, inserts them into the SVG code, and stores one SVG per interpolation step.
The drawback is that this only works for animateTransform statements.

Overflow SVG does not register clicks on OSX/iOS

I have a small SVG canvas in my application which houses a button to click. Upon clicking that button, the small circle does some flashy work and opens up to an appropriate sized bubble to fit all the data. This data is just a list of data with custom bullets off to the left; some of this text is clickable (bound to click events) and causes other functions to be fired off. My problem is that the SVG canvas never changes in size. It's really only big enough to contain the original circle that the user clicks on. The rest of SVG elements that make up this control flow outside the bounds of the SVG canvas, but is all viewable due to the SVG having its overflow set to visible. Everything works fine in all browsers, but things change when I open up the application in an iPad or on a Mac (regardless of browser). It seems the overflow pieces of my SVG are completely invisible to the browser. Any click event that fires fine on the overflowed SVG elements on Window's browsers are completely invisible on OSX/iOS browsers (it seems like any click actually falls through to whatever is behind the overflowed SVG). At first I thought it was an issue with the click binding, but after searching and playing around with it, I think OSX/iOS just doesn't handle overflowed SVG very well.
To explain in its simplest form what's going on in my application, I have a very simple JSFiddle here: https://jsfiddle.net/yno8daka/
HTML:
<svg style="overflow: visible !important" class="canvas" width='70px' height='70px' xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle id="circle1" stroke="black" fill="black" stroke-width="1" cx="10" cy="10" r="10"></circle>
<circle id="circle2" stroke="black" fill="black" stroke-width="1" cx="110" cy="110" r="10"></circle>
</svg>
Javascript:
$('#circle1').click(function() {
alert('yeah');
})
$('#circle2').click(function() {
alert('yeah2');
})
Circle1 is in the bounds of the SVG canvas, while circle2 is not. On Windows browsers, you will be able to click on both circles without any issue. On OSX/iOS browsers, the click event will fire for circle1, but not for circle2.
Does anyone know how to get this to work?

Animate circle along sine curve

I am trying to replicate the visuals of f.lux using jQuery which looks like this (sorry for the poor quality). Basically, it is a circle following a sine wave path. I have looked at the jQuery.path animations and I tried replicating the animation, but my sine wave is choppy since it is drawn using CSS. Is there a way to draw the sine wave so it is smooth and then animate the circle following the sine wave path? I couldn't get a fiddle working but this is what my animation currently looks like now. My animation also does not loop back to where it starts unlike the f.lux animation.
I am not sure what the limits are for this either, will I need to make f.lux's background (the blue and pink halves of the sine wave) and then lay the animation on top of it or can I make the whole visual using Javascript, CSS, and jQuery?
Does anybody have any ideas or can anyone point me in the right direction?
To make your line smoother, you can use a svg element, like so :
<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M10 80 C 40 10, 65 20, 95 80" stroke="black" fill="#E0E0E0"/>
<path d="M95 80 C 105 100, 150 150, 180 80" stroke="black" fill="#EEEEEE"/>
</svg>
This uses two different bézier curves so you can use different colors for your background. You will have to change the values a bit to match you desired width/height and curvature. There is an explanation on svg paths here : https://developer.mozilla.org/en/docs/Web/SVG/Tutorial/Paths.
You can create an insert a svg element with javascript or jQuery SVG if you don't have access to the HTML source file.

SVG line animation not working in IE

I am using a carousel that animates the stroke-dasharray but it doesnt work in IE.
my SVG:
<svg class="facts__svgs" viewBox="-10 -10 220 220" data-facts-stoke-svg="">
<path d="M200,100 C200,44.771525 155.228475,0 100,0 C44.771525,0 0,44.771525 0,100 C0,155.228475 44.771525,200 100,200 C155.228475,200 200,155.228475 200,100 Z" stroke-dashoffset="651"></path>
</svg>
My JS that changes the stroke:
function calculateDashArray(percentage) {
return (dashOffset * 2) - (segmentOfDashOffset * percentage) - 20;
}
function animateFactsSVG(percentage) {
elPath.style.strokeDasharray = calculateDashArray(percentage);
}
In everything apart from IE this works. In IE the dasharray style is applied but the svg does not change.
I have taken your example and placed it in Internet Explorer and it didn't work. Then I started playing with the various properties to see how I could get to your desired effect. The only way I could make the change in the dasharray reflect graphically was to reset the 'd' attribute: elPath.setAttribute('d',elPath.getAttribute('d')); which made the dasharray show as required, but destroyed the animation. Another way to make the changes appear was to add a second comma separated parameter to dasharray, like '1008.58,100%', but it wouldn't create the desired effect either.
I have to conclude that Internet Explorer doesn't deal well with one value stroke-dasharray and you should probably look for another solution.
I actually made it work with a circle instead of a path like this:
<svg xmlns="http://www.w3.org/2000/svg" style="width:100%;height:100%" >
<circle cx="100" cy="100" r="100" stroke="green" stroke-width="1" fill="none" style="stroke-dasharray:228,628;transition: all .6s ease;" ></circle>
</svg>
<script>
var el=document.getElementsByTagName('circle')[0];
var circumference=2*Math.PI*(+el.getAttribute('r'));
function animatePercentage(per) {
el.style.strokeDasharray=(per/100*circumference)+','+((1-per/100)*circumference);
}
setInterval(function() {
animatePercentage(70);
},2000);
</script>
No animation, though. The value changes instantly on IE. Apparently this doesn't work in Internet Explorer, only Edge (see SVG animation is not working on IE11)
Updated the code and saved it in a CodePen here: http://codepen.io/anon/pen/wGPwYq

Catch changes on SVG element circle and change attributes elsewhere via js

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.

Categories