Today we are going to make a canvas animation with beautiful blooming flowers step by step. You can follow through in this blog post by running the StackBlitz project, and you’re welcome to check out the source code in this GitHub repo.
In my recent blog post, I described a high level view of composing canvas animations using TypeScript. Here I will present a detailed process of how to model objects and animate them on canvas.
First things first, we need a function to draw flowers on the canvas. We can break the parts of a flower into petals and the center (pistil and stamens). The center of the flower can be summarized as a circle filled with colour. The petals grow around the center, and they can be drawn by rotating the canvas with a certain degree of symmetry.
Note that the bold nouns (flower, petal, center) indicate the model in the code. We are going to define these models by identifying their properties.
Let’s first focus on drawing a petal with some essence. Inspired by this tutorial, we know that petal shape can be represented by two quadratic curves and two Bezier curves. And we can draw these curves using quadraticCurveTo() and bezierCurveTo() methods in HTML Canvas API.
As shown in Figure 1(1), a quadratic curve has a starting point, an end point, and a control point that determines the curvature of the curve. In Figure 1(2), a Bezier curve has a starting point, an end point, and two control points.
In order to smoothly connect two curves (any two curves, either quadratic or bezier, or other), we need to make sure that the connection point and two nearby control points are on the same line, so that these two Curvature at the point of connection be equal in curves.
Figure 1(3) shows a basic petal shape consisting of two quadratic curves (green) and two Bezier curves (blue). There are 4 red dots representing petal vertices and 6 blue dots representing control points of the curve.
The lower red top is the center point of the flower and the top red top is the petal tip of the flower. The middle two red corners represent the petal’s radius. And the angle between the center point between these two vertices is called the petal angle span. You can play with this StackBlitz project about petal shapes.
After defining the petal shape, we can fill the shape with a color and get a petal, as shown in Figure 1(4). With the above information, we are happy to write our first object model: Petal.
Since we have a petal and a flower center, we are ready to move on to drawing a flower that has a center circle and several petals with the same shape.
From an object-oriented perspective, the flower can be created as a new flower (center: flower center, petal: petal) or as a new flower (center: flower center, numberOfPetals: number, petal: petal ). I use the second way, because an array is not needed for this scenario.
In the constructor, you can add some validation to ensure data integrity. For example, if center.centerPoint doesn’t match petal.centerPoint , throw an error.
Note the dropettles (reference) method. Since the rotation happens around the center point of the flower, we must first translate the canvas to move the origin to the flower center, then rotate the canvas. After the rotation, we need to translate the canvas back so that the origin is past (0, 0).
Using these models (Flower, FlowerCenter, Petal), we are able to get a flower like Figure 1(5). To make the flower more solid, we add some shadow effects so that the flower looks like in picture 1 (6). You can also play with the StackBlitz project below.
In this section, we are going to animate the process of flowering. We will simulate the blooming process as the petal radius increases as time goes on. Figure 2 shows the final animation with flower petals spreading over each frame.
Before we do the actual animations, we might want to add some varieties to the flowers so they don’t get boring. For example, we can generate random dots on a canvas to scatter flowers, we can generate random shapes/shapes of flowers, and we can paint random colors for them.
This kind of work is usually done for the purpose of centralizing logic and reusing code in a specific service. Then we put the randomization logic in the FlowerRandomizationService class.