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. 😊
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:
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):
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?
“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:
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