Today I did some experiments to try to figure out why the animations I’ve created seem to hitch and hiccup slightly from time to time. Again, this work is based on recreating the basic functionality of the Greensock animation product described here and especially here.
The first thing I thought of was that the drawing operations themselves might be taking long enough that they don’t complete during every animation step (about 16 ms at 60 frames per second). I therefore set the update loop to only draw the items every other scan. This approach didn’t relieve the hitching and updating at 30 fps left the animation looking slightly choppy.
The next thing I tried was recording the elapsed time for every drawing loop and displaying the value if it was greater than 16 ms. However, the value was never displayed, so it appears that the speed of the drawing isn’t an issue.
Next it occurred to me that if an element has to travel far enough and in fine enough increments that the difference between traveling three or four pixels in an update, particularly when advancing by the smaller number doesn’t happen often, might be enough to look like a hitch. I therefore set the durations of the transitions to be shorter so each increment is larger, and I also incorporated a fade-in by ramping up the opacity. The combination of these two changes made things look pretty smooth.
I also observe that the example animation in the second Greensock link, above, involves transitions that are relatively short, so there isn’t time for anything to hitch. That may be the key to the whole enterprise. That said, there’s an animation in the first link that shows a fountain of green balls. I will try to reproduce something like that and see how it looks. Most of the effects can be replicated (though perhaps not as well as the originals), and I’ll be working through duplicating some of them over the next few posts.
In the meantime, I found it necessary to add an initialization capability so rerunning the animation starts from a good place. Here’s the basic code in its entirety.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
<!DOCTYPE html> <html> <head> <title>Fast Animation Script Demo</title> <link type="text/css" rel="stylesheet" href="../stylesheet.css"/> <script type="text/javascript" src="jquery.min.js"> </script> <style> .demo { height: 200px; width: 300px; margin-left: auto; margin-right: auto; background-color: #222222; display: block; overflow: hidden; position: relative; } .demoPanel { position: absolute; background-color: rgba(0,0,0,0); color: #EEEEEE; font-size: 1.2em; } #rerun_button { margin-left: auto; margin-right: auto; display: block; } </style> </head> <body> <div class="demo"> <div id="handle001" class="demoPanel"></div> <div id="handle002" class="demoPanel"></div> </div> <br /> <button id="rerun_button" onclick="rerunClick()" style="height: 50px; width: 300px">Rerun the Animation</button> <br /> <p id="elapsedElement"></p> <script> //specify element //specify initial properties //specify final properties //specify duration //specify start time //build list of commands that run for every animation call var bcActivitiesList = new Array; for (var i=0; i<1800; i++) { //> bcActivitiesList[i] = new Array; bcActivitiesList[i][0] = 0; //number of commands each animation step } //30 seconds of activities var bcFinalActivity = -1; //index of last animation activity var bcInitializations = new Array; bcInitializations[0] = 0; function bcInitElement(pElement,pLeft,pTop,pColor,pBackgroundColor,pWidth,pOpacity,pDisplay,innerHTML) { pElement.style.left = pLeft; pElement.style.top = pTop; pElement.style.color = pColor; pElement.style.backgroundColor = pBackgroundColor; pElement.style.width = pWidth; pElement.style.opacity = pOpacity; pElement.style.display = pDisplay; pElement.innerHTML = innerHTML; } function bcDefineLeftTween(sElement,pStart,pEnd,tBegin,tDuration,mMethod) { var s = sElement+".style.left='"+pStart+"px';" bcInitializations[0] = bcInitializations.push(s); var alpha = tBegin * 60.0; var steps = tDuration * 60; var omega = alpha + steps; var increment = (pEnd - pStart) / steps; var left = pStart; for (var i=alpha; i<=omega; i++) { //> s = sElement+".style.left='"+Math.round(left)+"px';" bcActivitiesList[i][0] = bcActivitiesList[i].push(s); if (mMethod == "linear") { left += increment; } } if (omega > bcFinalActivity) { bcFinalActivity = omega; } } function bcDefineTopTween(sElement,pStart,pEnd,tBegin,tDuration,mMethod) { var s = sElement+".style.top='"+pStart+"px';" bcInitializations[0] = bcInitializations.push(s); var alpha = tBegin * 60.0; var steps = tDuration * 60; var omega = alpha + steps; var increment = (pEnd - pStart) / steps; var top = pStart; for (var i=alpha; i<=omega; i++) { //> s = sElement+".style.top='"+Math.round(top)+"px';" bcActivitiesList[i][0] = bcActivitiesList[i].push(s); if (mMethod == "linear") { top += increment; } } if (omega > bcFinalActivity) { bcFinalActivity = omega; } } function bcDefineOpacityTween(sElement,pStart,pEnd,tBegin,tDuration,mMethod) { var s = sElement+".style.opacity='"+pStart+"';" bcInitializations[0] = bcInitializations.push(s); var alpha = tBegin * 60.0; var steps = tDuration * 60; var omega = alpha + steps; var increment = (pEnd - pStart) / steps; var opacity = pStart; for (var i=alpha; i<=omega; i++) { //> s = sElement+".style.opacity='"+opacity+"';" bcActivitiesList[i][0] = bcActivitiesList[i].push(s); if (mMethod == "linear") { opacity += increment; } } if (omega > bcFinalActivity) { bcFinalActivity = omega; } } function bcInitializeElements() { var i = 1; while (i <= bcInitializations[0]) { //> eval(bcInitializations[i]); i++; } } var elapsedHandle = document.getElementById("elapsedElement"); function doSomeStuff(step) { var startTime = Date.now(); var i = 1; while (i <= bcActivitiesList[step][0]) { //> eval(bcActivitiesList[step][i]); i++; } var endTime = Date.now(); var elapsed = endTime - startTime; if (elapsed > 16) { elapsedHandle.innerHTML = elapsed; } else { elapsedHandle.innerHTML = ""; } } var handle001 = document.getElementById("handle001"); bcInitElement(handle001,"201px","50px","red","rgba(0,0,0,0)","260px",1,"block","This line slides in from the side"); bcDefineLeftTween("handle001",301,25,0.0,0.75,"linear"); bcDefineOpacityTween("handle001",0.0,1.0,0.0,0.75,"linear") var handle002 = document.getElementById("handle002"); bcInitElement(handle002,"13px","-25px","blue","rgba(0,0,0,0)","280px",1,"block","This line drops down from the top"); bcDefineTopTween("handle002",-25,125,0.5,0.75,"linear"); bcDefineOpacityTween("handle002",0.0,1.0,0.5,0.75,"linear") var maxAnimationSteps = bcFinalActivity; bcInitializeElements(); var animationStep = 0; var requestId; function animateSomeStuff() { doSomeStuff(animationStep); if (animationStep >= maxAnimationSteps) { cancelAnimationFrame(requestID); } else { animationStep++; } requestId = window.requestAnimationFrame(animateSomeStuff); } animateSomeStuff(); function rerunClick() { //called on button click animationStep = 0; bcInitializeElements(); animateSomeStuff(); } </script> </body> </html> |