1. Home
  2. Resources
  3. HTML Canvas Tutorial

HTML Canvas Tutorial

HTML Canvas Tutorial

Modified 17.08.2011

Difficulty: Intermediate

Some HTML and JavaScript knowledge required.

Completion Time

1:00

HTML5 Canvas Support in webOS

Overview

In this article, we will introduce and discuss HTML5 Canvas concepts, and the use of Canvas in webOS applications. We will list the compatibility of specific Canvas tags in both webOS 1.4.5 and 2.0.

For reference purposes, a summary of supported Canvas tags is provided at the beginning of each section as well as here.

What is Canvas?

Canvas, or more correctly HTML5 Canvas, is a new markup element added to the HTML5 spec that lets you draw in a web page without using Document Object Model (DOM) APIs or plug-ins. Originally developed by Apple® to do real 2D drawing in their dashboard widgets, they proposed it as a web standard, and with the advent of HTML5, it has been adopted by most browsers.

Canvas is a 2D drawing API (with 3D support planned). Rather than creating graphics on-screen using HTML elements and other markup with CSS styling, you can directly draw graphics in JavaScript with commands like drawRect, fill, and drawImage. You can even manipulate pixels directly to adjust images and create special effects.

Canvas is important because it allows you to draw graphics directly, without resorting to div tricks. Because it is a web standard, most browsers have compatible implementations, meaning you can spend more of your time working on your app rather than fighting browser compatibility. Also, because it is a separate graphics viewport on the screen, it can be more easily hardware accelerated, resulting is dynamic and fast animations on platforms that support it.

What about browser support?

At this time Canvas is supported in Safari, FireFox, Chrome, Opera, and betas of Internet Explorer 9. For older browers there are compatibility libraries to help out. Virtually all smartphone platforms support Canvas, including HP webOS, iOS, and Android. webOS doesn't support every feature in Canvas, but more and more features are coming with each release. In the rest of this document, we will describe Canvas support in webOS 1.4.5 and webOS 2.0, and point out some issues to be aware of.

How is Canvas different from SVG?

While both be used to create graphics, SVG and Canvas are very different and have different uses. Canvas is an immediate mode drawing surface. SVG is a retained mode scene graph. This means that Canvas is lower level than SVG, but gives the developer more control and speed at the cost of more code and complexity. SVG is a higher level API, which lets the developer quickly draw shapes, but at the cost of fewer features and reduced speed. The biggest difference between them is that SVG is a resolution— independent vector API, while canvas is a resolution—dependent bitmap API. In general, Canvas has better browser support and can be used to draw vectors like SVG, so it gives you the best of both worlds.

Getting Started

The Canvas API is very simple to work with. Just create a canvas element in your page, grab a drawing context, then start drawing. The code below is the minimum amount needed to draw a rectangle.

<html>
<head>
<style type="text/css">
canvas { border: 1px solid black; }
</style>
</head>
<body>

<h3>Canvas Demo</h3>

<canvas id="can" width="100" height="100"></canvas>

<script language="Javascript">

// get a reference to the canvas element
var canvas = document.getElementById('can');

// get a context to draw on
var c = canvas.getContext('2d');

//set the fill color
c.fillStyle = "red";

//draw a filled rectangle
c.fillRect(10,10, 10,10);

</script>

</body>
</html>


Drawing a filled rectangle with the canvas element

You must give the canvas element an id so you can reference it in your code. You must also give it a width and height. Then, put a reference to the Canvas ID in your code and call getContext('2d') to get a drawing context. In the future, some browsers will support contexts other than '2d', such as the pending WebGL spec for 3D graphics. For now, we will just use '2d'.

Once you have a drawing context, you can begin drawing with commands like fillRect and draw(). That's all there is to Canvas drawing!

Since virtually all smartphone browsers support Canvas, you don't need to detect if it isn't available. However, if you are dealing with desktop browsers on the same page, then you should provide appropriate fallbacks. To read more about desktop support and fallbacks, see this Mozilla tutorial.

Shapes and Images

Drawing Rectangles

Canvas directly supports only rectangles, paths, and images. Anything else you'd like to draw, such as ovals or polygons, have to be drawing using paths.

Drawing rectangles is as simple as setting the fillStyle then calling fillRect with the x, y, width, and height of your rectangle. To draw just the outline of a rectangle, set the strokeStyle and lineWidth, then call the strokeRect function.

c.fillStyle = "red";
c.fillRect(10,10, 10,10);
c.strokeStyle = "blue";
c.lineWidth = 1;
c.strokeRect(15.5,10.5, 10,10);

Notice in the code above that we are drawing at an x and y of 15.5 and 10.5. When you stroke a shape, you are essentially tracing the outline of the shape using a pen with a width determined by the lineWidth property. The pen is centered on the edge of the shape, which means a one pixel wide stroke would actually draw a pixel outside and half a pixel inside, resulting in a blurry line. To make the line perfectly smooth, we offset the drawing by an extra 0.5 pixel. Whenever you stroke shapes with a non-even pixel-width, you may need to do offsets like this to make everything perfectly smooth.

The fillStyle and strokeStyle attributes must be set using the CSS color syntax, either with color names or hex values. Though not used as much, you can also clear a rectangle using the clearRect function. This will clear the background to fully transparent black.

Drawing Paths

A path in Canvas is a shape composed of line and curve segments. The path may be either open or closed. A path is closed if the first and last points meet, so that the shape can be filled. It is open if they do not meet.

Lines

Paths are defined by calling beginPath, issuing some drawing commands, then calling closePath. Once the path is defined, you can fill or stroke it as many times as you want. The following example defines a triangle using lineTo commands to create straight segments, then fills and strokes the triangle.

c.fillStyle = "red";

c.beginPath();
c.moveTo(10,10);
c.lineTo(60,10);
c.lineTo(60,60);
c.lineTo(10,10);
c.closePath();
c.fill();

c.lineWidth = 1;
c.strokeStyle = "blue";
c.stroke();

Curves

Drawing Support
1.4.52.0
fillRectyesyes
strokeRectyesyes
fill pathsyesyes
stroke pathsyesyes

You can draw curves using the bezierCurveTo command, which will draw a bezier curve connected to the previous point and a new point defined by the last two arguments. The first four arguments are for the control points of the curve.

c.fillStyle = "red";

c.beginPath();
c.moveTo(10,40.5);
c.bezierCurveTo(10,80, 60,0, 60,40.5);
c.closePath();
c.fill();

c.lineWidth = 1;
c.strokeStyle = "blue";
c.stroke();

While we won't cover them here, you can also draw arcs and quadratics using the arcTo and quadraticCurveTo functions.

NOTE: Some versions of webOS may draw 1px stroked rectangles so they come out blurry. You can work around this by using a path composed of four lineTos instead of using strokeRect.

Drawing Images

Drawing Support
1.4.52.0
drawImageyesyes
scale imageyesyes
sub imageyesyes

Images can be drawn with the drawImage function. The image must already be loaded for it to be drawn, so typically you will put your drawing code in the image's onload event handler.

var img = new Image();
img.onload = function() {
    c.drawImage(img,5,5);
}
img.src = 'smile.png';

The images you draw can come from creating a new Image() object, using existing images in the document with the document.images[] array, or using the data: URL .

The drawImage function has a few variations used to stretch and slice images. You can set the target size of the image by adding width and height arguments to the function. The following code stretches the image to 200 x 200 pixels.

var img = new Image();
img.onload = function() {
    c.drawImage(img,5,5,200,200);
}
img.src = 'smile.png';

For even more flexible drawing, you can specify a sub-rectangle within the source image, and the size and location of the target image. This lets you draw just portions of the image to the Canvas, stretched and placed however you want. This form of drawImage is often used to implement stretched tile buttons, scrolling platform video games, and fast image clipping.

var img = new Image();
img.onload = function() {
    c.drawImage(img,
        20,20,30,30, //source coords
        0,0,40,40//dest coords
        );
    c.drawImage(img,
        40,20,20,20, //source coords
        0,50,100,20//dest coords
        );
}
img.src = 'smile.png';

Fill and Stroke: Colors, transparency, gradients, and patterns

The stroke and fill colors must be specified using the CSS color syntax. You can use either color names, RGB hex values, or the rgb and rgba color functions. Additionally, the hsl and rgb functions using percentage values have been defined in the HTML5 specification, but most browsers do not yet support them.

The following code defines the color orange using several syntaxes. (The last two forms are probably not supported by your browser and will show black instead because the invalid syntax is ignored.)

c.fillStyle = "black";
c.fillStyle = "orange";
c.fillRect(0,0,30,30);

c.fillStyle = "black";
c.fillStyle = "rgb(255,165,0)";
c.fillRect(35,0,30,30);

c.fillStyle = "black";
c.fillStyle = "rgba(255,165,0,1.0)";
c.fillRect(70,0,30,30);

c.fillStyle = "black";
c.fillStyle = "#ffa500";
c.fillRect(0,35,30,30);

c.fillStyle = "black";
c.fillStyle = "hsl(100%,25%,0)";
c.fillRect(35,35,30,30);

c.fillStyle = "black";
c.fillStyle = "rgb(0,100%,0)";
c.fillRect(70,35,30,30);

Global Alpha

Color Support
1.4.52.0
color namesyesyes
rgb()yesyes
rgba()yesyes
hex valuesyesyes
hslnono
rgb%nono
globalAlphayesyes

You can define partially transparent colors using the rgba color function. This function will affect only the current fill or stroke color. You can also set the alpha used for all drawing with the globalAlpha property. The following code draws the color orange four times. The first row is orange with full and half opacity on the color. The second row draws them again, but this time with globalAlpha set to 0.5. Notice how the last swatch combines transparency on both the color and globalAlpha to produce an even lighter color.


c.globalAlpha = 1.0;
c.fillStyle = "rgba(255,165,0,1.0)";
c.fillRect(35,0,30,30);

c.fillStyle = "rgba(255,165,0,0.5)";
c.fillRect(70,0,30,30);

c.globalAlpha = 0.5;

c.fillStyle = "rgba(255,165,0,1.0)";
c.fillRect(35,0,30,30);

c.fillStyle = "rgba(255,165,0,0.5)";
c.fillRect(70,0,30,30);

Line Styles

Line Style Support
1.4.52.0
lineWidthyesyes
lineCapnono
lineJoinnono

Canvas lets you control the way lines are drawn using three properties: lineWidth, lineCap, and lineJoin. lineWidth controls the width of the line. The example below shows the same line drawn nine times with different widths.

for(i=1;i<10;i++) {
    c.fillStyle = "black";
    c.lineWidth = i;
    c.beginPath();
    c.moveTo(i*10,10);
    c.lineTo(i*10,90);
    c.stroke();
}

The lineCap property controls the way the end of the line is drawn. The three keywords butt, round, and square draw the line end with a flat end (butt), a rounded end (round), or a squared end that extends past the final point (square).

var ends = ['butt','round','square'];
for(i=0;i<3;i++) {
    c.fillStyle = "black";
    c.lineWidth = 10;
    c.lineCap = ends[i];
    c.beginPath();
    c.moveTo(20,20+20*i);
    c.lineTo(50,20+20*i+20);
    c.lineTo(80,20+20*i);
    c.stroke();
}

The lineJoin property controls the way line segments are joined together. The three keywords draw the joins with a round curve (round), a chopped off bevel (bevel), or a straight angle (miter). The maximum length of the miter can be further controlled with the miter length property.

var joins = ['round','bevel','miter'];
for(i=0;i<3;i++) {
    c.fillStyle = "black";
    c.lineWidth = 10;
    c.lineJoin = joins[i];
    c.beginPath();
    c.moveTo(20,15+20*i);
    c.lineTo(50,15+20*i+30);
    c.lineTo(80,15+20*i);
    c.stroke();
}

Linear gradients

Canvas can both fill and stroke shapes using linear gradients. A linear gradient fills an area by blending the colors provided as stops. It can have any number of stops, but must have at least two. It is called a linear gradient because it blends the colors in a straight line. The radial gradient (covered next) blends the colors in a circular shape.

To use a gradient, just create one with the createLinearGradient function, add some color stops at various positions between 0 and 1, then set it as the fill style. The arguments to createLinearGradient function give the x and y coordinates of the start and ending points of a gradient. So to fill a rectangle from top to bottom, you could set the two x values to 0 and the two y values to 0 and 100.

//diagonal gradient
var grad = c.createLinearGradient(0,0,100,100);
grad.addColorStop(0, "white");
grad.addColorStop(1, "black");
c.fillStyle = grad;
c.fillRect(0,0,100,100);

//vertical gradient
var grad = c.createLinearGradient(0,0,0,100);
grad.addColorStop(0, "white");
grad.addColorStop(1, "black");
c.fillStyle = grad;
c.fillRect(0,0,100,100);

Strokes can also be drawn using a gradient with the same syntax.

var canvas = document.getElementById('can');
var c = canvas.getContext('2d');
var grad = c.createLinearGradient(0,0,100,100);
grad.addColorStop(0, "white");
grad.addColorStop(1, "black");
c.lineWidth = 10;
c.strokeStyle = grad;
c.strokeRect(10,10,80,80);

var canvas = document.getElementById('can2');
var c = canvas.getContext('2d');
var grad = c.createLinearGradient(0,0,0,100);
grad.addColorStop(0, "white");
grad.addColorStop(1, "black");
c.lineWidth = 10;
c.strokeStyle = grad;
c.strokeRect(10,10,80,80);

Radial Gradients

Radial gradient use the same color stop syntax as linear gradients, but have a different creation function. createRadialGradient takes six arguments: x1,y1,r1 x2,y2,r2. The first three define the start circle at x1,y1 with a radius r1. The second three define the ending circle at x2,y2 and radius r2. In general the first circle should be inside the second circle.

var grad = c.createRadialGradient(45,45,10,  52,50,30);
grad.addColorStop(0, "white");
grad.addColorStop(1, "black");

c.fillStyle = grad;
c.fillRect(10,10,80,80);

Patterns

Gradient and Pattern Support
1.4.52.0
fill linear gradientnoyes
fill radial gradientnoyes
stroke linear gradientnono
stroke radial gradientnono
texture repeatnono
texture repeat-xnono
texture repeat-ynono
texture repeat-nonono

You can also fill or stroke using repeating image patterns. As with drawImage, the image should be loaded before drawing. Create an image pattern with the createPattern function, passing it the image and a repeat keyword. This can be either repeat, repeat-y, repeat-x, or no-repeat; which have the same meaning as in CSS.

var img = new Image();
img.onload = function() {
    
    var canvas = document.getElementById('can');
    var c = canvas.getContext('2d');
    var pat = c.createPattern(img,'repeat');    
    c.fillStyle = pat;
    c.fillRect(10,10,80,80);    
    
    var canvas2 = document.getElementById('can2');
    var c2 = canvas2.getContext('2d');
    var pat2 = c2.createPattern(img,'repeat-y');    
    c2.fillStyle = pat2;
    c2.fillRect(10,10,80,80);    
    
}
img.src = 'smile.png';

Transforms, Compositing, and Clipping

Transforms

Canvas lets you apply arbitrary geometric transforms to any graphics you draw. The example below shows how the same square can be drawn in different colors with the transforms translate, rotate, and scale.

c.strokeStyle = "red";
c.lineWidth = 2;
c.strokeRect(0,0,30,30);

c.strokeStyle = "green";
c.translate(60,20);
c.strokeRect(0,0,30,30);

c.strokeStyle = "blue";
c.rotate(Math.PI/4);
c.strokeRect(0,0,30,30);

c.strokeStyle = "purple";
c.scale(2,1.5);
c.strokeRect(0,0,30,30);

Saving and Restoring the Graphics State

Transforms and State Saving
1.4.52.0
scaleyesyes
translateyesyes
rotateyesyes
shearnono
state savingyesyes

The current transform is part of the graphics state, which is the list of properties that defines the current way things are drawn. This state includes not only the current transform but also the current fillColor, strokeColor, lineWidth, and anything else that affects drawing. You can save and restore the graphics state using context.save() and context.restore().

c.strokeStyle = "red";
c.lineWidth = 2;
c.strokeRect(0,0,30,30);

//save the current state
c.save();
c.strokeStyle = "green";
c.lineWidth = 6;
//translate
c.translate(60,20);
c.strokeRect(0,0,30,30);
//go back to the previous state
c.restore();

c.strokeRect(0,30,30,30);

The graphics state is actually stored in a stack structure, so you can save and restore multiple times, just as you can push and pop objects on a stack.

Compositing

c.globalCompositeOperation = "source-over"; // the default
c.fillStyle = "red";
c.fillRect(30,30,40,40);

c.globalCompositeOperation = "destination-out"; // cut away the existing source
c.fillStyle = "blue";
c.fillRect(15,55,30,30);

c.globalCompositeOperation = "lighter"; // make it lighter where source & target overlap
c.fillStyle = "blue";
c.fillRect(55,55,30,30);

Clipping

Compositing and Clipping
1.4.52.0
source-atopnono
source-innono
source-outnono
source-overyesyes
destination-atopnono
destination-innono
destination-outnono
destination-overnono
lighternono
copynono
xornono
clippingyesyes

Normally, when you draw something to the canvas it is completely drawn. Sometimes, you would like to draw just a portion of the thing you are drawing. For example, you might want to draw a complex shape but only see the part inside of a circular area. This concept is called clipping. When you set a clip shape, any subsequent drawing will be clipped by the clip shape. Only pixels inside the clip shape will actually be drawn on screen.

You set the clip shape by defining a path, just as you would do when drawing a path. However, instead of then calling draw() you will call clip(). Any further drawing will be clipped by the clip shape. To get rid of the clip you should save the state before setting a clip, then restore the graphics state afterwards.

c.save();
c.beginPath();
c.arc(50,50,40,0, Math.PI*2,true);  //a circle
c.clip();

//draw a red rectangle clipped by the circle
c.fillStyle = "red";
c.fillRect(0,0,50,50);

c.restore();
//draw a blue rectangle not clipped
c.fillStyle = "blue";
c.fillRect(50,0,50,50);

Pixel Manipulation

Pixel Manipulation
1.4.52.0
getImageDatanoyes
putImageDatanoyes
createImageDatanoyes
toDataURLnono

No matter what you draw to the canvas, at the end of the day it's just a lot of pixels. You can get manipulate these pixels directly using the getImageData and putImageData functions. getImageData returns an ImageData structure containing the width and height of the canvas data as well as the actual pixels stored in a long array of RGBA values called data. Once you have the pixel data, you can read and set pixels directly, then put the data back when you are done.

The code below draws a red rectangle, grabs all of the pixels, sets the green part of each pixel to half (128), sets the blue part of each pixel to 0, then puts the data back into the canvas.

Note that each pixel is represented by four values for the red, blue, green, and alpha values. Because they are separate values, you can manipulate the color components of each pixel without having to use bit-shifting.

var canvas = document.getElementById('pixel01');
var c = canvas.getContext('2d');
//draw rect
c.fillStyle = "red";
c.fillRect(0,0,50,50);

//get the data
var data = c.getImageData(0, 0, canvas.width, canvas.height);
//set the green part of every pixel to 128
for(n=0; n<data.width*data.height; n++) {
    var pi = n*4; //pixel index 
    //data.data[pi]   don't touch the red
    data.data[pi+1] = 128;  // set green to half
    data.data[pi+2] = 0;  //set blue to zero
    data.data[pi+3] = 255; //set alpha to max
}

//set the data back
c.putImageData(data,0,0);

You can grab the image data or a sub-rectangle of it using getImageData(). You can also create new imageData from scratch with the createImageData. The example below creates a new 50x50 pixel imageData object, fills it with random red/blue pixels, then puts the data into the center of the Canvas.

<canvas id="pixel01" width="100" height="100"></canvas>
<script language="Javascript">
var canvas = document.getElementById('pixel01');
var c = canvas.getContext('2d');

var data = c.createImageData(50,50);
for(n=0; n<data.width*data.height;n++) {
    var index = n*4;
    //fill with random red/blue color
    data.data[index] = Math.random()*255;
    data.data[index+1] = 0; //force green to 0
    data.data[index+2] = Math.random()*255;
    data.data[index+3] = 255; //force alpha to 100%
}
//set the data back
c.putImageData(data,25,25);

</script>

Canvas can also return the image data as a data: URI containing a base64 encoded PNG. Essentially, it turns the Canvas pixels into a giant string that can then be saved, shown on screen, or sent to webservices.


<canvas id="pixel03" width="100" height="100"></canvas>
<script language="Javascript">
var canvas = document.getElementById('pixel03');
var c = canvas.getContext('2d');
c.fillStyle = 'red';
c.fillRect(0,0,50,50);

var str = canvas.toDataURL();

alert('str = ' + str.substring(0,30));
</script>

NOTE: webOS does not yet support the toDataURL function. However, as a workaround, you can use this open source PNG library by Robert Eisele to generate a base64 encoded string of a PNG of the Canvas contents.

For more details on direct pixel manipulation, see the Mozilla Developer Network tutorial on pixel manipulation with Canvas.

References