I need to animate a circle that bounce 3 times and hits a wall and return. it should follow a given path. i have tried it with animateMotion. This is how it is so far,
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>animation</title>
<!--<rect x="15" y="5" rx="5" ry="5" width="20" height="10" style="fill:#CCCCFF;stroke:#000099">-->
<circle cx="0" cy="50" r="15" fill="blue" stroke="black" stroke-width="1">
<animateMotion dur="6s" repeatCount="indefinite" rotate="auto">
<mpath xlink:href="#path1"/>
</animateMotion>
</circle>
<!--</rect>-->
<path id="path1" d="m21,39c0,0 46,-44 79,-1c33,43 62,58 97,26c35,-32 86,-30 86,
-31c0,-1 61,-9 29,43c-32,52 -19,51 -87,51c-68,0 -158,-5 -158,-6c0,-1 -40,-11 -41,-12 Z"
stroke-width="5" stroke="#000000" fill="none"/>
</svg>
actually something like below is what i am expecting, as i am new to the area, appreciate any guidance or support.
In order to create the impression that the ball is bouncing organically, you probably want a trajectory that looks a bit more like this:
In addition, you do not want a linear timing function. A linear timing function means the element will move at the same speed throughout the animation. But, the closer a ball is to the apex of a bounce, the slower the ball is going. Also consider that it is going faster at the start and end of the tallest bounce than it is at the start and end of the shortest bounce.
Based on the aforementioned info about the behaviour of a bouncing ball, we might guess that the timing function for one bounce should look something like this:
It starts out fast, slows down, and then speeds up again.
This diagram represents a cubic-bezier curve, also known as a spline. This particular cubic-bezier can be written as 0.1 0.8 1 0.3 - the x and y coords of the first control point (P1) followed by the x and y coords of the second control point (P2).
Combining the trajectory and the timing function:
jsfiddle. The timing needs to be refined a bit, but that's the general idea.
The <animateMotion> element requires four additional attributes to make this happen:
keyPoints="0;0.5;0.75;1"
keyTimes="0;0.35;0.6;1"
calcMode="spline"
keySplines="0.3 0.5 0.8 0.7;0.3 0.5 0.8 0.7;0.3 0.5 0.8 0.7"
what these mean:
keyPoints="0;0.5;0.75;1" - I've decided to split up the path into three sections, each leg of which is associated with a start and end time (from keyTimes) and a timing function (from keySplines). The values are between 0 and 1, separated by semi-colons, where 0 is start of path and 1 is end of path.
keyTimes="0;0.35;0.6;1" - the total duration of the animation is segmented according to these times. There should be exactly as many keyTimes times as there are keyPoints. The values are between 0 and 1, separated by semi-colons, where 0 is start and 1 is the total duration of the animation.
calcMode="spline" - indicates that we want the timing function to be cubic-bezier. setting calcMode to spline means we also have to set keySplineson this element.
keySplines="0.3 0.5 0.8 0.7;0.3 0.5 0.8 0.7;0.3 0.5 0.8 0.7" - each leg of the trajectory gets its own timing function. (So, there should be one fewer keySplines value than there are keyTimes. Here, I've used the same timing function for each, 0.3 0.5 0.8 0.7. Since there are four keyTimes, there are three keySplines values because the trajectory has three legs. (Again, the actual values that I used need to be refined but that's the general idea.)
If you want your circle to follow the path, you need the circle's position to be (0,0), since the motion animation will be relative to the circle's current position:
<circle cx="0" cy="0"...
You can also remove rotate="auto" since it isn't of any use in this case.
See jsfiddle
Related
I am working on a svg graphic that will represent health bar in a game, thus far it is looking like this https://jsfiddle.net/8ds9hpuv
Concept is to have responsive bar that decreases / increases in width based on character health.
Right now I can't figure out how to decrease this path in width, but maintain that rounded edge on the right side all the time.
Ideally I would like to make it's height responsive as well
<svg width="428" height="35">
<path d="M0 0h414.333785C423.444595 9.346449 428 15.179782 428 17.5c0 2.320218-4.555405 8.153551-13.666215 17.5H0V0z" fill="red"/>
</svg>
I've modified the path by changing every command to lowercase (using this tool: Convert SVG path to all-relative or all-absolute ) but I've left the last H command to uppercase since H0 is going back to x="0"
Next I'm replacing the first h command with the variable healthIndicator
I'm assuming that the tip of the arrow has only an aesthetical function.
For the sake of the demp I'm using an input type range to change the value of the healthIndicator. I hope this is what you need.
itr.addEventListener("input",()=>{
let healthIndicator = itr.value;
let d = `M0,0 h${healthIndicator}c9.111,9.346,13.666,15.18,13.666,17.5c0,2.320218,-4.555405,8.153551,-13.666215,17.5H0v-35z`;
thePath.setAttributeNS(null,"d", d);
})
svg{border:1px solid}
<svg viewBox="0 0 550 35" >
<path id="thePath" d="M0,0
h414
c9.111,9.346,13.666,15.18,13.666,17.5
c0,2.320218,-4.555405,8.153551,-13.666215,17.5
H0z" fill="red"/>
</svg>
<input type="range" id="itr" value="414" min="0" max="500" />
I'm trying to create an infinite animation loop of an SVG circle.
I want to create 12 equal pieces and separate it by some gap.
To calculate value of circle pieces I used k coefficient from an table below
So I did 0,25782 * 160 (diameter of my circle) and I got: 41.2512 (it's should be a value of my pieces).
After that I created strokeDasharray prop via that value: 40 1.2512 I thought that it should be correct value.
Looks like it is but when I changed the strokedashOffset prop I saw some artifact at the right side. I don't know why it's happened and how I can fix it (and where I did an mistake)?
Thanks for any help.
Demo here (just change the strokedashOffset to a 408 value and you will see that issue).
https://jsfiddle.net/q8enje9o/
Here my pure svg code
<svg version="1.1" id="svgout_dasharray" baseProfile="full" width="500"
height="500" viewBox="0, 0, 500, 500" xmlns="http://www.w3.org/2000/svg" style="border: 1px solid black">
<defs></defs>
<circle cx="150" cy="150" r="80" fill="none" stroke="#ff0000" style="stroke-width: 30;stroke-dasharray: 40, 1.2512;stroke-dashoffset: 380;"></circle>
</svg>
Table of k coef. n - count of circle pieces
Here is the formula how you can calculate those coef. by itself
n - count of pieces
360 - 2*PI (a whole circle)
k - our coef. that we want to find
P.S. Here is a demo with the issue after update (Chrome latest & Windows 10)
The circumference of the circle / sum of the stroke-dasharray values needs to be an integer if you want evenly spaced lines and it isn't in your case.
So you probably want something like stroke-dasharray: 40, 1.8879; which should work with any stroke-dashoffset value.
Your question is a little confusing because you talk a lot about how you are calculating the dash array, but complain about things looking funny when you change the dash offset.
The circumference of a circle is 2 * PI * radius. If you want a n sections in your circumference, then the "dash + gap" in your dash array needs to sum to:
(2 * PI * radius) / n
so for 12 sectors, and a radius of 80, that value would be
(2 * PI * 80) / 12 = 41.8879
As Robert said, `stroke-dashoffset="40 1.8879" should work. And indeed it does.
<svg width="500" height="500" style="border: 1px solid black">
<circle cx="150" cy="150" r="80" fill="none" stroke="#ff0000"
style="stroke-width: 30;stroke-dasharray: 40 1.8879;"/>
</svg>
Now you also talk about dash offset. I don't know why you want to change the dash offset. I guess you are trying to make the dashes rotate around the circumference of something. Is that right?
If so, then that should work - as long as you are accurate with your dash array values. See below.
<svg width="500" height="500" style="border: 1px solid black">
<circle cx="150" cy="150" r="80" fill="none" stroke="#ff0000"
style="stroke-width: 30;stroke-dasharray: 40 1.8879; stroke-dashoffset: 408;"/>
</svg>
I'm writing a document in HTML5 with included SVG (actually ePub3), and I wanted to have a path in the SVG going from one character position in a text element to another. I found a way to do it, but it seems like a particularly awkward way, and I am wondering if anyone has any tips for improving it.
A bit more context: the particular illustration is to demonstrate how multiplication by integer powers of 10 shifts the decimal point around in decimal representations of numbers (particularly for converting between percentages and decimal representations, multiplying or dividing by 100). So I want the decimal number in a text element, and an arrow (specified as a path element with an arrowhead marker on one end) under it showing the shift of the decimal point from one position to another. But I want to do in in a general enough way that the arrow stretches for me if I need to change the font size or add digits between the start and stop positions of the arrow.
Here is what I came up with:
<svg id="shift" height="40" width="85">
<defs>
<marker id="arrowhead" markerWidth="6" markerHeight="6" refX="3" refY="0" >
<path d="M 0 6 L 3 0 6 6" stroke="black" fill-opacity="0" rotate="90"/></marker>
</defs>
<text id="fullstring" x="0" y="15" fill="black" font-family="serif" font-size="100%">10<!--
--><tspan id="firstdec" fill="blue">.</tspan>0000<tspan id="seconddec" fill="red">.</tspan>00</text>
<path id="decarrow" stroke="black" fill="white" stroke-width="1" d=""
style="marker-end: url(#arrowhead);"/>
<script type="text/javascript"><![CDATA[
var arrowpath = document.getElementById("decarrow");
var firstdecbox = document.getElementById("firstdec").getExtentOfChar(0);
document.getElementById("fullstring").setAttribute("y",""+firstdecbox.height);
document.getElementById("shift").setAttribute("height",""+(1.4*firstdecbox.height+15));
document.getElementById("shift").setAttribute("width", ""+document.getElementById("fullstring").getComputedTextLength());
var seconddecbox = document.getElementById("seconddec").getExtentOfChar(0);
var startx = firstdecbox.x+firstdecbox.width/2;
var starty = firstdecbox.y+firstdecbox.height;
var stopx = seconddecbox.x+seconddecbox.width/2;
var stopy = seconddecbox.y+seconddecbox.height;
var bendheight = 15;
arrowpath.setAttribute("d", "M "+startx+" "+starty+" L "+ startx+" "+(starty+bendheight)+" "+stopx+" "+(stopy+bendheight)+" "+stopx+" "+stopy);
]]></script>
</svg>
I can't help thinking that there must be a better way to do this. What I really want is a way to specify a path with starting point and ending point based on character positions in a text element. Is there such a way to specify path data relative to character positions? Or have I really stumbled my way onto the best available option?
This is my first SVG project, and I’m not a programmer, but I dabble in interactive infographics. My previous experience in this area comes from working with ActionScript.
I’m using plain SVG (no Raphael, D3, etc.) and trying to create an interactive barchart. After some initial difficulty with the SVG coordinate system and scaling, I found some code online that handles the postscaling translation:
<text x="x_coord0" y="y_coord0" transform="scale(x_scale, y_scale) translate(-x_coord0*(x_scale-1)/x_scale, -y_coord0*(y_scale-1)/y_scale)" …>text</text>
And I converted it into this JavaScript:
var translationfactor = ((0 - y_position)*(y_scalefactor - 1) / y_scalefactor);
var matrix = "scale(1," + y_scalefactor + ") translate(0," + Number(translationfactor) + ")";
targetbar.setAttribute("transform", matrix);
The problem is that I need the bars “translated” back to the chart’s baseline, not the original locations of their topmost points. Currently the correctly scaled bars are hugging the top of the chart:
http://billgregg.net/miscellany/upsidedown-barchart.png
I’ve tried several fixes, including plugging the bars’ ”missing height” into translationfactor (the bars start out the full height of the chart and get scaled down dynamically). Nothing has worked. Part of my problem is that, besides being new to SVGs, I can stare at that code all day and my brain still can’t parse it. Multiplying negative numbers is too abstract and at a fundamental level I just don’t “get” the math, which of course makes modifying the code difficult.
My questions:
(1) What’s the fix for the code above to position the bars back on the baseline of the chart?
(2) Is there a more transparent, more pedestrian way of accomplishing the translation? My first thought along these lines was that if a bar’s height is reduced to 40% of its original value, then multiplying the original Y coordinate value by 250% should reset the bar to its original location (at least its topmost point), but that doesn’t seem to work.
(3) Is there a way to set a bar’s point of origin to its bottom? In Flash it’s possible, though as far as I know it’s a manual, not a programmatic task.
(4) Is there a method similar to .localToGlobal() in ActionScript that would allow me to avoid having to mess with the local coordinate system at all?
Behind the scenes there is matrix math going on and it can be hard to get your head around the pre and post multiplication of arrays.
It's not entirely clear what you are trying to achieve, but reading between the lines, it sounds like you are wanting to provide graph coordinates in their raw(ish) form and have the SVG scale and position them for you(?)
If that's the case, then I think the solution is simpler than what you think.
Assuming I'm right, we'll start with something that looks like this:
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g transform="">
<rect x="0" width="1" height="5" fill="red"/>
<rect x="1" width="1" height="11" fill="green"/>
<rect x="2" width="1" height="12" fill="orange"/>
<rect x="3" width="1" height="8" fill="blue"/>
</g>
</svg>
Where x is obvious and the bar length is in height. y defaults to 0, so we don't need it here.
You basically want to know what goes in the transform to scale and position the bars on your page. The fact that your graph is "upside-down" helps a little. Because the origin in an SVG is at the top left.
First apply a scale. Let's make the bars 20 pixels wide, and scale the lengths up by 10.
<g transform="scale(20,10)">
Next you want to position the graph on the page. Let's put the top-left corner at (40,40).
In SVG the transformations are concatenated in order (post-multiplied) so in order for the translation to be what you specify and not be multiplied by the scale, you should put it first.
<g transform="translate(40,40) scale(20,10)">
So the final SVG looks like this:
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g transform="translate(40,40) scale(20,10)">
<rect x="0" width="1" height="5" fill="red"/>
<rect x="1" width="1" height="11" fill="green"/>
<rect x="2" width="1" height="12" fill="orange"/>
<rect x="3" width="1" height="8" fill="blue"/>
</g>
</svg>
The above has been simplified by assuming you have already subtracted the values from your base 20%. If you wanted to keep the pure raw values, it's possible, but things get a bit trickier. You would need to either tinker with both the y and height value of each bar, or use clipping to hide the part of the bar above 20%.
For "right way up"/normal graphs. All you need to do is make the y scale negative and translate the graph so that the bottom-left is where you want it.
<g transform="translate(40,140) scale(20,-10)">
Hope this helps.
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.