Sometimes it’s not about the computer, management, analysis, or much of anything else.

Sometimes it’s not about the computer, management, analysis, or much of anything else.

A while ago I wrote about a museum exhibit featuring a large number of creative, custom-made iron implements that were displayed in a little museum in northwestern Montana. Today, as I’m circumnavigating much of North America, I went hundreds of miles out of my way to get a picture.
When I got there, in the picturesque hamlet of Troy, the museum turned out to be closed. I went over to the hardware store next door, asked when the museum was supposed to be open (Friday through Monday, today is Wednesday….), and told them my story. The very nice lady called over to city hall (possibly an over-glamorized title for a town of 957), and told me someone would come over and open it up for me. A very nice gentleman did come by and open it up for me, giving a few other passersby a chance to get inside as well, and what did I find?
The museum was laid out somewhat as I remembered it, complete with a second room oriented about as I remembered, which was, in fact, filled with various tools and farm implements, but this was totally not the right museum. I took some pictures (I’ll post a couple when I have time), thanked my host profusely, made a generous donation, and headed across the street for a lunch that included huckleberry ice cream.
Now, either my memory, ten years removed, is faulty, the implements were in the caboose displayed out back (with a lovely new paint job, I might add), or they are in a completely different museum. All of the implements I saw in this museum were quite comprehensible and seemingly ordinary.
If I succeed in locating the display I’m thinking of via Google or the dim recesses of my brain I’ll be sure to let you know. In the meantime you can be assured that I’m willing to go out of my way to accomplish a mission I’m passionate about.
This is a weird but true story … A complaint was received by the Pontiac Division of General Motors:
This is the second time I have written you, and I don’t blame you for not answering me, because I kind of sounded crazy, but it is a fact that we have a tradition in our family of ice cream for dessert after dinner each night. But the kind of ice cream varies so, every night, after we’ve eaten, the whole family votes on which kind of ice cream we should have and I drive down to the store to get it.
It’s also a fact that I recently purchased a new Pontiac and since then my trips to the store have created a problem. You see, every time I buy vanilla ice cream, when I start back from the store my car won’t start. If I get any other kind of ice cream, the car starts just fine.
I want you to know I’m serious about this question, no matter how silly it sounds: ‘What is there about a Pontiac that makes it not start when I get vanilla ice cream, and easy to start whenever I get any other kind?'”
The Pontiac President was understandably skeptical about the letter, but sent an engineer to check it out anyway. The latter was surprised to be greeted by a successful, obviously well educated man in a fine neighborhood. He had arranged to meet the man just after dinner time, so the two hopped into the car and drove to the ice cream store. It was vanilla ice cream that night and, sure enough, after they came back to the car, it wouldn’t start.
The engineer returned for three more nights. The first night, the man got chocolate. The car started. The second night, he got strawberry. The car started. The third night he ordered vanilla. The car failed to start.
Now the engineer, being a logical man, refused to believe that this man’s car was allergic to vanilla ice cream. He arranged, therefore, to continue his visits for as long as it took to solve the problem. And toward this end he began to take notes: he jotted down all sorts of data, time of day, type of gas used, time to drive back and forth, etc. In a short time, he had a clue: The man took less time to buy vanilla than any other flavor. Why? The answer was in the layout of the store.
Vanilla, being the most popular flavor, was in a separate case at the front of the store for quick pickup. All the other flavors were kept in the back of the store at a different counter where it took considerably longer to find the flavor and get checked out. Now the question for the engineer was why the car wouldn’t start when it took less time.
Once time became the problem — not the vanilla ice cream — the engineer quickly came up with the answer: vapor lock. It was happening every night, but the extra time taken to get the other flavors allowed the engine to cool down sufficiently to start. When the man got vanilla, the engine was still too hot for the vapor lock to dissipate.
Moral of the story: even insane-looking problems are sometimes real.
This classic story has been floating around for years. There are many reasons why a process analyst may be called to work with a customer. First, the customer may already know what the problem is, but they don’t know how to solve it. Next, the customer may know there’s a problem but they don’t know what the problem is. In this case they’ll want help identifying it. Sometimes the customer doesn’t have a problem at all, per se, but simply wants to improve the way they do things, either in terms of quality or volume of output. In other cases the customer doesn’t have a specific problem but their process needs to be documented, quantified, simulated, or something else, possibly as part of a larger program.
While process analysts do want to bring their skills, intelligence, experience, and tools with them to learn about customers’ problems so they can work on solving them, and while they can apply numerous formal and ad hoc techniques to analyze those problems, the process always begins and ends with listening to the customer.
In all of these cases you have to listen carefully. Even if you have your own expertise in a field the customer will know more about their specific processes, issues, conditions, and contexts than you will. It’s up to you to learn as much about that background as you can. The more you respect the customer, the more you ask them questions, the more you show interest and appreciation for what they do, the more information and cooperation you will get. That applies to managers, subject matter experts (SMEs) you may interview, line personnel, and every other person you encounter. The principles described in Dale Carnegie’s How to Win Friends and Influence People may seem cliché at this point but they aren’t. (Nor are they cliché when dealing with family, friends, salespeople, colleagues, or anyone else, but that’s a different conversation.) The purpose is never to beat people over the head trying to demonstrate how smart you are, but to learn as much as you can so they’ll get their problems solved, you’ll get paid, and everyone will be happier. Besides, they’re much more likely to recognize your brilliance when you actually solve their problem. That’s a lot easier if you get cooperation by concentrating on other people and their needs. A lot of people, including Harry Browne have included similar observations and advice in their own works.
I’ve worked in all of these contexts and probably some others. I’ve definitely done the wrong thing at times, especially when I was starting out, but when I’ve been able to listen carefully I’ve been able to achieve my ends much more effectively. These days I’m consciously trying to improve my awareness of these principles so I can not only solve communication issues, but try to prevent them in the first place. That is, I’m applying my best analytic skills to an area that is a critical — and pervasive — problem all its own. I’ll leave you with another story, this one guaranteed to be true, that shows some of the possible confusion, even if everyone is on their best behavior. The problem is hard enough. Imagine how hard it becomes when people aren’t on their best behavior.
Many years ago my uncle, a retired Coast Guard officer, was explaining how to open a petcock valve to drain water from the fuel line of a small outboard motor. He told me to turn the valve clockwise to open it which for some reason inspired me to ask whether the direction was based on looking down at it or up at it. He thought for a moment and observed, “ships have been lost for that reason.”
I got the basics of the fountain effect working. I defined forty elements, gave them random characteristics in a loop, set a delay of 1.5 seconds from the end of the previous animation element, then set the forty elements to begin flying on a 1.45 second setback following by a jump back 1.5 seconds. This has the effect of launching a new element every 0.05 seconds (twenty per second) for two seconds, with the elements all clearing after a further 1.5 seconds minus.
It could probably be tweaked a hair, but I hope you’ll agree that this is a respectable emulation of the effect shown on the Greensock home page. Again, I have no idea whether I’ve done this the same way they did, but it works well enough.
Here is the initialization code. The handle006 element was already in place, but I added a clear instruction with a 1.5 second delay, as described above.
|
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 |
var handle006 = document.getElementById("handle006"); bcInitElement(handle006,"20px","201px","green","rgba(0,0,0,0)","260px",1,"block","This text reacts to gravity."); addTransformToList("handle006",2.0,{topNew:0},{effect: "gravity", velocity: -8.0, acceleration: 0.16}); addTransformToList("handle006",2.0,{left:80},"linear","-=2.00"); addTransformToList("handle006",0.00,{display:"none"},"","+=1.50"); var ftnElementNames = new Array; ftnElementNames[ 0] = "ftn000"; ftnElementNames[ 1] = "ftn001"; ftnElementNames[ 2] = "ftn002"; ftnElementNames[ 3] = "ftn003"; ftnElementNames[ 4] = "ftn004"; ftnElementNames[ 5] = "ftn005"; ftnElementNames[ 6] = "ftn006"; ftnElementNames[ 7] = "ftn007"; ftnElementNames[ 8] = "ftn008"; ftnElementNames[ 9] = "ftn009"; ftnElementNames[10] = "ftn010"; ftnElementNames[11] = "ftn011"; ftnElementNames[12] = "ftn012"; ftnElementNames[13] = "ftn013"; ftnElementNames[14] = "ftn014"; ftnElementNames[15] = "ftn015"; ftnElementNames[16] = "ftn016"; ftnElementNames[17] = "ftn017"; ftnElementNames[18] = "ftn018"; ftnElementNames[19] = "ftn019"; ftnElementNames[20] = "ftn020"; ftnElementNames[21] = "ftn021"; ftnElementNames[22] = "ftn022"; ftnElementNames[23] = "ftn023"; ftnElementNames[24] = "ftn024"; ftnElementNames[25] = "ftn025"; ftnElementNames[26] = "ftn026"; ftnElementNames[27] = "ftn027"; ftnElementNames[28] = "ftn028"; ftnElementNames[29] = "ftn029"; ftnElementNames[30] = "ftn030"; ftnElementNames[31] = "ftn031"; ftnElementNames[32] = "ftn032"; ftnElementNames[33] = "ftn033"; ftnElementNames[34] = "ftn034"; ftnElementNames[35] = "ftn035"; ftnElementNames[36] = "ftn036"; ftnElementNames[37] = "ftn037"; ftnElementNames[38] = "ftn038"; ftnElementNames[39] = "ftn039"; var ftnElements = new Array; var tempHalf; var tempColor; var startX = new Array; var endX = new Array; var tempVelocity = new Array; var tempAcceleration = new Array; var tempColors = new Array; tempColors[0] = "#770000"; tempColors[1] = "#990000"; tempColors[2] = "#BB0000"; tempColors[3] = "#DD0000"; tempColors[4] = "#FF0000"; for (var j=0; j<40; j++) { ftnElements[j] = document.getElementById(ftnElementNames[j]); tempHalf = Math.floor((Math.random() * 3) + 1); tempWidth = 8 + tempHalf * 2 + "px"; tempColor = Math.floor((Math.random() * 4) + 1); startX[j] = "140px"; endX[j] = -148 + Math.floor((Math.random() * 282) + 1) + "px"; tempVelocity[j] = -9.0 - Math.random() * 4; tempAcceleration[j] = 0.37; //0.25 + Math.random() * 0.25; bcInitElement(ftnElements[j],startX[j],"201px","#FFFFFF",tempColors[tempColor],tempWidth,1,"block",""); ftnElements[j].style.height = tempWidth; ftnElements[j].style.borderRadius = 4 + tempHalf + "px"; addTransformToList(ftnElementNames[j],1.5,{topNew:0},{effect: "gravity", velocity: tempVelocity[j], acceleration: tempAcceleration[j]},"-=1.45"); addTransformToList(ftnElementNames[j],1.5,{left: endX[j]},"linear","-=1.50"); } |
I’ll work on looping back at a later date, since I’m currently doing a very long road trip to clear my mind before coming back home to get back at it. Please feel free to contact me as I’m traveling.
I sat down to add in the mods that would allow me to reproduce the fountain effect I’ve described, building on yesterday’s work, and immediately identified some issues. Those will have to be sorted out before proceeding.
The animation capability I’ve built up so far is based on pre-calculating the full state of every object for every time step. With that in mind, here are a few questions and thoughts that occur to me:
I could probably go on, but this line of thinking suggests at least an initial course of action, which I’ll pursue going forward.
Today I added a new effect and applied it to a top tween that makes up the latest addition to the animation. The effect is based on this equation from the first class you take in physics:
x = xo + vot + at2/2
where:
x = location
xo = initial location
vo = initial velocity
a = acceleration (due to gravity in this example)
t = time (in animation steps)
Note that when working in screen coordinates, positive numbers increase going down. Here’s the code I added to the processEffect function.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
} else if (effect == "gravity") { var velocity = -8.00; var acceleration = 0.16; for (var ii=1; ii<keyList.length; ii++) { if (keyList[ii] == "velocity") { velocity = mMethod.velocity; } else if (keyList[ii] == "acceleration") { acceleration = mMethod.acceleration; } } var t = (i - alpha); result = pStart + t * (velocity + t * (0.5 * acceleration)); return result; } |
I had to tune the values for the initial velocity and acceleration so the path of the moving element covered most of the designated screen area and most of the designated time. It should be possible to invert the equation so that the extents of the desired path and time span are specified and the values for initial velocity and acceleration are back calculated to achieve the desired results.
Here’s how the effect is defined by the user. I added a simultaneous sideways motion to make the text look a bit like a projectile (think of a cannonball). I initialized the affected element to start just below the bottom of the demo window and the element finished somewhere below the bottom of the demo window as well. If I put a bunch of these together in the right way I can replicate the fountain-like effect on the Greensock home page (it starts at about the halfway mark).
|
1 2 3 4 |
var handle006 = document.getElementById("handle006"); bcInitElement(handle006,"20px","201px","green","rgba(0,0,0,0)","260px",1,"block","This text reacts to gravity."); addTransformToList("handle006",2.0,{topNew:0},{effect: "gravity", velocity: -8.0, acceleration: 0.16}); addTransformToList("handle006",2.0,{left:80},"linear","-=2.00"); |
Today I changed the form of specifying a tween effect. It was originally specified as a string (e.g., “linear” or “vibrate_right”) but now it is defined as an object literal. The first member field of the object has to be effect and its value specifies the effect to be used. If the effect requires additional parameters then those may also be provided within the object literal, but defaults are provided for all values so they aren’t strictly necessary.
Here is the code for the processEffect function I described yesterday. As noted in the comments it’s kind of annoying to have to peel the effect description objects apart every time this routine is called, but that can be improved later by creating some external state variables. Whether you as a developer would want to pursue that course of action is a matter of taste. Do you want more speed or to use less memory?
|
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 |
function processEffect(pStart,pEnd,alpha,omega,i,mMethod) { var result = 0.0; var keyList = Object.keys(mMethod); if (keyList[0] == "effect") { //for now this should always be the case var effect = mMethod.effect; if (effect == "linear") { result = pStart + (pEnd - pStart) * (i - alpha) / (omega - alpha); //returning result in each branch allows for possible type flexibility return result; } else if (effect == "vibrate_right") { //retrieve required/expected additional parameters //yes, it's annoying that we have to peel this apart every time... // ...but we can think of adding improvements later var frequency = 1.0; var amplitude = 50.0; var decay = "linear"; for (var ii=1; ii<keyList.length; ii++) { if (keyList[ii] == "freq") { frequency = mMethod.freq; } else if (keyList[ii] == "amplitude") { amplitude = mMethod.amplitude; } else if (keyList[ii] == "decay") { decay = mMethod.decay; } } var multiplier = i - alpha - (Math.PI*0.5); multiplier *= frequency; multiplier = Math.cos(multiplier); multiplier *= amplitude; if (decay == "linear") { multiplier *= (omega-i) / (omega-alpha); } else { alert("We're not dealing with this for now, and we may never!") } //var multiplier = Math.cos(i - alpha - Math.PI*0.5)* 100 * (omega-i) / (omega-alpha); result = pStart + multiplier; return result; } } } |
Now that this is in place it can (theoretically) be used to modify a wide variety of tweens. The code for each type of modification need only be written once.
Here’s the animation with a new block showing that the vibrate effect works as intended. There are a lot more twists I could put on this thing but the basics are working for now.
I want to make the handling of the mMethod parameter in the bcDefineWhateverTween function more flexible and modular. As you can see in the code below, the parsing of the different types of methods or applied effects has to be handled in every tween definition. Even worse, the code all has to be peeled apart and processed in every individual case.
Therefore, I’m going to try to define a wrapper function that takes the mMethod parameter, and the values for pStart, pEnd, alpha, omega, and i, and return the desired output value for that animation step. I think we’re giving up the need to peel apart the mMethod during every call in order to get a more streamlined expression where the animation step values are assigned and the ability to have to only use a single copy of the code in all tweens.
Note that this approach is expected to work for numeric tweens. It is not necessary for tweens that merely change state in a single step (like the display CSS parameter). This approach may or may not prove troublesome for other types of tweens.
|
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 |
function bcDefineLeftTween(sElement,pStart,pEnd,tBegin,tDuration,mMethod) { var s = sElement+".style.left='"+pStart+"px';"; //initial string value var alpha = Math.round(tBegin * 60.0); //index of start of transform series var steps = Math.round(tDuration * 60.0); //number of steps in transform series var omega = alpha + steps; //index of end of transform series var increment = (pEnd - pStart) / steps; //increment to use during transform series var left = pStart; //initialize tracking value var index = bcActivitiesList[0].length-1; //determine size of activities list if ((alpha > 0) && (!bcChainFlag)) { //if need to fill beginning of list AND first of this transform type for this element for (var i=0; i<alpha; i++) { //> // for elements at beginning of list bcActivitiesList[i][0] = bcActivitiesList[i].push(s); // add the activity and increment the array length } } if (!bcChainFlag) { //if writing for the first time index++; // bump the index by one } for (var i=alpha; i<=omega; i++) { //> //for the active steps in the transition series s = sElement+".style.left='"+Math.round(left)+"px';"; //updated string value bcActivitiesList[i][0] = index+1; //set size of array bcActivitiesList[i][index] = s; //add activity if flag not set, overwrite last if flag is set if (mMethod == "linear") { //if linear increment left += increment; // add simple increment } else if (mMethod == "vibrate_right") { //if something else more complicated var multiplier = i - alpha - (Math.PI*0.5); // do the more complicated increment multiplier *= 0.3; // goal is to make this modular in future multiplier = Math.cos(multiplier); multiplier *= 100.0; multiplier *= (omega-i) / (omega-alpha); //var multiplier = Math.cos(i - alpha - Math.PI*0.5)* 100 * (omega-i) / (omega-alpha); left = pStart + multiplier; } } for (i=omega+1; i<=bcFinalActivity; i++) { //for elements through to the end of the list bcActivitiesList[i][0] = index+1; // set size of array bcActivitiesList[i][index] = s; // add activity if flag not set, overwrite last if flag is set } bcClearChainFlag(); //clear the flag if set } |
I’m thinking the new version of the bcDefineWhateverTween function should look like this:
|
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 |
function bcDefineLeftTween(sElement,pStart,pEnd,tBegin,tDuration,mMethod) { var s = sElement+".style.left='"+pStart+"px';"; var alpha = Math.round(tBegin * 60.0); var steps = Math.round(tDuration * 60.0); var omega = alpha + steps; var increment = (pEnd - pStart) / steps; var left = pStart; var index = bcActivitiesList[0].length-1; if ((alpha > 0) && (!bcChainFlag)) { for (var i=0; i<alpha; i++) { //> bcActivitiesList[i][0] = bcActivitiesList[i].push(s); } } if (!bcChainFlag) { index++; } for (var i=alpha; i<=omega; i++) { //> s = sElement+".style.left='"+Math.round(left)+"px';"; bcActivitiesList[i][0] = index+1; bcActivitiesList[i][index] = s; //this line replaces everything we included above left = processEffect(pStart,pEnd,alpha,omega,i,mMethod); } for (i=omega+1; i<=bcFinalActivity; i++) { bcActivitiesList[i][0] = index+1; bcActivitiesList[i][index] = s; } bcClearChainFlag(); } |
Note that in the first example, in the code as it currently exists, that the left parameter is incremented in the linear case and set absolutely in the vibrate_right case. We therefore have to come up with a consistent treatment for generating results with the proposed processEffect function. I believe it will be preferable to generate the result in a form that can be used in an absolute assignment.
The last subject I wanted to explore was animation effects, which is to say defining tweens that proceed from beginning to end in a fashion that is other than linear, which is the default I’ve been using to this point. The tweens are defined using the addTransformToList function, as shown in multiple instances below.
|
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 |
var handle001 = document.getElementById("handle001"); bcInitElement(handle001,"301px","50px","red","rgba(0,0,0,0)","260px",0,"block","This line slides in, out, in"); addTransformToList("handle001:1",0.75,{left:-284, opacity:1.0}); addTransformToList("handle001",0.50,{left:284}); addTransformToList("handle001:2",0.75,{left:-284}); addTransformToList("handle001",0.00,{display:"none"}); var handle002 = document.getElementById("handle002"); bcInitElement(handle002,"17px","-25px","blue","rgba(0,0,0,0)","280px",0,"block","This line drops down from the top"); addTransformToList("handle002",0.75,{top:150, opacity:1.0},"linear","handle001:1-=0.25"); addTransformToList("handle002",0.00,{display:"none"},"","handle001:2"); var handle003 = document.getElementById("handle003"); bcInitElement(handle003,"120px","60px","yellow","rgba(0,0,0,0)","80px",0,"block","This text grows in scale"); //handle003.style.transform = "rotate(45deg) scale(0.5,0.5)"; //handle003.style.transform = "rotate(45deg)"; handle003.style.transform = "scale(0.5,0.5)"; addTransformToList("handle003",0.05,{opacity:1.0},"linear","handle001:2-=0.05"); addTransformToList("handle003",1.50,{scale:"(2.0,2.0)"},"linear","+=0.50"); addTransformToList("handle003",0.00,{display:"none"}); var handle004 = document.getElementById("handle004"); bcInitElement(handle004,"80px","70px","orange","rgba(0,0,0,0)","140px",0,"block","This text rotates into place"); addTransformToList("handle004",1.00,{opacity:1.0}); addTransformToList("handle004",1.00,{rotate:"-360"},"linear","-=1.00"); |
If, instead of defining a linear tween, we wanted to define one that behaved differently, we’d have to provide an alternate string or object literal. The information would have to include as many parameters as required to govern the desired effect. Here’s one I did for an animation on one of my intro pages; it simply specifies a string and the details are hard-coded in the tween function (in this case a bcDefineLeftTween function).
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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; } else if (mMethod == "vibrate_right") { var multiplier = i - alpha - (Math.PI*0.5); multiplier *= 0.3; multiplier = Math.cos(multiplier); multiplier *= 100.0; multiplier *= (omega-i) / (omega-alpha); //var multiplier = Math.cos(i - alpha - Math.PI*0.5)* 100 * (omega-i) / (omega-alpha); left = pStart + multiplier; } } |
The idea is that the element would start in a given spot, get kicked to the right by some specified amplitude, vibrate back to the original position in a sine wave pattern, with a vibration having a specified period, and with a specified decay (during which the vibration gets smaller and smaller). I used parameters to govern the amplitude (the 100.0, in pixels), period (the 0.3, modifying the cumulative angle), and decay (the (omega-i) / (omega-alpha) term) so I might define an object literal of the form:
|
1 |
{effect: "vibrate_right", freq: 0.3, amplitude: 100.0, decay: "linear"} |
If I wanted the vibrate starting to the left I would just change the sign of the last term in the first line of the relevant block like this:
|
1 |
var multiplier = i - alpha + (Math.PI*0.5); |
This would make the tween start in the original location but as the quantity i – alpha began to increase the cosine value would first go negative instead of positive.
The tween definitions here aren’t bad but we’d certainly want to implement something more modular. We wouldn’t want to have to embed all that specialized code in each function that defines a tween, we’d probably want to use a functional parameter that gets its parameters set in one place, then is called in the tween function using a standard-looking interface that takes only the alpha, omega, and i values as inputs. Any other method would involve too much duplication of code.
If you are wondering, this is implemented in the Greensock product in a very powerful way. They’ve created a page which lets you see how it all works in detail, here. As I’ve stated before, the folks who’ve created this product have done such a nice job with it, and their licensing terms are so easy, that it would be crazy to try to reverse-engineer or duplicate the whole thing.
One type of effect I do want to create involves simulating gravity, so that when you give an element an initial velocity moving upward, a downward velocity increment is added during every animation step. Combined with a modest leftward or rightward movement, an object can be made to “fly” in a natural-looking arc. A bunch of objects animated together in this way could be made to look like a fountain of sorts. See here (the effect starts about halfway through the title animation) for the inspiration for this exercise, which will begin next week.
Today I added the capability of adding a rotation tween, which I applied to a fourth element at the end of the previous animation. The tween definition is here:
|
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 |
function bcDefineRotateTween(sElement,pStart,pEnd,tBegin,tDuration,mMethod) { var s = sElement+".style.transform='rotate("+pStart+"deg)';"; var alpha = Math.round(tBegin * 60.0); var steps = Math.round(tDuration * 60.0); var omega = alpha + steps; var increment = (pEnd - pStart) / steps; var angle = pStart; var index = bcActivitiesList[0].length-1; if ((alpha > 0) && (!bcChainFlag)) { for (var i=0; i<alpha; i++) { //> bcActivitiesList[i][0] = bcActivitiesList[i].push(s); } } if (!bcChainFlag) { index++; } for (var i=alpha; i<=omega; i++) { //> s = sElement+".style.transform='rotate("+angle+"deg)';"; bcActivitiesList[i][0] = index+1; bcActivitiesList[i][index] = s; if (mMethod == "linear") { angle += increment; } } for (i=omega+1; i<=bcFinalActivity; i++) { bcActivitiesList[i][0] = index+1; bcActivitiesList[i][index] = s; } bcClearChainFlag(); } |
Here’s the code added to handle rotations in the addTransformToList function. The only interesting bit is ensuring the default starting value is properly initialized.
|
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 |
if (keyList[ii] == "left") { pStart = parseFloat(element.style.left); pEnd = pStart + parseFloat(trans.left); } else if (keyList[ii] == "top") { pStart = parseFloat(element.style.top); pEnd = pStart + parseFloat(trans.top); } else if (keyList[ii] == "opacity") { pStart = parseFloat(element.style.opacity); pEnd = pStart + parseFloat(trans.opacity); } else if (keyList[ii] == "display") { pStart = element.style.display; pEnd = trans.display; } else if (keyList[ii] == "scale") { var temp = element.style.transform; var locStart = temp.search("scale"); var temp1 = temp.substr(locStart); var locEnd = temp1.indexOf("\)"); pStart = temp1.substr(5,locEnd-(5-1)); pEnd = trans.scale; } else if (keyList[ii] == "rotate") { var temp = element.style.transform; var locStart = temp.search("rotate"); var temp1 = temp.substr(locStart); var locEnd = temp1.indexOf("\)"); pStart = temp1.substr(5,locEnd-(5-1)); if (pStart == "") { pStart = 0; } else { pStart = parseFloat(pStart); } pEnd = parseFloat(trans.rotate); } |
Here are the initializations for the elements and animations.
|
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 |
var handle001 = document.getElementById("handle001"); bcInitElement(handle001,"301px","50px","red","rgba(0,0,0,0)","260px",0,"block","This line slides in, out, in"); addTransformToList("handle001:1",0.75,{left:-284, opacity:1.0}); addTransformToList("handle001",0.50,{left:284}); addTransformToList("handle001:2",0.75,{left:-284}); addTransformToList("handle001",0.00,{display:"none"}); var handle002 = document.getElementById("handle002"); bcInitElement(handle002,"17px","-25px","blue","rgba(0,0,0,0)","280px",0,"block","This line drops down from the top"); addTransformToList("handle002",0.75,{top:150, opacity:1.0},"linear","handle001:1-=0.25"); addTransformToList("handle002",0.00,{display:"none"},"","handle001:2"); var handle003 = document.getElementById("handle003"); bcInitElement(handle003,"120px","60px","yellow","rgba(0,0,0,0)","80px",0,"block","This text grows in scale"); //handle003.style.transform = "rotate(45deg) scale(0.5,0.5)"; //handle003.style.transform = "rotate(45deg)"; handle003.style.transform = "scale(0.5,0.5)"; addTransformToList("handle003",0.05,{opacity:1.0},"linear","handle001:2-=0.05"); addTransformToList("handle003",1.50,{scale:"(2.0,2.0)"},"linear","+=0.50"); addTransformToList("handle003",0.00,{display:"none"}); var handle004 = document.getElementById("handle004"); bcInitElement(handle004,"80px","70px","orange","rgba(0,0,0,0)","140px",0,"block","This text rotates into place"); addTransformToList("handle004",1.00,{opacity:1.0}); addTransformToList("handle004",1.00,{rotate:"-360"},"linear","-=1.00"); |
The animation works as expected.