paint-brush
Javascript Promises for animation sequencesby@emilong
11,551 reads
11,551 reads

Javascript Promises for animation sequences

by Emil OngMay 13th, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

I recently had the chance to participate in the amazing <a href="http://creativecoding.club" target="_blank">creativecoding.club</a> and slap together an animation of a tree with flowers that fall to the ground. The tree builds up first using a randomized algorithm (inspired by this <a href="http://codepen.io/survivol/pen/pbGpog" target="_blank">growing</a> pen from survivol), then attaches flowers randomly to the ends of the branches of the newly grown tree.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Javascript Promises for animation sequences
Emil Ong HackerNoon profile picture

I recently had the chance to participate in the amazing creativecoding.club and slap together an animation of a tree with flowers that fall to the ground. The tree builds up first using a randomized algorithm (inspired by this growing pen from survivol), then attaches flowers randomly to the ends of the branches of the newly grown tree.

Having more experience as a backend engineer than in design or animation, I immediately thought of using Javascript Promises to help me sequence everything, just like a bunch of database transactions interleaved with API calls. It involves asynchronous recursion, which can seem tricky at first, so I thought I’d write up an explanation in case it might help others who want to use it for animation in the same way. 😊

Growing the tree

From tiny acorns…

The first step is to figure out how to create a random tree. We use recursion, which is really just a fancy way of saying we build up on work that we’ve already done as we go along. Here’s the high-level idea:

  1. Start with a tree trunk — we know where it begins (the ground) and where it ends (the height of the trunk above the ground).
  2. Draw the tree trunk.
  3. Next create some branches off of the trunk — they start where the trunk ended. We pick some random angles to the left and right of the trunk, some random length that’s slightly shorter than the trunk itself, and use those to compute the ends of the new branches.
  4. Draw the branches.
  5. For each of the two new branches, we take their end points and grow two more branches off of them, just like in step 3.
  6. Repeat until we’ve reached the depth of tree we want!

So this algorithm will give us a tree, but if we draw as we go along, it will be drawn as fast as the computer can draw and compute, which may be too fast. What if we want to animate the tree so it looks like it’s growing more slowly? Well, we can add in a delay!

Between steps 3 and 4, computing then drawing the new branches, we can wait (using setTimeout()) to draw the branches. Since repeating comes after drawing, the tree will slowly fan out, computing, waiting, drawing, computing, waiting, drawing, etc.

So we might end up with code that looks roughly like the following (assume the undefined functions do what they say for the moment):

Finding the ends of the last branches

Once we grow the tree, we know where the last branches are and we want to collect them all up so we can attach flowers to them in the next phase. Specifically, when we realize that our newRemainingBranches ≤ 0, we know we’re not going to branch anymore, thus we know the endingPoint that we have computed is an ending branch.

If we didn’t add all of those delays to our code, we could have figured out some way to return the ends of the branches to the caller of growTree(). So we need to figure out another way to get all those branch ends…

One way that we might try first is to have an array that we just put the branch ends in. We could either pass the array into growTree()/growBranch() or we could use a global. Either way though, we’re never quite sure when all the branches are in the array yet. We could try to compute the number of branches we expect, but the we’d still have to wait and poll for the right number.

Wouldn’t it be better if we just got notified when all the branches were created?

Enter Promises

“I’m sorry, but I’m just thinking of the right words to say” for this section which is hard to explain

Promises are a great way to do just that! Let’s say that our growTree() function returned a promise that whenever the branches were all computed and drawn, that it would tell us and give us the branches? Then we could take those branches and use them for our flower animations.

Since growTree() is just a call to growBranch() with special arguments, it makes sense to have growBranch() just return that promise. But growBranch() recursively calls itself, albeit in a delayed way. So if we say that whatever growBranch() returns is a promise to an array of endpoints, we can start to build up a recursive “tree” (now in the Computer Science™ sense) of promises. When we get the results of all these promises, we can flatten them out and get an array of branch endings.

There are a few different aspects of the Promise library going on here, so let’s look at them individually:

  • Promise.resolve() is a utility function that says, “I promise I’ll give you this value, but, like, right now.” It just makes it consistent that we’re always returning a Promise from the function so whoever calls it, including ourselves, can treat it as such.
  • Promise.all() is another utility used for promising that you’ll return an array of all the promise resolutions (the actual thing that you promised to return) that you pass it. In other words, it waits until several things are all done, then returns them as a bundle.
  • new Promise() lets us create a new promise in a totally custom way. It takes a function that it calls with a resolve() function. When you call resolve() within that function, it resolves to the argument you gave to resolve(). It can be really tricky to wrap your head around the first time you see it, but what’s happening in our example is that we’re just trying to give back the output of the recursive call to growBranch() back to the parent. If that didn’t make sense, Mozilla has a much clearer, more in-depth explanation.
  • Promise.prototype.then() lets us chain computation onto the end of a promise. In this case, we want to flatten out all the resolved values from a tree into a flat array of endpoints.

The final product

So I hope this all made sense, but there’s a lot going on so it might just be helpful to fork and play around with this CodePen with just the tree.

Run the inspector and you should be able to see an array of all the endpoints output to the console. The code in this pen is slightly different than the code in the gists above, which I tried to simplify for clarity.

Finally, once you get a sense of the tree animation above, you can check out this pen which uses the branch endpoints to grow the flowers:

If you end up using this technique, please share it with me on Twitter @OngEmil or as a response below! 🌸🌸🌸

Ok, I’m not going to post that gif that asks to you and follow if you liked this post, so in return… Will you 💚 and follow if you liked this post? :D