This week we will take a little time going over canvas composition. Some of this is easy, some of it takes a little thought. Still it is all faily simple if it wasn't it wouldn't be in Chapter 1. Our composition methods and variables handle, as you might guess, composite shapes. That is shapes on top of shapes. By default any shape drawn to the canvas that follows a previously drawn shape is drawn over. If you were to put two rectangles slightly offset from eachother with different colors the second one to come up programatically would be drawn over top of the first one as you can see from this pen:
However, what if we don't want that. What if we want the blue to be slightly transparent so we can see the red through it. What if we want the blue to be behind the red. This is what we are talking about when we are refering to composite images.
Starting off we will be talking about creating shadow effects. These are processor intensive effects. Once you are animating on canvas it will be important to keep this in mind. Keep these to a minimum. Once we get into animating I will go more in depth into optimizing performance but for now and especially if you are doing anything static shadow is ok. Now if you have ever used CSS box-shadow it works basically the same way. We have 4 variables
The first is fairly obvious it is a string color/color code. shadowBlur is the amount you want it to blur the bigger the number the more it is blurred. OffsetX and Y indicate how far you want the shadow to be offset from the shape along the x and y axis. This one is pretty simple and works like so
ctx.shadowColor = "#000"; // Color default is black ctx.shadowBlur = "20"; // blur amount ctx.shadowOffsetX = "15"; // offset-x amount ctx.shadowOffsetY = "15"; // offset-y amount // Draw your shape now that these are set ctx.fillStyle = "#F00"; ctx.fillRect(W/2,H/2,100,100);
Here is a live example
Note: If you do not save the canvas prior to setting this you will have to manually reset all these variables or everything that you draw after will have this shadow on it. What you didn't think save() and restore() were going away now that we aren't working on transforms did you. Like I said even things like fillStyle are saved when you use save().
Opacity is controled by the globalAlpha variable. This variable takes a number between 0 and 1. Zero being completly transparent and 1 being completly opaque. This is another very simple thing to use. So let's recreate our initial pen with the blue and red square since I still want to see that red square. The globalAlpha variable handles the alpha channel for the entire canvas. Hence the name global. So let's draw our red square set our global alpha to say 0.4 and draw our blue square. I picked 0.4 because if you start getting much higher than that thing become purple very quickly and less clear.
ctx.fillStyle= "#F00"; ctx.fillRect(W/2-50, H/2-50, 100, 100); ctx.globalAlpha = 0.4; ctx.fillStyle = "#00F"; ctx.fillRect(W/2-40, H/2-40, 100, 100);
And the live exmple
As you can see this to is rather simple.
In clipping we will use beginPath() and our path tools (i.e. arc(), lineTo(), moveTo(), etc) to define a region on our canvas. Once we have done that we can call clip() and we will no longer be able to draw outside that region. As if we clipped the rest of the canvas off.
// Call save so we can save // our normal canvas ctx.save(); // First we defin our path // This will be the region we want // To clip ctx.beginPath(); ctx.moveTo(W/2 - 300, H/2 + 200); ctx.lineTo(W/2 + 300, H/2 + 200); ctx.lineTo(W/2, H/2 - 200 ); ctx.lineTo(W/2 - 300, H/2 + 200); ctx.closePath(); // You do not have to call stroke // I am doing it for the visual ctx.stroke(); // Call clip and the defined are // is now the only region we can draw // in ctx.clip(); // Let's make some shapes to prove it ctx.fillStyle = "#F00"; ctx.fillRect(W/2 - 300, H/2 - 100, 600, 70); ctx.beginPath(); ctx.arc(W/2, H/2 + 150, 140, 0, Math.PI*2); ctx.fillStyle = "#00F"; ctx.fill(); // restore our canvas ctx.restore(); // now we can draw outside of the // clipped region ctx.fillStyle = "#0F0"; ctx.fillRect(10,10,100,100);
Notice how the red Rectangle and the blue Circle are not able to actually be drawn all the way. This is because of the clipping. Imagine taking a triangle stencil and trying to draw a retangle that is wider than the triangle you are drawing through.
Next we will take a look at the globalCompositeOperations variable. This one will take a little longer to go through. This variable defines what we do when two shapes are drawn in the same place on the canvas. We will be using two terms you are most likely not familiar with, Source and Destination. Source refers to the image that is currently being drawn. Destination refers to the images that are already drawn on the canvas. There are 26 possible settings in this variable but we will only be going over 12 of them today. The other 15 are blending modes and are (at this time) only supported in Firefox and Firefox mobile.
- source-atop : this will draw the destination and only the portion of the source that is over the destination
- source-in : this will draw only the portion of the source that is over the destination
- source-out :this will draw the part of the source not over the destination
- source-over: this is default behaviour the source is drawn over the destination
- destination-atop : this will draw the source and leave the part of the destination that is over the source
- destination-in: this will draw the part of the destination over the source
- destination-out: this will draw the part of the destination that is not over the source
- destination-over: this will draw both source and destination with the destination on top
- lighter: this will draw a lighter composite color of the source and destination where they meet
- darker: this will draw a darker composite color of the source and destination where they meet
- xor: this will draw everything but where the source and destination meet
- copy: this ignores the destination alltogether and draws the source. good for copying from temp canvas
Yes this is a lot to sink in. The more you use it the easier it will get I promise. There is a pattern to what each of these do. Think of it this way. If it starts with source then the source is the most important thing that is being drawn if it starts with destination then its the destination that is important. This next pen will let you play around with the various Composites and see what they do. Now you may need to open this in the codepen editor. Until I can figure out a way to increase the size (and the iframe size is inline so yeah fun) this pen really doesn't work well at 268px.
Well that is all I have for you today. Next week I will be putting out some exercises to help you get some practice on all this and then we move on to Chapter 2 where we will discuss animation and canvas input. I will also be inserting a new type of article, pre-section prep articles. These will be for teaching fundamental principles and things that not everyone might know about before getting into the actual lesson on what ever it is we are working on. For instance, before we get into animation I will be doing a pre-section prep article on animation principles, IIFE, and the difference between binding a function to a variable and calling a function and using the return value as a variable. These are things people might not know or understand and that understanding will make learning the next section a whole lot easier. At the top of these prep articles I will be putting a list of the objectives covered in the article. If you feel confident in your knowledge of the subject you are welcome to skip that prep article and move on. I have not decided yet if these will be posted in conjunction with the normal Friday posts or if it will be done as the actual Friday post. I will decide by the first article however. As always if you have any questions feel free to reach out in the comments section below or join me on the tuts subreddit r/LetsDoCanvas and ask all the questions that you would like there. Until next week happy coding.