Painting with The HTML5 Canvas :A small presentation.
What is a Canvas ?
Think of your HTML5 canvas as a simple drawing surface that provides some powerful drawing tools. This canvas
might be used on an html page for immediate display, but also behind-the-scene to prepare some graphics for later use.
The drawing tools are provided through a Context, and most web Browsers offers two (exlusive) ways to draw
on a Canvas : WebGL and the Context2D.
WebGL is a very efficient but also very complex context, meant for demanding 2D or 3D graphics.
This presentation will focus on Context2D, a context meant for 2D drawing only, way simpler than webGL , and
powerful enough for most drawing tasks .
In your Html file, add a canvas, give him a size (width, height), in pixels, and an id :
<canvas width=640 height=480 id='myCanvas'> </canvas > // do not copy paste this html code but re-write it (issue with the < @gt; and html)
var myCanvas = document.getElementById('myCanvas'); var context = myCanvas.getContext('2d');
The drawing tools will either A) change the setting of the canvas or B) change the color value of some pixels : let’s
take a simple example and draw a red rectangle :
context.fillStyle = 'red'; // let's use red a filling color context.fillRect(100, 100, 80, 40); // drawing a (red) rectangle starting at (100,100) sized (80, 40)
What a Canvas is NOT !
There’s a common misunderstanding of the canvas, especially from the ones having a strong Html background, that is
to see the canvas as a classical html container, just like, say, a
div, in which you would basically add/remove some
visual elements in pretty much the same way you do it in regular hmtl.
But this is completely wrong : when you draw, you actually only change the values of some pixels within your
graphic card, and that’s all. No ‘scene graph’ is built, meaning the canvas has no memory of what happened before : you
cannot query a previously inserted element to change it, remove it, make it draggable, or what-not.
Hence any logic that goes beyond drawing, must be done by yourself (or by an external library of course).
Rq : Those who likes to build scene as a way of drawing (building step-by-step some queriable objects) should have
a look either at an canvas helper library (createJS, fabricJS, … ) or have a look at the SVG format/Object.
Mind the current status
Using the Context2D is always done in two steps : you 1) first setup the (fill or stroke) color, the width of the line, the font when
dealing with text, and any paramaters then 2) you draw a figure.
When you draw (fill or stroke), the current settings values at that point of time will get applied.
Let’s look again at the red rectangle example, that we’ll visually improve a bit by adding a shadow :
context.fillStyle = 'red'; // let's use red a filling color context.shadowColor = 'black'; // shadows will be black context.shadowOffsetX = 10; context.shadowOffsetY = 10; // shadow will be drawn (10,10) below the geometry context.fillRect(100, 100, 80, 40); // drawing a (red and shadowed) rectangle starting at 100,100
You should notice here that the final draw call (fillRect) did not change : what did change is the status of the context by
the time of that call -here it is set to : red fill and black shadow.
Principle is that all the changes you make to your context are ‘sticky’ – remains active until you discard them – which is
why the Context2D is called a ‘state machine’ -a machine that internaly handle some state variables for you-.
!! it can lead to tricky bugs if you don’t keep this idea always close to your mind. !!
Now you may wonder how you’ll keep some order in your drawing code : what if i want to draw one thing with a shadow,
and another with no shadow ?
Hopefully we have a life-saver here, which is the save/restore principle : let’s see it in action :
context.save(); // save all status variables here context.fillStyle = 'red'; // let's use red a filling color context.shadowColor = 'black'; // shadows will be black context.shadowOffsetX = 10; context.shadowOffsetY = 10; // shadow will be drawn (10,10) below the geometry context.fillRect(100, 100, 80, 40); // drawing a (red and shadowed) rectangle starting at 100,100 context.restore(); // !!! restore all status variables as in the last save()
Here, after that code has run, we have a red shadowed rectangle drawn, and the current status of the context is exactly
what it was before (not red – no shadow).
Any time you wonder if a change you make to the context will affect other drawings you make, do
not wonder a second, and save (before) / restore (after), so you can be sure no specific settings will be applied on
Two ways to draw
The Context2D offers two ways of drawing :
• Some drawing commands are immediate, meaning they will use the current status of the context to immediately
change the pixel colors . They are used for most common simple cases (see the above fillRect example).
• Some drawing commands are retained : they do not affect the pixels until you perform a stroke() or a fill(). They
are used in more complex cases, when drawing shapes built out of bezier curves, lines, …
The immediate commands of the Context2D are :
• fillRect / strokeRect : draws either the (filled) rect or just its outline.
• fillText / strokeText : draws either the text or just the text outline.
• clearRect : erase a part of the canvas.
• drawImage : used to draw an (or part of an) Image or canvas on current canvas.
• createImageData / getImageData / putImageData : (advanced) used to get/set the raw values of pixels.
• toDataURL : used to create an Image out of the Canvas.
Draw complex figures
The Context2D also offers some ‘retained’ commands that allows to draw more complex geometry. You can build
those geometry out of : lines, rect, arc (= part of circles), bezier curves, quadratic bezier curves, and arcTo.
The key thing to remember here is that you should always start a retained draw by beginPath, then build your
geometry step by step. Rq that nothing is drawn until you choose to stroke the outline or fill the whole geometry. You can
use closePath to automatcally link (by a line) the last point and the first point of the geometry.
You remember Context2D is a state machine, right ? So think of this way of drawing just like you woud do with a pen, which
‘remebers’ where it stands. Questions are : Where is the pen right now ? Where do i go from there ?
Here are the various retained commands of the Context2D :
• beginPath : start a new path. required for every new draw.
• closePath : Links the last built point to the first point by a line. Only if you need it.
• moveTo : unlift the pen, then set it at the provided point.
• lineTo : links last point to the provided point by a line.
• rect : adds a rect path to the current path. ! Rq that it does not draw immediatley like fillRect or strokeRect !
• arc : adds a (part of) circle to the current path.
• bezierCurveTo, quadraticCurveTo, arcTo : adds the corresponding mathematical curve to the path. All those curves
have a ‘smooth’ aspect.
A small example, a blue triangle :
context.beginPath(); // Start a new path ( == get rid of previous path) context.moveTo( 80, 40 ); // move the pen at ( 80, 40) context.lineTo( 140, 120 ); // line to (140, 120) context.lineTo( 20, 120 ) ; // (horizontal) line to (20, 120 ) context.closePath(); // close the path --> line to (80, 40) // notice nothing is drawn yet at that point context.strokeStyle = '#FF6600' ; // stroke in red. Use a colorpicker to find the value you want. context.lineWidth = 4 ; // use a 4 pixel width line context.fillStyle = '#AAAAFF'; // fill in blue context.fill(); // fill the current path (= the triangle) context.stroke(); // stroke the triangle outline
Let’s fill those geometry nicely
You might stroke or fill with a single color, sure. To define a color, you have various ways :
• an html color string : ‘yellow’, ‘red’, …
• a rgb string : ‘rgb( 23, 150, 80)’
• a hsl string ‘hsl( 39, 75%, 75%)’ . Hsl is very handy to quickly create a coeherent palette.
• an hexa rgb string : ‘#FFE118’. This is the fastest way, if performances matters.
You can use a color picker (online or from your graphic editor) to find the right color.
BUT what about pouring even more colors ? The canvas provide two gradients for your viewing pleasure : a linear
gradient, and a radial one. To build a gradient, you must choose the part of the screen you’ll paint, then you define some
‘color stops’ : the color steps by which the gradient goes.
var grayFade = context.createLinearGradient(10,10,100,100); // create a gradient from (10,10) to (100,100) grayFade.addColorStop(0 , 'rgb(100, 100, 100)'); // starting at drak gray grayFade.addColorStop(0.3, 'rgb(180, 180, 180)'); // quickly becoming brighter grayFade.addColorStop(1.0, 'rgb(255, 255, 255)'); // then fade to white // in use : context.fillStyle = grayFade; context.fillRect(10,10,90,90);
BUT you can also use some kind of (simple) texturing, and have a pattern repeated with the createPattern method. Very usefull
to build a wall out of a brick photo. Notice that the pattern might be built out of a canvas, so you can build a small off-screen
canvas, draw one star on it with various colors on it, then create a pattern out of that canvas and fill the on-screen canvas with
that pattern !
Transform / Clip / Go Composite
Well now you drew some nice things using lines, bezier curves, and such, but you’d like to draw all this twice as big : how ?
Hopefully you have transforms just for that : you can translate, scale, rotate your context at any time to re-use some
drawing code in another way and do some nice effects. Mind that rotation angle is in radian, not degrees.
A small example :
context.save(); // preserve the context context.translate(canvas.width/2, canvas.height/2); // translate to the middle of the canvas context.rotate( Math.PI / 3 ); // let's rotate a bit context.scale(3, 3); // let's scale by a factor 3 (on both x and y) context.fillStyle = 'red'; // let's use red a filling color context.fillRect(0, 0, 40, 40); // drawing a (red) rectangle starting at (0,0) sized (40, 40) // in fact, the rect will be drawn // in the middle of the screen, rotated, and with a (120, 120) size context.restore(); // restore context as before -no more transforms-
Another powerful feature is the globalCompositeOperation modes. They allow to change the way pixels are drawn, on a
per-pixel basis. You can use it for doing advanced masking with bitmaps. Use scenario are very specific, but they are very
quickly performed by most browsers. Notice that you can also define a blend mode, that will allow to do nice effects on colors
(the kind of effects high-end drawing software provide)
A scheme of the various composite modes is here :
It shows the output of drawing a new circle on an existing rect.
Last powerful feature is the clipping. You can define a part of the screen that will be the only part affected by all later drawings.
To define it, just create a path that defines the preserved area, and call clip ! Then do your drawings as usual.
You’ll most likely want to save/restore to avoid the clipping to remain permanent.
Small example :
context.save(); // save context context.beginPath(); // new Path context.arc(50,50, 30, 0, 6.28 ); // circle at (50,50) context.clip(); // now we are clipped context.fillRect(50, 50, 30, 30); // draw a rect : it will have rounded borders context.restore();
This page is useful for quick reference about Context2D methods / properties ::
Complete official reference for the Context2D : a bit harsh, but always true :
This is a long read, but this is a complete tutorial, very useful for graphic coding beginners
A quick cheat sheet for the Context2D :