The Mystery of the Bouncing Square
In a class on 2D vector graphics today, my Professor showed an example of simple svg animation using [Snap.svg](https://snapsvg.io). Immediately, the class noticed that the squares had a tiny jitter during each pass across the screen. Someone pointed it out, and the professor said that he wasn't too sure what was causing it. He encouraged the class to let him to know if anyone could figure out why.
Here's the snippet of code responsible for the squares:
<script src="./libs/snap/snap.svg-min.js"></script> <svg width="400" height="300" id="trans"> </svg> <script type="text/javascript"> window.onload = function(){ var s = Snap("#trans"); var outer = s.path("M0,0 L300,0 300,300 0,300 Z") ; var inner = s.path("M0,0 L300,0 300,300 0,300 Z") ; outer.attr({ fill: "pink", stroke: "lightblue", strokeWidth: 25 }); inner.attr({ fill: "lightblue", stroke: "pink", strokeWidth: 25 }); var trans = 0 ; function timerFunction() { trans = (trans+Math.PI*0.02)%(Math.PI*2) ; outer.transform("s0.5 t"+Math.cos(trans)*300) ; inner.transform("s0.75 t"+Math.sin(trans)*300) ; setTimeout(timerFunction, 30) ; } timerFunction() ; } </script>
Here's the link to where the snippet lives. There's an interactive box you can use to play with the code yourself.
These are the kinds of little puzzles that nerd-snipe me. I couldn't resist pulling out my laptop and poking at the code a little bit. After some fiddling, it was clear that the offending line was the transformation. I found out that if the Y argument to the translation directive was given explicitly like so:
// Creates a string like "s0.5 t300,0" outer.transform("s0.5 t"+Math.cos(trans)*300 + ", 0");
The jitter went away. This only made the rabbit hole even deeper, since "t300" should be entirely equivalent to "t300,0". It was clear at this point that whatever was causing the underlying behaviour exists within Snap.svg itself, and so I started digging.
The core of the problem is that Snap.svg does not support scientific notation in the transformation string. So a transform string such as "s0.5 t2e-11" isn't valid - however, it happily parses it anyways, and actually interprets it equivalent to "s0.5 t2,-11". In other words, the command parsing engine within Snap.svg is erroneously parsing the given exponent as the second argument.
Taking a step back, the reason why numbers in scientific notation are being passed in, is because Javscript chooses to represent very small (or large) numbers in scientific notation - even when concatenated as a string. In other words, "hello" + 0.00000000002 will construct the string "hello2e-11".
The reason why such a string is being constructed, is because the trigonometric calculations being done in our square code are returning very small numbers.
In the string construction:
"s0.5 t" + Math.sin(trans) * 300
Math.sin(trans) * 300 is evaluating to a very small number, like 2.2e-15.
At first, one might find that surprising - after all, trans is incrementing by Math.PI * 0.02 amounts every iteration, which is roughly an increase of ~6.6 each time. The reason here is because we apply a modulo of Math.PI * 2 in the calculation of trans. Since this number is irrational, we can see that there exists the chance that the calculation returns a very small number, which then results in a very small sine ratio.
An example would be if trans was 6.283185308, ever so slightly greater than Math.PI * 2 (6.283185307179586). Then trans % Math.PI * 2 evaluates to 1.640827917981369e-09 - which is very small indeed.
Having now understood what the real issue is, we can see that the fix of explicitly stating the second argument for transform is really only a fix by coincidence. The correct solution would be to eliminate the scientific notation from the string, which we can use the Number.toFixed() method to do (with limitations). In lieu of that, we can also round trans to avoid the jitter, at the cost of some fidelity.
The "real" fix would be to use repeated animation (native functionality within Snap.svg) instead of manually translating the squares.
One might argue that perhaps Snap.svg ought to support scientific notation in its commands, and that ultimately this is a bug within the parsing code itself. Whether or not that's true would be up to the API defined for Snap.svg command strings - something that I haven't yet been able to find in their documentation.