How to create christmas tree - Canvas API
What you will learn
- how to use canvas API to draw shapes
- how to use Path2D to define shapes
- how to draw only inside a specific Path2D object
- how to create infinite color generator
- how to use JavaScript objects as configuration for functions
- how to use arrow functions as providers for different types of data
- how to refactor existing code during the process of creating more complex drawings
Preparation
Let’s start with adding two elements to html page: canvas
(the text inside will be displayed by the browser only if the browser is incapable of rendering the canvas element) and script
which will contain a constant representing canvas element and the code that draws on the canvas:
|
|
Drawing background
Inside the script
you can put this simple code:
|
|
This code:
- finds an element with id tree in the document - the canvas element t
- uses the canvas element to get a drawing context - ctx
- sets the fill color (which is part of the context state) to navy
- draws a filled rectangle with top left at (0,0) and bottom right at (t.width, t.height)
This is the canvas:
Drawing a tree shape
The next step is drawing the christmas tree shape. This will be just a simple isosceles triangle. I will assume that:
- top vertex of the tree is at 0.9 of the total height, in the middle of the available horizontal space
- bottom vertices of the tree create a base of the tree and are at the 0.1 of the vertical space
- calculation of vertices coordinates require knowing the width and height of the available space, so I need to refactor the
basicCtx
so that it receives the id of the canvas and width and height and returns the ctx (so that it can be passed to a separate function for drawing)
|
|
Then I need to calculate the coordinates of the vertices. I use Vertex
function to create new vertices. Drawing and filling the polygon consisting of vertices I’ve just created is done in drawVertices
function which takes:
- drawing contest
- color
- an array of vertices
and uses several functions of the drawing context, namely beginPath
, moveTo
, lineTo
and closePath
to create a path. Such path is then filled using fill
function:
|
|
Gradient
Next step is to use a gradient instead of a simple color. In a call to drawVertices
I will use a linear gradient. It will start at vtop
vertex and will end up at the bottom of the tree. It will consist of two colors only:
|
|
It’s worth extracting the gradient creation code to a separate function, which will receive data necessary to create a simple gradient: two colors and a “color stop” of the second color (which take value from [0, 1] range and represent a location on gradient line where this exact color appears). The first “color stop” is always at 0 as the gradient expands starting from the top pf the tree.
Let’s write a function named linear
which takes gradient configuration and returns a one-argument function. This function, when given a context - would create and return a gradient.
|
|
A path
One of the ways of creating complex drawings in JavaScript API is using a Path2D interface which allows not only to create polygons but also to create arbitrary SVG paths.
Let’s change the code that draws and fills the tree in drawVertices
so that it takes a context, a path and a color and fills the path with the color on the context.
The path creation is a different task than path drawing so it can be extracted to a separate function:
createVerticesPath
function takes a list of vertices (Vertex) and returns a closed path; I use this function to createtreePath
- a Path2D object representing the tree/rectangledrawVertices
function is renamed todrawPath
so that the name refects better what happens inside: it draws a path
|
|
Joining paths
In order to create a path that consists of many subpaths - and to be able to fill all the subpaths at once, for example - I need to use addPath
method.
See below:
- I use a
treePath
Path2D for drawing a tree - I create a path with many subpaths:
createBaubles
function returns a table with paths representing baubles andjoinPaths
joins all paths into one path (all baubles are joined together)
|
|
And here’s the picture:
Clipping path
Well, the baubles need to be tamed a bit: I will draw them only inside treePath
path. I wil use clip
method in CanvasRenderingContext2D
interface .
After calling this method, everything that is drawn on the context on which this method was called will not be drawn outside of the current path (if clip
is called without arguments) ir outside of the given path (if a path was given as argument) and will only be visible inside it.
I will use treePath
as a clipping path and I use it on main context; drawing random baubles will now be restricted to (and visible inside of) the treePath
.
This is the code:
|
|
And this is a tree with “tamed” baubles:
Colors for the baubles
Red color is too intense and bites the eyes. I want each bauble to be of different, less intense color.
I will not join baubles but draw baubles in the loop, using next color from the color generator:
|
|
The above generator uses different hue values (increasing in steps defined by del
) for generating colors, but saturation and lightness are given as generator’s configuration. I also use default values: for saturation it is 80, for lightness it is 80, for delta it is 10.
A drawing:
A gradient in the background
I want to use a gradient in the background, so I will change basicCtx
. Now it has a hardcoded background color:
|
|
I will pass a function that can create something to be used to fill the background (color, gradient or pattern). By default, this function will return the color which is currently hardcoded. But my goal is to pass a function returned by gradient-cretaing linear
function:
|
|
And now I can easily create simplel gradients for a background and for a tree shape:
|
|
More christmas trees!
I want to draw more christmas trees, so that my readers know that the blog post is really in the right season. Let’s plant a few of the trees next to each other.
I will write drawTrees
function, that receives a context and a configuration object. The object contains:
gradientFn
- function that retuns the fill for the treeoutclip
- function that - when called with a context - will draw something using the whole context (before a call toclip
)inclip
- function that - when called with a context - will draw something inside the clip path, i.e. the tree shape (after a call toclip
)treesCount
- number of treestreePath
- path of a tree, also used as a clip path
|
|
Baubles drawing function will also be parametrized - configuration object passed to drawBaubles
contains three elemens:
count
- number ob baublescolFn
- function that returns the color of the next baublerFn
- function returning the size of the bauble
|
|
The last step is the creation of the configuration object for drawing baubles and, finally, doing the actual call to drawTrees
.
I also implement callable
function that will wrap a generator into a function that can simply be called to get next value from the generator. This way I can wrap existing colorGenerator
and pass it as colFn
parameter to drawBaubles
.
|
|
Chains, stars and snow
It would be nice to place a gold star at the top of each tree, to put some christmas tree chains and have white snow falling down!
Chains are drawn inside a clip path (I simply draw arcs), but the stars are drawn outside a clip path - this is how I can re-use or extend drawTrees
(which is now a bit like a Template Method) - I just carefully construct inclip
and outclip
values of configuration object.
Merry Christmas!
|
|