Using animateMotion along with keyTimes/keyPoints - animation

I am trying to use non-linear animation rate on an SVG <animateMotion> by using the keyTimes="…" and keyPoints="…" attributes. It does not appear to be working: the animation motion is as linear as can be.
Here's the test file try it!
<svg xmlns="http://www.w3.org/2000/svg" xmlns:x="http://www.w3.org/1999/xlink"
viewBox="0 0 300 200">
<style>
path { stroke:#999 }
circle { fill-opacity:0.5; stroke:black }
</style>
<path id="p" d="M30,160 L270,40" />
<circle id="c" r="5" />
<animateMotion x:href="#c" fill="freeze"
dur="10s"
keyTimes="0;0.1;1"
keyPoints="0;0.9;1">
<mpath x:href="#p" />
</animateMotion>
</svg>
When working the ball should move 90% along the path in the first second, and move the final 10% in the remaining 9 seconds. What do I need to change to get this to work?
I've found another example online that is working correctly, so that I know it's not my OS/browser/version at fault.
(FWIW: Win7x64, Chrome30)

I found my mistake. Even though the default value for calcMode is linear—which is what I want—I didn't read far enough into the spec to see that it's a different default value for <animateMotion> elements.
Adding an explicit calcMode="linear" fixes the problem.

The default calcmode Value for animate Motion is paced not linear;
http://www.w3.org/TR/SVG/animate.html#AnimateMotionElement
And, if calcmode = "paced" is specified, any ‘keyTimes’ or ‘keySplines’ will be ignored.
http://www.w3.org/TR/SVG/animate.html#CalcModeAttribute;
That is why you have not got the the desired output...

Related

How to morph a path data to another path data in SVG?

I'm trying to figure out why it wont morph this path data to another path data, I need to make it look like an real animation.
This is my SMIL code:
<animate xlink:href="#Barra3"
repeatCount="indefinite"
attributeName="d"
dur="5s"
values="M52,346L56,346C61.523,346 66,350.477 66,356L42,356C42,350.477 46.477,346 52,346Z;
M54,225C60.627,225 66,230.373 66,237L66,356L42,356L42,237C42,230.373 47.373,225 54,225Z;"/>
Here is my codepen:
https://codepen.io/joannesalfa/pen/mdPBJxq
and go line 181. I'm using SMIL.
The most important when trying to morph a path in svg is thast the d attribute hes to have the same number of commands and the same commands. I've rewritten the short path so that the lines drawing the sides of the shape have a length = 0.
M54,346
C60.627,346,66,351.373,66,358
L66,358L42,358L42,358
C42,351.373,47.373,346,54,346Z
Please take a look:
svg{border:solid}
<svg viewBox="5 200 100 200" width="100">
<path d="M54,346
C60.627,346,66,351.373,66,358
L66,358L42,358L42,358
C42,351.373,47.373,346,54,346Z" stroke="red" fill="gold" >
<animate dur='5s'
attributeType="XML"
attributeName='d'
repeatCount='indefinite'
values="M54,225
C60.627,225 66,230.373 66,236
L66,356L42,356L42,236
C42,230.373 47.373,225 54,225Z;
M54,346
C60.627,346,66,351.373,66,356
L66,356L42,356L42,356
C42,351.373,47.373,346,54,346Z;
M54,225
C60.627,225 66,230.373 66,236
L66,356L42,356L42,236
C42,230.373 47.373,225 54,225Z" />
</path>
</svg>
Update
The OP is commenting:
Would you mind how to rewrite the short path step by step? I find it's very confusing to me
I'm taking both those paths and I'm breaking them in 5 paths of different colors, one for every command. Please note that I had to add a move to command (M) at the beginning of each path. The value for the move to is the last point of the previous path. The lines, are the blue paths.
For the short path you can see those blue paths in the code but not in the svg because their length is 0. I needed those 0 length lines because you have lines in the long path.
svg{width:200px;border:solid;overflow:visible; fill:none}
<svg viewBox="40 220 28 140" >
<desc>The short path</desc>
<path d="M54,346 C60.627,346,66,351.373,66,356" stroke="red" />
<path d="M66,356 L66,356" stroke="blue" />
<path d="M66,356 L42,356" stroke="green" />
<path d="M42,356 L42,356" stroke="blue" />
<path d="M42,356 C42,351.373,47.373,346,54,346" stroke="gold"/>
</svg>
<svg viewBox="40 220 28 140" >
<desc>The long path</desc>
<path d="M54,225 C60.627,225 66,230.373 66,237" stroke="red"/>
<path d="M66,237 L66,356" stroke="blue" />
<path d="M66,356 L42,356" stroke="green" />
<path d="M42,356 L42,237" stroke="blue"/>
<path d="M42,237 C42,230.373 47.373,225 54,225;" stroke="gold"/>
</svg>

Selecting last path of svg with d3.js

I am fairly new in D3.js I want to build a real time collaborating blackboard. One admin client draw paths on svg and other clients receive them. I'm trying to pick last path element from clients side. I walked around last few hours finding several posts as How can I select :last-child in d3.js? but cannot make it work. When i ask:
console.log('svg ='+svg.selectAll("path")
having drawn two paths i get
svg ={"_groups":[{"0":{},"1":{}}],"_parents":[{"__on":[{"type":"click","name":"","capture":false},{"type":"mousedown","name":"drag","capture":false},{"type":"touchstart","name":"drag","capture":false},{"type":"touchmove","name":"drag","capture":false},{"type":"touchend","name":"drag","capture":false},{"type":"touchcancel","name":"drag","capture":false}]}]}
EDIT: my DOM structure
<svg id="myCanvas" width="960" height="500">
<rect fill="#F2EDE4" width="100%" height="100%"></rect>
<path fill="none" stroke="black" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M223.64999389648438,304.25L223.98332722981772,304.5833333333333C224.31666056315103,304.9166666666667,224.98332722981772,305.5833333333333,226.31666056315103,306.75C227.64999389648438,307.9166666666667,229.64999389648438,309.5833333333333,231.14999389648438,310.4166666666667C232.64999389648438,311.25,233.64999389648438,311.25,234.98332722981772,310.4166666666667C236.31666056315103,309.5833333333333,237.98332722981772,307.9166666666667,238.81666056315103,307.0833333333333L239.64999389648438,306.25"></path><path></path></svg>
Thanks in advance
Have you tried something like this?
const lastPath = d3.select('svg>path:last-child')
You should be able to use a string like :last-child in the d3 selection api.
Peter's answer is working.
You get a {"_groups":[[{}]],"_parents":[{}]}, as you said in your comment, because that is a D3 selection, and that's the expected object.
If you want to get the DOM element, simply use the node() function:
const lastPath = d3.select('svg>path:last-child').node();
//getting the DOM element -------------------------^
Here is a demo using your SVG, it will log that empty path, which is the last one (since S.O. snippets freeze — at least in my machine, using Chrome — when trying to log D3 selections, here is a fiddle with the same code):
const lastPath = d3.select('svg>path:last-child').node();
console.log(lastPath)
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="myCanvas" width="960" height="500">
<rect fill="#F2EDE4" width="100%" height="100%"></rect>
<path fill="none" stroke="black" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M223.64999389648438,304.25L223.98332722981772,304.5833333333333C224.31666056315103,304.9166666666667,224.98332722981772,305.5833333333333,226.31666056315103,306.75C227.64999389648438,307.9166666666667,229.64999389648438,309.5833333333333,231.14999389648438,310.4166666666667C232.64999389648438,311.25,233.64999389648438,311.25,234.98332722981772,310.4166666666667C236.31666056315103,309.5833333333333,237.98332722981772,307.9166666666667,238.81666056315103,307.0833333333333L239.64999389648438,306.25"></path>
<path></path>
</svg>
This is what solved this problem for me. According to d3.selections docs, this is what you should do:
var lastPath = d3.selectAll('svg>path').filter(':last-child');

Setting keyTimes for SVG visibility animation

I'm trying to create a flashing lightning icon with SVG but I can't get keyTimes to work. The intention is to set up a more realistic flash with uneven steps between on and off but for the purposes of this question I have simplified the SVG like so
<g id="lightning">
<polygon fill="#FFD744" points="55.724,91.297 41.645,91.297 36.738,105.038 47.483,105.038 41.622,124.568 62.783,98.526 51.388,98.526" />
<animate attributeType="CSS"
attributeName="visibility"
from="hidden"
to="hidden"
values="hidden;visible;hidden"
keyTimes="0; 0.5; 0.6"
dur="2s"
repeatCount="indefinite"/>
</g>
However if I have the keyTimes attribute all flashing stops and the lightning bolt is static on the screen. If I remove the attribute the flashing is slow because the loop is two seconds long and it just oscillates gently back and forth.
From the SVG specification
For linear and spline animation, the first time value in the list must be 0, and the last time value in the list must be 1. The key time associated with each value defines when the value is set; values are interpolated between the key times.
You haven't specified a calcMode, but the default is linear so the last value must be 1 or the animation is invalid and ignored.
Here's what happens if I set the last value to 1.
<svg id="lightning">
<polygon fill="#FFD744" points="55.724,91.297 41.645,91.297 36.738,105.038 47.483,105.038 41.622,124.568 62.783,98.526 51.388,98.526" />
<animate attributeType="CSS"
attributeName="visibility"
from="hidden"
to="hidden"
values="hidden;visible;hidden"
keyTimes="0; 0.5; 1"
dur="2s"
repeatCount="indefinite"/>
</svg>

How can I make text orbit along a circular path using HTML5 Canvas?

There are a number of JavaScript snippets that will make text or graphics travel along a circular path with the letters or words always upright.
Example: http://www.dseffects.com/f_scripts.html
I want to have text (or graphics) orbit a point the way the moon orbits the Earth, with one face always toward the center. The following example shows this, but very crudely and not using web fonts.
Example: http://javaboutique.internet.com/text/Manipulation/TextRotor/
I am sure there is a way to modify orbiting code like the first example (only not around the cursor) so that each letter/image keeps one side toward the center (axis).
SVG really is the way to go for this kind of thing. I just whipped this up really quick but at least it works as an example. The HTML part can vary a lot but this is one way.
Put this into an html page:
<iframe src="orbitingText.svg" width="100%" height="100%"></iframe>
Then, create the orbitingText.svg file (it's just a text file with a .svg extension):
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 300 300">
<defs>
<path id="textPath" d="M 150 75 a 75 75 0 1 0 0.00001 0"/>
</defs>
<circle cx="150" cy="150" r="40" stroke="blue" stroke-width="1"></circle>
<text fill="red">
<textPath xlink:href="#textPath" startOffset="0">
<animate attributeName="startOffset" dur="7s" from="0" to="500" repeatCount="indefinite" />
Orbiting Text
</textPath>
</text>
Oh, and if you are worried about cross-browser compatibility, check out this site:
http://code.google.com/p/svgweb/

Calculate path center after transformation matrices have been applied

I made a logo in Inkscape. For learning I wanted to make a wheel shape in the logo rotate by the animation support in SVG.
It was easy to implement the rotation, but it was difficult for me to be able to specify the correct axis of rotation. The shape was a cog wheel and I wanted it to rotate around its center. Trial and error gave that the xy-coordinate (47.1275, 1004.17) (whose components are strangely asymmetric, but I guess that has to do with the transformation matrices Inkscape applies) was a good approximation (see animateTransform tag below), but how would I get that from first principles?
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 321.281 150.799" xmlns:dc="http://purl.org/dc/elements/1.1/">
<g transform="translate(-9.9178912,-891.57237)">
<g transform="matrix(1.9522781,0,0,1.9522781,4.6434311,-1008.1558)">
<animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 47.1275 1004.17" to="45 47.1275 1004.17" dur="2s" fill="freeze" additive="sum" repeatCount="indefinite" />
<g transform="matrix(0.65043772,0,0,0.65043772,-143.67477,980.4256)" stroke="#666" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="7.68713093" fill="none">
<path stroke-linejoin="miter" d="m293.404-3.51576c-2.73916,0-5.41514,0.287192-8,0.8125v6.1875c-3.47484,0.838872-6.7198,2.18462-9.6875,4l-4.375-4.375c-2.24264,1.48612-4.29226,3.22977-6.1875,5.125s-3.63888,3.94486-5.125,6.1875l4.375,4.375c-1.81538,2.9677-3.16112,6.21265-4,9.6875h-6.1875c-0.5253,2.58486-0.8125,5.26083-0.8125,8s0.2872,5.41515,0.8125,8h6.1875c0.83888,3.47485,2.18462,6.7198,4,9.6875l-4.375,4.375c1.48612,2.24264,3.22976,4.29227,5.125,6.1875s3.94486,3.63888,6.1875,5.125l4.375-4.375c2.9677,1.81538,6.21266,3.16113,9.6875,4v6.1875c2.58486,0.525308,5.26082,0.8125,8,0.8125,2.73916,0,5.41514-0.287192,8-0.8125v-6.1875c3.47484-0.838872,6.7198-2.18462,9.6875-4l4.375,4.375c2.24264-1.48612,4.29226-3.22977,6.1875-5.125s3.63888-3.94486,5.125-6.1875l-4.375-4.375c1.81538-2.9677,3.16112-6.21266,4-9.6875h6.1875c0.5253-2.58485,0.8125-5.26083,0.8125-8s-0.2872-5.41515-0.8125-8h-6.1875c-0.83888-3.47485-2.18462-6.7198-4-9.6875l4.375-4.375c-1.48612-2.24264-3.22976-4.29227-5.125-6.1875s-3.94486-3.63888-6.1875-5.125l-4.375,4.375c-2.9677-1.81538-6.21266-3.16113-9.6875-4v-6.1875c-2.58486-0.525308-5.26084-0.8125-8-0.8125z" stroke-dashoffset="162" stroke="#666" stroke-linecap="butt" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="7.68713093" fill="none"/>
</g>
</g>
</g>
</svg>
From what I've read in the specification I would say the transformation matrices applied are
1.9522781 0 4.6434311
0 1.9522781 -1008.1558
0 0 1
and
0.65043772 0 -143.67477
0 0.65043772 980.4256
0 0 1
Are they applied on the xyz-coordinate (-9.9178912,-891.57237,0) after the translation transformation?
I guess a correct analysis above would get me the top left point of the path described, or maybe the coordinate for the first handle. After that, does one have to parse the path to decide the bounding-box and thereby the center (since it concerns a somewhat circular object) of the path?
Is it all a lesson in not trying to manually do animation on freely created shapes?
I think the transformations will be applied from the innermost outward, so transform="translate(-9.9178912,-891.57237)" will be done last. But you can ignore the other transformations if you put your animation in the innermost region, i.e. within the path itself:
<g transform1>
<g transform2>
<g transform3>
<path d="coordinates">
<animateTransform your transformation here>
</path>
</g>
</g>
</g>
Then you just need to find the centre of your path, which is easy to do in Inkscape, but tricky to do on-the-fly (related question here: programmatically How to get shape width in SVG document using java).
Personally, I'd use a script within the svg so you can use getBBox to find the bounding box of your shape. If you add the following element into your SVG you can make any element with the id="cog" turn about its centre:
<script type="text/ecmascript"><![CDATA[
var svgNS = "http://www.w3.org/2000/svg";
function init(evt)
{
if ( window.svgDocument == null )
{
svgDocument = evt.target.ownerDocument;
}
addRotateTransform('cog');
}
function addRotateTransform(target_id)
{
var element_to_rotate = svgDocument.getElementById(target_id);
var my_transform = svgDocument.createElementNS(svgNS, "animateTransform");
var bb = element_to_rotate.getBBox();
var cx = bb.x + bb.width/2;
var cy = bb.y + bb.height/2;
my_transform.setAttributeNS(null, "attributeName", "transform");
my_transform.setAttributeNS(null, "attributeType", "XML");
my_transform.setAttributeNS(null, "type", "rotate");
my_transform.setAttributeNS(null, "dur", "4s");
my_transform.setAttributeNS(null, "repeatCount", "indefinite");
my_transform.setAttributeNS(null, "from", "0 "+cx+" "+cy);
my_transform.setAttributeNS(null, "to", "360 "+cx+" "+cy);
element_to_rotate.appendChild(my_transform);
my_transform.beginElement();
}
]]></script>
You also need to add onload="init(evt)" as an attribute to the SVG tag. e.g.
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 321.281 150.799"
xmlns:dc="http://purl.org/dc/elements/1.1/"
onload="init(evt)">
This will call the init() function when the SVG is first loaded. The init() function calls addRotateTransform() which find the element with a given id. It then finds the centre of that object using getBBox() and adds an animateTransform method with the relevant centres. You can change the dur attribute which determines the speed of a full rotation.
It might seem like a lot of code, but I think it's the easiest way to determine the centre of a path. It also means to can easily add other rotating elements by add addRotateTransform('whatever-id'); to the init() function.

Resources