Canvas Chapter 1 Section 1

So you want to make JS art

So for our first venture into this series we will be working on some basic things. This will probably be a longer section than the others but there is a lot to get in. So, get your OOP hat on and let's have some fun. Now I love codepen for playing around with JS. So I like to keep a pen in my folders that I can just fork to get this basic stuff out of the way. I should also point out there is no one way to code. I want to stress this because I will be doing this tutorial series in my style of coding. I will admit as I learn more this style changes. So keep in mind there are a number of ways to do things. As long as it works it's not really wrong(though there are somethings that are more efficient). Coding is a funny beast like that. I will also say that since I will be doing these once a week you will have more than enough time to play around with what you learn and do do that play around with your code. It's is a great way to learn how things work in this world. This will especially be true once we get to things like velocities and gravity. A little change can make for some very different results. Last byt not least do not copy and past don't even fork anything. Sometimes this can be a great thing. I admit I do it if I want to play with someones code and see how different variables change the code however, especially for a lot of the things I will be teaching you they build on each other and practicing yourself using the code will help you retain what you are learning. If you do not well not my fault if you can't keep up. Don't do the disservice to yourself. Type the code yourself.

Understanding the objects involved in canvas.

There are certain things you should be aware of in canvas. One is the window object. If you have spent time coding with JS you know about this object. If this is a new delve into this world. Make sure that you remeber this thing exists. It will help with things like setting your canvas size timeouts/requestanimationframes in animation honestly it is just a good all around thing to realize about in JS. The window object is kind of the king. The second is the canvas object itself. The canvas is one solid object. No matter what is drawn on the canvas it is still just the canvas object. You fill in the context of the canvas when you draw on it not add new objects. This means that the things you are drawing are not their own objects just paint on a canvas. for example if you have a ball on your canvas the ball itself has nothing to click on. If you click on the ball all you are doing is clicking on the canvas. This is an important distinction once we start working on things like click and drag events.

Setting up our canvas

So let's start with initializing our canvas. There are a few ways that you can do this. Personally, I don't like to add the canvas dynamiclly through JS but it is something that you can do using the createElement and appendChild JS methods. For today thought we are going to start simple:

In our html box or in your html file if your going that route:

      <canvas id="canvas">No canvas support</canvas>


Just a real simple html tag. We set our id to canvas, which will help up use the canvas in JS later on, and add a fallback incase someones browser doesn't support canvs. Anything typed inbetween the open and closing canvas tags will be what someone sees when they don't have canvas as an option :-( sad face. Next we will do a small bit in our CSS file/box:

  *{
padding: 0;
margin: 0;
}

body{
overflow: hidden;
}

This will give us a real simple reset ( all padding and margin set to 0) and will make sure that when we set our canvas size dynamically in JS that it will take up the whole window and not allow it to grow beyond with scrolling. Now that we have those set for this section we will not need our html or css files/boxes anymore. Personally I close them at this point. It gives me more room to work in JS and it is so much easier to read.

Next we will initialize our canvas and set the size. Now this is another thing you can do different ways. You can make a canvas class/object array in JS you can make an initializtion function ie the procedural paradigm and you can mix them both to do kind of a functional/OOP thing today however we are going to just write it like we are writing a list of commands(don't worry the OOP anf functions will come later)

  //Initialize and set our variables since we will be writing this straight
//First we set our canvas element to a variable
//So we can work with it
var canvas = document.getElementById('canvas') 
//Next we get the context of that element.
//If you have used JS enough it's the innerHTML 
//of the canvas world with a little extra we tell
//the canvas what type of drawings it will be making
var ctx = canvas.getContext('2d') 
var W = window.innerWidth; //Set a variable to our window width
var H = window.innerHeight; //Same with the height.
// We can then take those variables and set the canvas
// width and height dynamically
canvas.width = W;
canvas.height = H;

Now it may not look like much yet but it will soon. Right now all you have is a white blank canvas the size of your screen. What can we do with that. Well let's start with our basic shapes. To begin we will be learning how to make rectangles. They are probably the easiest shapes you can do on here and we will be using them to add color to our canvas. Why because I like working with a black background usually makes errors a lot more obvious. In order to make a rectangle we will be using some other functions and learning the coordinate system of canvas.

Coords

Canvas coordinates are an x,y graph system. Any point can be expressed as (x,y). The origin for these points (x: 0, y: 0) is in the upper left corner of the canvas object. When you set the width and height of the canvas before you were setting the max x and max y coordinate. If you were to set the width and height like so

  canvas.width = 100;
canvas.height = 100;

You would get a 100 by 100 canvas with the top left being (0,0) and the bottom right being (100,100). The coordinate system is based of this. So with our width and height set to the window size if we were to place an object on the canvas at (50,50) it would show up 50px from the left of the browser and 50px from the top of the browser window.

Methods and variables

We access the functions through the canvas context which we have set to the variable of ctx. This like everything else in JS is an object and it comes with it's own methods and variables. The first variables we will be working with are fillStyle and strokeStyle. These two are used in a number of places. Using fillStyle will set the fill color or the inner color while strokeStyle will set the outline or border color. As we go through methods and variables marked in green can be used in other shapes not just in the section they are talked about.

Rectangles

So let's set our strokeStyle to black and our fillStyle to red (I do so love the colors black and red)

  ctx.fillStyle = "#F00"; // accepts HSLA RGBA HEX and words like black
ctx.strokeStyle = "#000";

Now that we have our colors set we can create a rectangle. We do this by calling the fillRect and strokeRect methods on ctx. fillRect will take the color of fillStyle and strokeRect will take the color of you guessed it strokeStyle. The methods take 4 paramaters the x position of the top left corner of the rectangle the y position of the top left corner the width of the rectangle and the height of the rectangle

  ctx.fillRect(50,50,50,50);
ctx.strokeRect(50,100,50,50);

Now you should see two boxes a filled box that is 50x50 pixels away from the top left corner that is 50x50 pixels in size above an empty box 50 pixels away from the left 100 pixels away from the top that is 50x50 pixels in size.

So what happens if we take it a step further and paint the whole canvas? We get a nice easy backdrop for just about anything. Since we have already the canvas width and height set as a variable we can easily make one solid shape with fillRect and cover the whole canvas.

  ctx.fillStyle = "#000";
ctx.fillRect(0,0,W,H);

Once you add that to your code you should see a black background and only one rectangle(remeber the stroked rectangle was set to a black color). Now that we have our backdrop we can move on to some other shapes.

Text

Next we will work on text. You may remove the fillRect and strokeRect code if you would like. I would suggest if you are going to clear everything or are going to clear what we were working on leave the fillRect that fills the canvas with black. You don't have to but the shapes we will be working with in my code will be white. If you choose a different background color or none at all make sure that you remeber that. Putting white on white doesn't work so well. So, creating text well that one is a pretty easy job. Once again we can use fillStyle and strokeStyle to set the color we want (color defaults to black if these are not used) but we will be adding 3 new methods/variables to your arsenal.

  • font
  • fillText()
  • strokeText()
  • textAlign
  • textBaseline
  • measureText()

After working with fillRect and strokeRect I'm sure you can gues what fillText and strokeText do so let's take a look at font. Font is a variable like fillStyle that takes serveral space delimeted options. It does require that at least the font family is entered. the format for it is this.

ctx.font = "font-style font-variant font-weight font-size/line-height font-family";

There are a few other options but that is the gist W3Schools has a list of the options you can use. I'll be honest I may not learn through W3Schools but as a quick refrence sometimes it is nice.

Once you have all your setting the next thing is filling in or stroking your text. The format will be

ctx.(fill or stroke)Text("text",x,y,max-width this is optional);

So lets put Hello World in the middle of our screen at a 30px Georgia font with white letters.

We would do that like so

  ctx.fillStyle = "#FFF";
ctx.font = "30px Georgia";
ctx.fillText("Hello World", W/2,H/2);

You should see something like this

Ok but the text isn't actually in the middle of our screen. Placing it at half the width and height should do that right. Well no, just like with the Rect methods text is placed starting in the upper left of the text. For this we will use textAlign and textBaseline. The format is like a so

ctx.textAlign = "[start end left center right]"; default is left

ctx.textBaseline = "[top, hanging, middle, alphabetic, ideographic, and bottom.]"; default is alphabetic

Adding that into our pen and we now see it centered

A quick touch on mesureText. measureText will take in a text string and give a width. It works like so

ctx.measureText("a text string or string variable");

Now that we know how to get the width of our text we can wrap text. To do this we will create a custom function that iterates through our sting of text adds each word to the a line and checks to see if it is bigger than the line width we specify. Once it hits that barrier it will move the next line to our y + lineHeight and do it again.

  function wrapText(context, text, x, y, lineWidth, lineHeight){
    var words = text.split(' ');
    var line = "";

    for(var n = 0; n < words.length; n++){
        var testLine = line + words[n] + " ";
        var metrics = context.measureText(testLine);
        var testLineWidth = metrics.width; //this gives us the actually width
        if(testLineWidth > lineWidth && n > 0){
            context.fillText(line,x,y); // If it is too big then go ahead and paint the text
            line = words[n] + " "; // set our line variable to the current word with a space
            y += lineHeight; // set our y to the next line
        }
        else{
            line += words[n] + " " //if not add our current word to the line variable
        }
    }
    context.fillText(line,x,y) //add our last line to the canvas
}

The code in action

Arcs Circles and Semicircles

Next we will take look at arcs. Once again fillStyle and strokeStyle can be used in this (are we noticing a pattern yet?). Working with arcs is a little different than working with rectangles. There is a bit more to them and the way the coords work for them is a little different. The new methods/variables for you in this section will be

  • beginPath()
  • arc()
  • lineWidth
  • stroke()
  • fill()
  • closePath()

To start with we will go over beginPath(). This method is literally to let the canvas object know this is a different thing from anything else we drew. This does make a difference later on. Not so much with only one shape but with multiple shapes letting the canvas know that it is drawing something different each time. With that does come closePath() it is pretty much the same thing. It lets canvas know you are done defining the shape. Both take no inputes and are simply written like this

ctx.beginPath();

Line width (lineWidth) is used to set the thickness of the border. The input is a number.

ctx.lineWidth = (some int);

The stroke and fill methods you will run into more and more in making non rectangular shapes(rect is the only native method for a specific shape). They both work like their counter parts strokeRect and fillRect or strokeText and fillText. The only difference is this time they require no inputs.

ctx.fill();

ctx.stroke();

Lastly we come to arc(). Arc is the meat and bones of creating arc circles ellipses(which we will get into in another tutorial) and semicircles. It takes several inputs like so:

ctx.arc(x, y, radius, startPoint, endPoint, clockwise/anticlockwise(this is a boolean and optional));

Starting off the x and y for this work differently. They represent the exact center of the shape. The radius is the radius but if you have no recolection of you math classes it is the distance between the center and any outside point on the circle. The start and end points this is at what point on the circle do you want to start drawing and what point do you want to end it. This is in radians. I hate working in radians but there is an easy solution for this. Including this variable (see code) as a global variable or creating a custom function (also see code):

  var piRads = Math.PI/180; // multiplying a degree by piRads now will give you radians


//This function will take a number in degrees and return it as radians
function getRads(degrees){
    return degrees * piRads; // or just times Math.PI/180 if you want. 
}

For those not familiar with a rad rads are the mesurement along the outside of a circle equivelant to the radius. They are a slice of pie and are always started at 0 which is to the right of the center. Some other things to keep in mind. PI is 180 degrees 2xPI is 360 degrees as far as radians are concerned. So if you start your arc at 0 and end at 2xPI/180 radians you will get a complete circle. However as I said I hate radians so keeping the getRads as a utility function is handy. For more info on radians go take a look at Math Wearhouse. Moving from that the next input is anticlockwise or clockwise (default is clockwise). If you pass in true the circle will be drawn from 0 rads on the right to however many rads going in a anitclockwise fashion. If you set it false or leave it blank it will draw the arc starting from 0 rads in a clockwise fashion. Ok so lets get to it and draw some arcs. To start out with lets draw a simple filled circle

  ctx.beginPath();
ctx.fillStyle = "#FFF";
ctx.arc(W/2, H/2, 100, 0, 2*Math.PI);
ctx.fill();
ctx.closePath();

That should give you a nice big white circle in the middle of your canvas a like a so

If this is confusing don't worry we are going to be making a few of these shapes.

So lets change things up and make just the border of this circle. like a ring. We will get it a small thickness and stroke the circle this time. So change your fillStyle to strokeStyle and your fill() to stroke() and before you call stroke() add a lineWidth = 20. It should now look like this

We are going to keep working with stroke for a little bit to get you used to actual arcs (if you want a semicircle just use fill instead with what I am about to run you through). So let's start by making a 90 degree arc from 0 rads to the bottom of the circle.

  ctx.beginPath();
ctx.fillStyle = "#FFF";
ctx.arc(W/2, H/2, 100, 0, 90*Math.PI/180);
ctx.fill();
ctx.closePath();

Once again the result:

Notice that the end point is converted to radians via simple formula in the arc call. From now on I will be using that helper function where I need to add radians. So lets try this. On your own create an arc that goes from the left of the circle (180 degrees or Math.PI rads) to the top of the circle. It should look like this.

If it does not keep in mind that rads are always counted from the 0 point which is the left of the center in a clockwise direction. The left is 180 degrees and the top is 270 degrees. Let's try another how about from 0 to 315 degrees. That should be from the right of the center and up 45 degrees. It should look like this

If you have a much longer arc then remember to set your anticlockwise to true. This also can be resolved by making sure that startPoint is 315 degrees and endPoint is 0.

Images

Now that we have played with arcs let's take a step back and do something a little easier before getting into drawing lines. Loading image assets will be something that will come in handy later. Using sprites help to limit the amount of processsing power is required to draw on the canvas. In this part we will learn

  • Instantiating a new Image object
  • Setting the image source
  • drawImage()
  • Creating an image loader

Each image must be instantiated into an Image object before it can be used. And this image must be loaded into JS before I can be used as well. Instantiating a new image is easy.

var objectname = new Image(width, height);

With objectname being what ever you want and width and height being optional. Next you set the source

objectname.src = "src url";

The lastely you draw the image using drawImage(). We want to wrap this in a function using an onload event. If you don't the image will never be drawn because it will try to draw and image that hasn't even been loaded. The whole thing looks like this.

  
var image = new Image();

images.src = "url";

image.onload = function(){
ctx.drawImage(objectname, x, y, width, height);
}


Width and height being optional but if you want to set a specific size on the canvas that is what you need to add. So let's try this. I already have a source image url for you to work with https://raw.githubusercontent.com/tourn171/canvasBlogAssets/master/Chapter1/Section1/images/puppy.png

We will just draw the puppy to the x = W/2 and y = H/2 coords. Drawing images works like the rect methods the top left of the image will be placed at the x,y coords. Since we will set a width and height to the image we also want to take of half the width and height from our x and y coords to center the image.

  // First we create an image object

var puppy = new Image(100,100);

// We set the source to the image location 


puppy.src = "https://raw.githubusercontent.com/tourn171/canvasBlogAssets/master/Chapter1/Section1/images/puppy.png" 

//Then we draw it to the canvas.
puppy.onload = function(){
  ctx.drawImage(puppy,W/2-50,H/2-50, 100,100);
}


This should put our puppy in the middle of the screen like this

Now that we have learned how to load and set out image size we should take a moment to understand cropping an image. This is important as this technique is used in sprite sheets to simulate movment. We are going to work with this https://raw.githubusercontent.com/tourn171/canvasBlogAssets/master/Chapter1/Section1/images/ninja-sprites.jpg sprite sheet for this exercise. To start out with let's put the whole sheet on our canvas in the middle of the screen. Now we are going to do this a bit differently. When cropping an image the format is

ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destinationX, destinationY, destinationWidth, destinationHeight );

Source X and Y are the (x,y) coords in pixels of the top left corner of the area you want to crop. The source width is the width of the crop area and the height is the height crop area. destination x and y are the coords for the left top corner of where you want to place the image on canvas. Destination width and height are the width and height displayed on canvas. Here is a visual representation (i know this one is everywhere on the interwebs but i like this one).

drawImage visual representation

So lets draw the full sprite sheet on the canvas. To make things easier for you the image width and height comes up perfectly at 330x279 (to help with loading times I kind of shrunk the asset by 50% so the numbers are a little odd )

  // So just like last time
// First we create an image object
var ninja = new Image();

// We set the source to the image location
ninja.src = "https://raw.githubusercontent.com/tourn171/canvasBlogAssets/master/Chapter1/Section1/images/ninja-sprites.jpg"

//Then we draw it to the canvas.
ninja.onload = function(){
  ctx.drawImage(ninja,0,0,330,279,W/2-165,H/2-139,330,279);
}

It should look like this now

Now let's try to get just one of the ninjas. It doesn't matter which one. The important thing is I want you to play around with the input values and see what you get. When you get done you will want it to look something like this.

Now that you have that down. Once we get to a point where we start learning canvas animation you will be able to do things like this.

Custom shapes: The path , Lines, and curves

Lines

Now that we covered all the easy basics, we can start to work on some of the more complicated low level canvas methods.

  • moveTo()
  • lineTo()
  • lineCap
  • quadraticCurveTo()
  • bezierCurveTo()
  • How to create a path
  • lineJoin
  • arcTo()

To start off we will be working with lines. I hope you remember beginPath() and closePath() because we will be using them a lot here. To start let's make a simple line. To do this we will be using beginPath(), moveTo(), lineTo(), lineWidth, strokeStyle, lineCap and stroke() Now some of this is not necessary to create a basic line. lineWidth, strokeStyle, and lineCap all have defaults and a line will be drawn without them. However, we are going to go for broke here and make 3 lines with the different lineCap options, depending on your background you may need strokeStyle and lineWidth will thicken our line to make our lineCap's more pronounced.

Because we are working with lines we use beginPath() to let our canvas know that until we close the path this is all one drawing. Our method moveTo() takes to parameters, an x and y coord, it is like if you are drawing on a sheet of paper and you pick up your pen and move it to a spot on the paper. lineTo() also takes an x and y parameter. It tells our canvas to draw a line from where our pen was placed in moveTo() or from where we last drew a line segment/arc/curve in our path. lineCap is a variable with three options butt round or square Note: lineCap will not work with closePath() because closePath() expects that the line closes in on itself. This tells our canvas that at the end of the line we draw this is how it should look. Butt is a straight flat edge with no extra width round is a rounded edge added to the line and square is a squared edge added to our line. Both rounded and square will add extra length to our line equal to the line width. So without further ado let's make a simple line

Here is how

  ctx.beginPath();
ctx.moveTo(W/3, H/2 - 100);
ctx.lineTo(W/3, H/2 + 100);
ctx.lineWidth = 20 ;
ctx.strokeStyle = "#FFF";
ctx.lineCap = "butt"; //this is default
ctx.stroke();


That will give you a line about 1/3rd of the way into your canvas with a length of 200px. Now lets create the other two lineCap lines. So on the same canvas create two more lines to the right of the first one with a round end cap and the other with a square end cap. It should look something like this.

Quadratic Curves

Next we will be working on quadratic curves. These and bezier curves can be very daunting. The idea is taking a start point (which will be the x,y in your moveTo() method) a control point and an end point drawing a curve on a tangent between these points. The format to create a quadratic curve is like so

ctx.beginPath();
ctx.moveTo(startX,  startY);
ctx.quadraticCurveTo(controlPointX,controlPointY,endPointX,endPointY);
// We can add closePath() here and it will automatically draw a line from
// our start point to our end point. 
ctx.stroke();

This is something that is easiest to explain when you play around with it but imagine it like so.

A quadratic curve

Look familiar, well unless you havn't gotten to your algebra class or slept through it. Remember that formula y=ax^2+bx+c. Yes, graphing function come back to bite you mwahahahaa. Your control point controls your vertex (the highest point in the arc) and the start and end points control the parabolas anchors (for lack of a better term) So say for instance your control point is directly over your end point you will get something like this

The further away your control point is the sharper the curve will be as it raises the vertex. I have created a pen to show you how this would work. Click and drag the points to change their position.

Play around with that and try to get a feel for how that works.

Bezier Curves

Next up bezier curves. Guess what same thing except more control points. Same idea in creating them

ctx.beginPath();
ctx.moveTo(startX,  startY);
ctx.bezierCurveTo(controlPoint1X, controlPoint1Y, controlPoint2X, controlPoint2Y, endPointX, endPointY);
// We can add closePath() here and it will automatically draw a line from
// our start point to our end point. 
ctx.stroke();

This way you can draw more expressive curves.

I would suggest looking here for a more detailed explination on them if for nothing else than the animations on how a bezier curve is drawn.

Putting it all together

So what good does all this do for me. Well know that you have an understanding of creating curves and and lines you can make more complex shapes. For instance how would you make a triangle. In fact let's do that let's make a triangle. In this we will be using lineJoin. This variable has three options mitter(the default), bevel, and round. Mitter is just the two lines butted up together. For instance take two rulers and put on on the other joined at the end so that neither have anything sticking out past the other. Notice the sharp edge. That is mitter. Round is litterally mitter but round the edge out smooth. Bevel is like mitter but the corner has been cut off. So here would be our triangle

ctx.beginPath();
ctx.moveTo(W/2, H/2 - 100);
ctx.lineTo(W/3, H/2 + 100);
ctx.lineTo(W/3+W/3, H/2 + 100);
ctx.lineTo(W/2, H/2 - 100);
ctx.lineWidth = 10;
ctx.lineJoin = "round";
ctx.strokeStyle = "#FFF";
ctx.closePath();
ctx.stroke();

And it should give you something like this

Now I used round for my lineJoin go ahead and change it and see the difference it makes. Also notice that I used closePath(). This closed the three lines back in on itself. Remove the 3rd lineTo() and see what happens. You should notice it stays a triangle because closePath is connecting the beginning and end of the connected lines. Now using this we can create any number of fluid shapes. For example:

Not the greatest shape I know but an odd one none the less. Knowing this though we can also create rounded corners with our beginPath() and the last drawing implement we will learn today which is arcTo(). The format for this method is

lineTo(startX, startY);
ctx.arcTo(X1, Y1, endX, endY, radius);

To illistrate this visually

arcTo visual

So applying that to what we already know working with paths we get an arc

That's all for this week. I will be trying to put out a new section each week on Friday. Until then play with what you have seen here and feel free to leave comments and questions below. Next week will be on drawing and altering individual pixels and some more styling options.