204
Safari HTML5 Canvas Guide

HTML Canvas Guide

Embed Size (px)

Citation preview

Page 1: HTML Canvas Guide

Safari HTML5 CanvasGuide

Page 2: HTML Canvas Guide

Contents

About Canvas 10At a Glance 11

You Can Add a Canvas Element in a Few Lines of Code 11There Are Methods for Drawing Rectangles, Lines, Curves, Arcs, and Complex Shapes 11It’s Easy to Include JPEGs, GIFs, PNGs, and SVGs 12You Can Also Render Text On Canvas 12Canvas is Great for Infographics 13Canvas Can Create Fast, Lightweight Animations 13You Can Manipulate Pixels Directly for Image Processing 13Make Games That Play on the Desktop and iOS-Based Devices 13The Web Inspector Provides Built-In JavaScript Debugging 14Export to Canvas is Possible from Illustrator or Flash 14

Prerequisites 14See Also 14

Setting Up the Canvas 16Start by Adding a <canvas> Tag 16Specify the Fallback Behavior 16Create a Drawing Context 16Save and Restore the Context 17

Drawing Lines and Shapes 19Set the Stroke and Fill Styles 19Drawing Rectangles 20Paths and Subpaths 20Drawing Lines 21Setting Caps and Joins 22Drawing Bezier Curves 23Drawing Arcs and Circles 23Drawing Complex Shapes 24Creating Masks 27

Gradients and Patterns 29Gradients 29

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

2

Page 3: HTML Canvas Guide

Linear Gradients 29Radial Gradients 33Animating Gradients 35

Patterns 37

Using Predrawn Images 41Image Sources 41Preparing to Draw an Image 42Drawing an Image Normally 43Drawing an Image with a Given Height and Width 43Drawing an Image with Region Mapping 44

Adding Text 48Text Settings 48

Font Settings 49Text Direction 49Text Alignment 49Text Baseline 50Bounding Box Width 51

Drawing Text 52Animating Text 53

Adding Shadows 57Shadow Basics 57Transparency and Blur 58Shadows and Animation 58

Translation, Rotation, and Scaling 60Translation 61Scaling 62Rotation 63Rotation Around the Center 65Combining Asymmetric Scaling and Rotation 67

Matrix Transforms 69Setting the Transformation Matrix 69

Example: Setting the Matrix for Reflection 70The Identity Matrix 72

Transforming the Transformation Matrix 72

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

3

Contents

Page 4: HTML Canvas Guide

Advanced Compositing 73Global Alpha 73Compositing Modes 74

Creating Charts and Graphs 76Scaling Your Data 76

Scaling Using the Transformation Matrix 77Scaling Using a Function 78

Data Plots 79Bar Graphs 82Pie Charts 86Interactive Data Visualization and Animation 91

Animating the Canvas 99The Animation Sequence 99Animation Timing 100A Simple Animation 102Adding a Gradient 106Intermittent Animation 107Panning Background 110

Simple Panning Background 111Layered Panning Background 113

Modifying the Canvas with CSS 116Assigning a Border and Background 116A Pop-Up Canvas—Animating Position and Opacity 117Free Range Canvas 121

Adding Mouse and Touch Controls to Canvas 126Using Standard Inputs with Canvas 126Using Standard Inputs on Canvas 129Responding to Mouse and Touch Events on Canvas 132

Tracking a Single Touch 132Tracking Multiple Touches and Testing for Hits 135

Creating Custom Canvas Controls 139Adding a Custom Button 139Adding a Slider 143

Adding Sound to Canvas Animations 149Adding a Soundtrack 150

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

4

Contents

Page 5: HTML Canvas Guide

Looping Audio 153Adding a Voice-Over 158Adding Sound Effects 161

Pixel Manipulation 166Getting Pixel Data From the Canvas 166Creating Buffer Objects 166Modifying Pixel Data 167Example: Reading and Modifying Pixel Data 168

Creating Games 171Collision Detection 171Space Arcade Game 171Loony Lander Game 177

Putting Video on Canvas 188Canvas Over Video 189Tiled Video 192Image Processing 197

Document Revision History 203

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

5

Contents

Page 6: HTML Canvas Guide

Figures and Listings

Drawing Lines and Shapes 19Figure 2-1 Default cap and join 22Figure 2-2 The winding rule in action 25Figure 2-3 Clipping mask 27Listing 2-1 Exercising the winding rule 26Listing 2-2 Making a mask 27

Gradients and Patterns 29Figure 3-1 Linear gradient 30Figure 3-2 Linear gradients 31Figure 3-3 Rainbow gradient 32Figure 3-4 Radial gradient 33Figure 3-5 Ball with gradient overlay 34Figure 3-6 Animated gradient 35Figure 3-7 Patterns 38Listing 3-1 Creating a linear gradient 30Listing 3-2 Creating a radial gradient 33Listing 3-3 Animating a gradient 35Listing 3-4 Creating patterns 38

Using Predrawn Images 41Figure 4-1 Region mapping 44Figure 4-2 Exploding soccer ball 45Listing 4-1 Drawing an image 42Listing 4-2 Exploding an image 45

Adding Text 48Figure 5-1 Text alignment 50Figure 5-2 Text baselines 51Figure 5-3 Hello world 52Figure 5-4 Animated text 54Listing 5-1 Drawing “Hello, world.” 52Listing 5-2 Animating text 54

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

6

Page 7: HTML Canvas Guide

Adding Shadows 57Figure 6-1 Shadows 57Figure 6-2 Shadow hardness 58Figure 6-3 Shadow rotation 59

Translation, Rotation, and Scaling 60Figure 7-1 Translating a path 62Figure 7-2 Nonintuitive interaction 68Listing 7-1 Drawing a path at a new location 61Listing 7-2 Rotating an image and text 64Listing 7-3 Defining a shape literally 66Listing 7-4 Defining a shape relative to its center point 66

Matrix Transforms 69Figure 8-1 Matrix parameters and positions 69Figure 8-2 Reflected text 70Figure 8-3 Reflection with scale and gradient 71Figure 8-4 The identity matrix 72Listing 8-1 Reflecting text 70Listing 8-2 Adding a scalar and gradient 71

Creating Charts and Graphs 76Figure 10-1 Sample data plot 79Figure 10-2 Sample bar graph 83Figure 10-3 Sample pie chart 87Figure 10-4 Frequency addition 91Listing 10-1 Scaling by matrix 77Listing 10-2 Creating a scaling function 78Listing 10-3 Data plot template 80Listing 10-4 Bar graph template 83Listing 10-5 Drawing a pie chart 86Listing 10-6 Pie chart generator 87Listing 10-7 Performing interactive frequency addition 91

Animating the Canvas 99Figure 11-1 Animation profile 101Figure 11-2 Animated soccer ball 102Figure 11-3 Animation with gradient fill 106Figure 11-4 Flapping butterfly 107Figure 11-5 Panning background 111

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

7

Figures and Listings

Page 8: HTML Canvas Guide

Figure 11-6 Layered background 113Listing 11-1 Creating a simple animation 103Listing 11-2 Creating an intermittent animation 108Listing 11-3 Adding a panning background 111Listing 11-4 Adding a layered panning background 113

Modifying the Canvas with CSS 116Figure 12-1 CSS bulletin board 116Figure 12-2 Animating a pop-up canvas 118Figure 12-3 Butterfly 122Listing 12-1 Adding a CSS background and border 116Listing 12-2 Making the canvas a pop-up 118Listing 12-3 Making a free-roaming canvas 122

Adding Mouse and Touch Controls to Canvas 126Figure 13-1 Big buttons 127Figure 13-2 Controls on canvas 129Figure 13-3 Tracking events on canvas 132Figure 13-4 Bubbles 136Figure 13-5 Clicking a custom button 140Figure 13-6 Slider controlling image size 144Listing 13-1 Make big buttons 127Listing 13-2 Putting controls on canvas 130Listing 13-3 Track mouse and touch events 133Listing 13-4 Track multiple touches 136Listing 13-5 Creating a custom button on canvas 140Listing 13-6 Adding a slider using CSS 144

Adding Sound to Canvas Animations 149Figure 14-1 Soundtrack 150Figure 14-2 Looping audio 154Figure 14-3 Voice-over 158Figure 14-4 Animation with sound effects 162Listing 14-1 Adding a simple soundtrack 150Listing 14-2 Adding looping audio 154Listing 14-3 Displaying slides with voice-overs 159Listing 14-4 Creating animation with sound effects 162

Pixel Manipulation 166Figure 15-1 Green clear and restore 168

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

8

Figures and Listings

Page 9: HTML Canvas Guide

Listing 15-1 Manipulating pixels 168

Creating Games 171Figure 16-1 Space arcade game 172Figure 16-2 Lander game 177Listing 16-1 Space arcade game 172Listing 16-2 Lander game 177

Putting Video on Canvas 188Figure 17-1 Video under canvas 189Figure 17-2 Rotating video tiles 192Figure 17-3 Image processing 197Listing 17-1 Displaying video behind the canvas 189Listing 17-2 Breaking video into tiles 193Listing 17-3 Realtime image processing 197

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

9

Figures and Listings

Page 10: HTML Canvas Guide

If your website includes infographics, animation, image processing, interactive graphics, or games, you shouldlearn about the HTML5 canvas element, an immediate drawing surface in your webpage where you can createruntime-generated graphics, including animations, games, and video, all without using a plug-in.

You interact with canvas using JavaScript. HTML5 adds a set of powerful graphics methods to the JavaScriptAPI. There are JavaScript methods for drawing text, lines, curved paths, and shapes, as well as for renderingimages such as JPEGs, GIFs, PNGs, SVGs, and even video. There are also JavaScript methods for manipulatingcanvas pixels directly, supporting real-time image processing.

The canvas element offers a quick and elegant solution for creating interactive animations and 2D games.Because canvas animations are written in HTML, CSS, and JavaScript, you can modify them quickly using a texteditor or text output from a script. Since no plug-in is needed, games and animations written using canvasrun on iOS-based devices as well as on desktop computers. Because the graphics are created at runtime onthe user’s system, they’re always up-to-date and generate no server load or round-trip delay.

The canvas specification provides for simple fallback, so you can start using canvas today while keeping yourwebsite compatible with older browsers. Canvas is supported on the desktop (Mac OS X and Windows) inSafari 2.0 and later and in all versions of Safari on iOS, as well as in most other current browsers.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

10

About Canvas

Page 11: HTML Canvas Guide

Important This document includes over 40 complete HTML examples. You can download a .zip file withall of the HTML examples, including any required media assets. Click the Companion File link at the top ofthe page in the HTML version of this document to download the .zip file.

At a GlanceHere, in a nutshell, are the main features of canvas, along with the methods and properties you use to implementthem. Follow the links at the bottom of each subsection for details and examples on particular features.

You Can Add a Canvas Element in a Few Lines of CodeIn your HTML, include a line that defines the canvas element, giving it a height and width. Be sure to includea closing tag. It’s a good idea to assign it an ID as well. Put any fallback behavior for older browsers betweenthe opening and closing <canvas> tags.

<canvas id="can" height="300" width="400">

<img src="fallback.jpg">

</canvas>

Using JavaScript, get the canvas element into an object and get a "2d" drawing context.

var can = document.getElementById("can");

var ctx = can.getContext("2d");

That’s all the setup required. Now you’re ready to start drawing. Because the canvas element is HTML, youcan use CSS to modify it—give it a border or a background, round the corners, move it around on the screen,hide it offscreen, and so on.

Relevant Chapters “Setting Up the Canvas” (page 16) and “Modifying the Canvas with CSS” (page116).

There Are Methods for Drawing Rectangles, Lines, Curves, Arcs, and ComplexShapesTo draw a rectangle, specify the x and y coordinates and the height and width of the rectangle. ThestrokeRect(x,y, width,height) method draws the outline of a rectangle. The fillRect(x,y,width,height) method draws a filled rectangle.

About CanvasAt a Glance

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

11

Page 12: HTML Canvas Guide

You draw shapes other than rectangles by creating a path, adding line segments, curves, or arcs, and closingthe path. Begin a path using beginPath(). Set the starting point, or start a discontinuous subpath, by callingthe moveTo(x,y) method. The closePath() method draws a line from the current endpoint to the startingpoint of the path, creating a closed shape.

The path is not actually drawn until you call stroke() or fill(). The stroke or fill style specifies the colorthe element is drawn in. Colors are specified using the same naming conventions asCSS—ctx.strokeStyle="black", for example, or ctx.fillStyle="rgba(128,128,128,0.5)". Youcan also set the line width for strokes. For example, ctx.lineWidth=2.

You can add a shadow to any shape, or make any shape into a mask by designating it as the clipping regionfor drawing operations.

Canvas supports matrix transforms—anything you draw can be translated, rotated, scaled, and more.

Relevant Chapters “Drawing Rectangles” (page 20), “Drawing Lines and Shapes” (page 19),“Gradients and Patterns” (page 29), “Adding Shadows” (page 57), “Translation, Rotation, andScaling” (page 60), and “Matrix Transforms” (page 69).

It’s Easy to Include JPEGs, GIFs, PNGs, and SVGsThe drawing operations given so far are perfect for drawing graphs and charts, but you probably wouldn’twant to draw fine art, or even a smiley face, by describing the geometric shapes that make it up. Fortunately,you can include predrawn images on the canvas using an image source, such as an img, video, or anothercanvas element. Include the image source in your HTML and get it into a JavaScript object using a methodsuch as getElementById.

Relevant Chapters “Using Predrawn Images” (page 41) and “Adding Shadows” (page 57).

You Can Also Render Text On CanvasThe canvas element supports basic text rendering on a line-by-line basis. Just enter a line of text and x andy coordinates for the text box using the fillText("text",x,y) or strokeText("text",x,y) method.You can specify a number of text settings, such as the font family, size, and weight, and the text alignmentand baseline.

About CanvasAt a Glance

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

12

Page 13: HTML Canvas Guide

Relevant chapter “Adding Text” (page 48)

Canvas is Great for InfographicsIt’s easy to plot data, create bar graphs, pie charts and infographics using canvas. To plot data, just chooseyour colors and select an appropriate scale to fit your data. Bar charts are also straightforward—pick yourcolors, choose an appropriate scale, and make calls to fillRect(x,y, width,height). Pie charts are onlyslightly more complicated.

Relevant chapter “Creating Charts and Graphs” (page 76)

Canvas Can Create Fast, Lightweight AnimationsAnimation involves repeatedly clearing the canvas and drawing the visual elements, from backmost to frontmost.Set the animation to repeat using the setInterval("function()", ms) method for endless animations,or setTimeout("function()", ms) for animation that repeats conditionally. For the smoothest animation,break your animation into a model and a view, updating position, rotation, and scale of the elements in themodel, then drawing everything at the precalculated values.

Relevant chapter “Animating the Canvas” (page 99)

You Can Manipulate Pixels Directly for Image ProcessingYou have direct access to the canvas bitmap as an array of RGBa pixels. You can use this information to analyzethe canvas’s content, detect collisions, apply digital filters, and do image processing in real time.

Relevant chapter “Pixel Manipulation” (page 166).

Make Games That Play on the Desktop and iOS-Based DevicesYou can add sound to canvas-based media using HTML5 audio. You can also add controls that respond tomouse or touch input. You can detect collisions using isPointInPath(x,y). Because your game logic is inJavaScript, it has ready access to client-side data storage, which simplifies tracking multiple players, high scores,dungeon maps, and so on. Because canvas games work natively in the browser, web-based games based oncanvas work equally well on the desktop and on mobile devices, such as iPad, iPhone, and iPod touch.

About CanvasAt a Glance

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

13

Page 14: HTML Canvas Guide

Relevant chapters “Animating the Canvas” (page 99), “Adding Sound to Canvas Animations” (page149), “Adding Mouse and Touch Controls to Canvas” (page 126), and “Creating Games” (page 171).

The Web Inspector Provides Built-In JavaScript DebuggingSafari has built-in tools for debugging HTML and JavaScript, and for optimizing JavaScript timing. These toolsare invaluable for creating interactive canvas displays and games. To enable the developer tools, click “Showdevelop menu in menu bar” in Safari preferences > Advanced. To open the Web Inspector and begin debugging,load your webpage and choose Show Web Inspector from the Develop menu. For more information, see SafariDeveloper Tools Guide .

Export to Canvas is Possible from Illustrator or FlashIf your expertise has been honed using authoring tools such as Flash or Illustrator, you can continue to authorin them while creating canvas content. Illustrator can export vector graphics in SVG format, which is ideal forcreating resolution-independent images for canvas. You can draw images in Flash and export them in PNG orSVG format. Porting from ActionScript to JavaScript is relatively straightforward, as the two languages are quitesimilar. Current versions of Flash support export to HTML for complete Flash documents, ActionScript and all,using a provided JavaScript library.

PrerequisitesYou should already be familiar with HTML and JavaScript. Familiarity with CSS is helpful.

See Also ● HTMLCanvasElement Class Reference—Summary of the methods and properties of the canvas element.

● CanvasRenderingContext2D Class Reference—Summary of the methods and properties used for drawingon canvas.

● Safari HTML5 Audio and Video Guide—Guide to use of audio and video elements in Safari.

● TicTacToe with HTML5 Offline Storage—Example of a game that uses HTML5 client-side storage.

● Safari Developer Tools Guide—Complete guide to debugging JavaScript, HTML, and CSS using Safari’sbuilt-in Web Inspector.

● Safari CSS Visual Effects Guide—Guide to using CSS for animated transformations and effects.

● Safari CSS Reference—Supported CSS properties in Safari.

About CanvasPrerequisites

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

14

Page 15: HTML Canvas Guide

● iOS Human Interface Guidelines—Includes guidance for creating web-based apps for iOS-based devices.

About CanvasSee Also

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

15

Page 16: HTML Canvas Guide

To set up a canvas for drawing, you need to add a <canvas> tag to your HTML and assign a 2D drawing contextto it. All your drawing operations are performed on the context.

Start by Adding a <canvas> TagIn your HTML, include a line that defines the canvas element, giving it a width and height. Be sure to includea closing tag.

<canvas width="400" height="300">

</canvas>

If you don’t specify a width or height, the default width of 300 pixels and the default height of 150 pixels areused. The canvas is initially empty and transparent.

Specify the Fallback BehaviorPut any fallback behavior for older browsers between the opening and closing <canvas> tags.

<canvas height="300" width="400">

<img src="fallback.jpg">

</canvas>

HTML5-savvy browsers ignore everything between the opening and closing <canvas> tags. Browsers thatdon’t support the canvas element ignore the <canvas> tags and display the fallback content.

Create a Drawing ContextUsing JavaScript, get the canvas element into an object and get a "2d" drawing context. You perform alldrawing operations on the context. Currently, only a "2d" context is supported. The canvas specification isdesigned to support 3D drawing contexts, such as WebGL, in the future.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

16

Setting Up the Canvas

Page 17: HTML Canvas Guide

<html>

<head>

<title>Simple Canvas</title>

<script type="text/javascript">

var can;

var ctx;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="300" width="400">

</canvas>

</body>

</html>

Save and Restore the ContextThe context is, among other things, where you store settings such as line color, fill color, line thickness, rotation,and scaling.

To draw different elements in different colors or at different rotations, for example, you need to change thecontext settings.

Instead of setting and resetting large numbers of drawing parameters, it’s easier to save the current contextwith all its settings, then restore it as needed.

When you save a context, the settings are pushed onto a stack. When you restore a context, the settings arepopped off the stack. Saving and restoring contexts is therefore a very fast operation, and is nestable.

You save the current context settings by calling the save() method on the context. Restore the previoussettings by calling restore().

The following snippet saves the context, changes the rotation, calls a drawing operation, then restores thecontext.

Setting Up the CanvasSave and Restore the Context

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

17

Page 18: HTML Canvas Guide

ctx.save();

ctx.rotate(3.14);

drawSomething();

ctx.restore();

Setting Up the CanvasSave and Restore the Context

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

18

Page 19: HTML Canvas Guide

The 2D drawing context has methods for drawing rectangles, lines, curves, arcs, and circles.

● Lines, curves, and arcs can be connected at their endpoints to form paths. Paths can be closed to formcomplex shapes.

● Shapes can be stroked (drawn in outline) or filled.

● You can set the thickness of lines and strokes.

● Strokes and fills can be set to a color, pattern, or gradient.

Set the Stroke and Fill StylesTo actually draw a line or shape, you need to specify a fill style or a stroke style. The stroke or fill style specifiesthe color the element is drawn in. The following snippet specifies that drawing should be stroked in black orfilled in gray at 50% opacity.

ctx.strokeStyle="black";

ctx.fillStyle="rgba(128,128,128,0.5)";

The thickness of the stroke is determined by the context’s lineWidth property. The default line width is 1pixel. To set the line width to 2 pixels, for example, set ctx.lineWidth = 2.

Colors can be specified in any of the usual CSS ways, such as "white", "rgb(255,255,255)", or "#ffffff".

Colors can also be specified using RGBa, which specifies the 8-bit RGB values and an alpha value between 0(transparent) and 1 (completely opaque), such as "rgba(255,255,255,1.0)", for example.

A gradient or pattern can be used instead of a color. For more information, see “Gradients and Patterns” (page29).

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

19

Drawing Lines and Shapes

Page 20: HTML Canvas Guide

Drawing RectanglesOnce you’ve set a context and a stroke or fill style, you can begin drawing shapes on the canvas. To draw arectangle, specify the x and y coordinates (with 0,0 in the upper-left corner of the canvas) and the height andwidth of the rectangle.

There are three rectangle methods:

Use clearRect() to clear a rectangle (erase that area to transparent black).

Use strokeRect() to stroke a rectangle (draw the outline).

Use fillRect() to fill a rectangle in the current color, gradient, or pattern.

The following snippet draws a 50 x 50 blue rectangle at the upper-left corner of the canvas.

function drawBlueRect() {

ctx.fillStyle="blue";

ctx.fillRect(0,0,50,50);

}

Note If you’re unfamiliar with RGBa colors, the notion of “transparent black” may seem a little odd.It means that the RGB portion of the color is set to black, with the alpha channel set to transparent.When the alpha channel is set to zero (transparent), the RGB color value is immaterial, but it has tobe set to something.

Paths and SubpathsYou draw shapes other than rectangles by creating a path, adding elements to it, then calling fill() orstroke().

You begin a path by calling beginPath(). Set the starting point by calling moveTo(x,y). Then add elementssuch as lines, bezier curves, arcs, and so on. Each element adds a new endpoint to the current path. A pathends when you call beginPath() again. You do not necessarily need to end a path—once you start a path,it becomes the current path, and can be stroked or filled at any time.

A subpath is a continuous part of the path. Initially, the current path and the current subpath are the same.You can use moveTo(x,y) within a path to create a new, discontinuous subpath.

Drawing Lines and ShapesDrawing Rectangles

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

20

Page 21: HTML Canvas Guide

A subpath ends when you call moveTo(x,y) or closePath(). Note that closePath() does not necessarilyclose the current path, only the current subpath (though they may be the same). The closePath() methoddraws a line from the current endpoint to the starting point of the current subpath.

Calling fill() or stroke() renders the current path, including all subpaths.

You cannot create multiple paths and fill or stroke them all with a single call to fill() or stroke()—eachpath you want drawn must be followed by a call to stroke(), fill(), or both, before your next call tobeginPath().

Drawing LinesYou draw lines by specifying the endpoint of a line segment. Add an endpoint by using the lineTo()method.The line is drawn from the current endpoint on the path to the new endpoint. You can string endpoints togetherin a connect-the-dots fashion. Alternatively, set a new starting point for a line segment, without drawing a lineto it, by using the moveTo() method.

The lines are not actually drawn until you call the stroke() or fill() method.

If you call the fill() method, the line segments are treated as the outline of a shape, which is filled in thecurrent fill style.

You can adjust the line thickness by setting the context’s lineWidth property. For example, the followingsnippet sets the line width to 3 pixels.

ctx.lineWidth=3;

The following snippet draws a horizontal and a vertical line the height and width of the canvas, crossing in themiddle of the canvas.

function init() {

can = document.getElementsByTagName("canvas")[0];

ctx = can.getContext("2d");

}

function drawLines() {

ctx.strokeStyle="black";

ctx.beginPath();

ctx.moveTo(0,can.height / 2);

ctx.lineTo(can.width,can.height / 2);

ctx.moveTo(can.width / 2, 0);

Drawing Lines and ShapesDrawing Lines

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

21

Page 22: HTML Canvas Guide

ctx.lineTo(can.width / 2, can.height);

ctx.stroke();

}

Setting Caps and JoinsYou can choose how lines are capped at the ends and how they are joined where they meet.

Set the end cap by setting the context’s lineCap property to one of the three values in the following list.

● "butt"—Default. Line ends with no cap.

● "round"—Line ends with a round cap.

● "square"—Line ends with a square cap half a line-width long.

Example: ctx.lineCap = "round";

Set the join style by setting the context’s lineJoin property to one of the three values in the following list.

● "bevel"—Default. Lines join with a beveled corner.

● "round"—Lines join with a rounded corner.

● "miter"—Lines join with a smoothly mitered corner.

Example: ctx.lineJoin = "round"

The cap and join styles are illustrated in Figure 2-1.

Figure 2-1 Default cap and join

Drawing Lines and ShapesSetting Caps and Joins

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

22

Page 23: HTML Canvas Guide

Drawing Bezier CurvesYou draw bezier curves in the same connect-the-dots manner as you draw lines, but instead of using thelineTo() method, you use either the bezierCurveTo() method, which connects the endpoints with acubic bezier curve using a specified pair of control points, or the quadraticCurveTo() method, whichconnects the endpoints with a quadratic bezier curve using a single specified control point.

The following snippet draws two bezier curves, one cubic and one quadratic, from the upper left corner of thecanvas to the lower right corner of the canvas.

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

var wide = can.width;

var high = can.height;

}

function drawCurves() {

ctx.strokeStyle="black";

var ctrlX=25;

var ctrlY=150;

var ctrlXa=50;

var ctrlYa=300;

ctx.beginPath();

ctx.moveTo(0,0);

ctx.quadraticCurveTo(ctrlX, ctrlY, wide, high);

ctx.stroke();

ctx.beginPath();

ctx.moveTo(0,0);

ctx.bezierCurveTo(ctrlX, ctrlY, ctrlXa, ctrlYa, wide, high);

ctx.stroke();

}

Drawing Arcs and CirclesAn arc is a section of a circle. To draw a circle, draw an arc with a starting angle of 0 and ending angle of 2 xPi.

Drawing Lines and ShapesDrawing Bezier Curves

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

23

Page 24: HTML Canvas Guide

Draw a freestanding arc by calling arc(x,y,radius,startAngle,endAngle). The x and y parametersspecify the center point of the circle the arc is a section of. The radius parameter is the radius of the circlethe arc is part of. The startAngle and endAngle parameters are in radians, clockwise, with 0 correspondingto the 3:00 o’clock position on the right of the circle.

Note The arc() method draws an arc clockwise, unless you supply the optional anticlockwiseparameter as a sixth argument. Safari does not require this parameter, but some browsers do. Toensure cross-browser compatibility, add a false parameter at the end of the arc() methodparameter list:

arc(x,y,radius,startAngle,endAngle, false)

Add an arc to the current endpoint of the path by calling arcTo(x,y,radius). The arcTo()method connectsthe current endpoint with the specified endpoint using an arc. The height of the arc is determined by theradius—the larger the radius, the shallower the arc.

The following snippet draws a circle of radius 50 at the center of the canvas.

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

}

function drawCircle() {

ctx.strokeStyle="black";

ctx.arc(can.width / 2,can.height / 2,50,0,Math.PI * 2);

ctx.stroke();

}

Drawing Complex ShapesTo quickly review—you draw shapes other than rectangles by creating a path, adding line segments, curves,or arcs, and optionally closing the path. You can either stroke or fill a path. A path can contain discontinuoussubpaths—lines or shapes that aren’t connected. When filling a path, each of these subpaths is filledindependently. If you create a path but don’t close it, the stroke() method draws only the specified pathsegments, but the fill() method fills a solid shape as if you had closed the path.

Drawing Lines and ShapesDrawing Complex Shapes

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

24

Page 25: HTML Canvas Guide

You can create complex shapes that contain non-filled areas. For example, you can draw a doughnut by drawingan outer circle clockwise and an inner circle counterclockwise. The area between the circles is filled, but thearea inside the smaller circle is left unfilled.

For complex shapes, where a point is surrounded by more than one set of path edges, the non-zero windingnumber rule is used to determine if a point is within the path and should be filled. If a path overlaps itself goingin different directions, some areas may not be filled, even though you might expect them to be inside theshape. For instance, if a path winds around a region once going clockwise and once going counterclockwise,the region is not filled.

The non-zero winding number rule To determine whether the fill() method fills a given pointP, perform the following test.1. Initialize a winding number to 0.

2. Draw a line from point P to the outside of the path (without passing through any vertexes).

3. Add 1 to the winding number for every path segment the line passes through where the pathis going clockwise.

4. Subtract 1 from the winding number for every path segment the line passes through where thepath is going counterclockwise.

5. If the winding number is nonzero, the point is filled. If the winding number is 0, the point is notfilled.

As another example, consider a path consisting of two overlapping, discontinuous polygons. If both polygonsare composed entirely of line segments going clockwise, or all going counterclockwise, both rectangles arefilled completely. If the two polygons are composed of line segments going in opposite directions, the areawhere the polygons overlap is not filled.

The example snippet in Listing 2-1 draws two filled paths. Each path consists of a pair of overlapping squares.In one path, the squares are both drawn counterclockwise; in the other path, one square is drawn clockwiseand the other counterclockwise. The result is illustrated in Figure 2-2.

Figure 2-2 The winding rule in action

Drawing Lines and ShapesDrawing Complex Shapes

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

25

Page 26: HTML Canvas Guide

Listing 2-1 Exercising the winding rule

function drawPath() {

// two counterclockwise squares

ctx.beginPath();

ctx.moveTo(10,10);

ctx.lineTo(10,100);

ctx.lineTo(100,100);

ctx.lineTo(100,10);

ctx.lineTo(10,10);

ctx.moveTo(20,20);

ctx.lineTo(20,120);

ctx.lineTo(120,120);

ctx.lineTo(120,20);

ctx.lineTo(20,20);

ctx.fill();

// two squares, one clockwise, one counterclockwise

ctx.beginPath();

ctx.moveTo(210,10);

ctx.lineTo(210,100);

ctx.lineTo(300,100);

ctx.lineTo(300,10);

ctx.lineTo(210,10);

ctx.moveTo(320,20);

ctx.lineTo(320,120);

ctx.lineTo(220,120);

ctx.lineTo(220,20);

ctx.lineTo(320,20);

ctx.fill();

}

Drawing Lines and ShapesDrawing Complex Shapes

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

26

Page 27: HTML Canvas Guide

Creating MasksYou can make any shape into a mask by calling the context’s clip()method after you define the path. Callingclip() makes the current path the clipping region for the canvas. Any shapes, lines, or images drawnsubsequently are clipped if they fall outside the borders of the path.

The example in Listing 2-2 creates a triangle shape, then calls clip() to make the shape a clipping mask. Theexample then fills the canvas with a blue rectangle and draws some text. The rectangle and text are clippedwhen they fall outside the boundaries of the triangle, as shown in Figure 2-3. Note that the black outline aroundthe canvas is drawn prior to the mask, and so is unaffected.

Figure 2-3 Clipping mask

Listing 2-2 Making a mask

<html>

<head>

<title>Triangular Mask</title>

<script type="text/javascript">

var can;

var ctx;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

ctx.strokeStyle = "black";

ctx.strokeRect(1,1,can.width-2,can.height-2);

drawMask();

}

function drawMask() {

Drawing Lines and ShapesCreating Masks

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

27

Page 28: HTML Canvas Guide

// make a triangle with top at mid canvas

ctx.beginPath();

ctx.moveTo(can.width / 2,0);

ctx.lineTo(can.width,can.height);

ctx.lineTo(0,can.height);

ctx.closePath();

// make the current path the clipping region

ctx.clip();

// rectangle and text are clipped to the triangle

ctx.fillStyle = "blue";

ctx.fillRect(0,0,can.width,can.height);

ctx.fillStyle = "white";

ctx.font = "24pt Helvetica";

ctx.fillText("Triangular Clipping Mask", 20, 180);

}

</script>

</head>

<body onload="init()" >

<h2>Clipping To A Path</h2>

<canvas id="can" height="200" width="400">

</canvas>

</body>

</html>

Drawing Lines and ShapesCreating Masks

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

28

Page 29: HTML Canvas Guide

When you stroke or fill something on the canvas, it’s drawn using the current stroke or fill style. The stroke orfill style can be set to a color, a pattern, or a gradient.

GradientsA gradient specifies a starting color, an ending color, and an area over which color changes. A single gradientcan encompass more than one color change.

The 2D canvas drawing context supports two kinds of gradients: linear and radial.

Linear GradientsA linear gradient defines color change along a line between two points. You create a linear gradient by callingcreateLinearGradient() and passing in two points.

var grad = ctx.createLinearGradient(x1,y1, x2,y2);

The next step in defining a gradient is to add at least two color stops. A color stop consists of a color and aposition between 0 and 1, inclusive, where 0 is the beginning of the gradient and 1 is the end.

grad.addColorStop(0, 'black');

grad.addColorStop(1, 'white');

The color changes from one color stop to the next, at the defined intervals along the gradient.

Defining a linear gradient is like creating a virtual rectangle on the canvas, but it doesn’t display anything. Tolet the gradient “show through,” you need to set the fill style or stroke style to the gradient and draw somethingon top of the gradient. Note that if the thing you are drawing extends beyond the endpoints of the gradient,the fill or stroke color is the nearest color stop.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

29

Gradients and Patterns

Page 30: HTML Canvas Guide

The example in Listing 3-1 (page 30) defines a gradient that goes from black to white from the point 0,0 tothe point 50,0, then fills a rectangle with the gradient. The rectangle extends 25 pixels beyond the gradient’sendpoints. The result is shown in Figure 3-1.

Figure 3-1 Linear gradient

Listing 3-1 Creating a linear gradient

<html>

<head>

<title>Simple gradient example</title>

<script type="text/javascript">

var can;

var ctx;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

drawGradient();

}

function drawGradient() {

var grad = ctx.createLinearGradient(10,0, 80,0);

grad.addColorStop(0, 'black');

grad.addColorStop(1, 'white');

ctx.fillStyle=grad;

ctx.fillRect(10,10, 100,100);

}

</script>

</head>

<body onload="init()" style="background-color:#e0e0e0">

Gradients and PatternsGradients

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

30

Page 31: HTML Canvas Guide

<canvas id="can" height="200" width="400">

</canvas>

</body>

</html>

The gradient in the example above blends horizontally—the y-coordinate of the second point is the same asthat of the first point.

By creating a gradient with the second point above or below the first point, you can create a gradient thatblends up or down. If the second point is above or below the first point, and to the left or right as well, youcreate a gradient that blends diagonally to the left or right.

By specifying a color stop with an RGBa color value, you can create a gradient that includes degrees oftransparency.

The following snippet redefines drawGradients() to draw four rectangles, each with a gradient going in adifferent direction, all going from black to 50% transparent white. Since the webpage has a light greybackground, it shows through where the gradient is partly transparent. The output of the snippet is shown inFigure 3-2.

Figure 3-2 Linear gradients

function drawGradients() {

var grad = ctx.createLinearGradient(0,0, 50,0);

grad.addColorStop(0, 'black');

grad.addColorStop(1, 'rgba(255,255,255, 0.5)');

ctx.fillStyle=grad;

ctx.fillRect(0,0, 75,75);

var g2 = ctx.createLinearGradient(0,0, 0,50);

g2.addColorStop(0, 'black');

g2.addColorStop(1, 'rgba(255,255,255, 0.5)');

ctx.fillStyle=g2;

ctx.fillRect(75,0, 75,75);

Gradients and PatternsGradients

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

31

Page 32: HTML Canvas Guide

var g3 = ctx.createLinearGradient(150,0, 200,50);

g3.addColorStop(0, 'black');

g3.addColorStop(1, 'rgba(255,255,255, 0.5)');

ctx.fillStyle=g3;

ctx.fillRect(150,0, 75,75);

var g4 = ctx.createLinearGradient(275,50, 225,0);

g4.addColorStop(0, 'black');

g4.addColorStop(1, 'rgba(255,255,255, 0.5)');

ctx.fillStyle=g4;

ctx.fillRect(225,0, 75,75);

}

Of course, you can use colors other than black and white, and you can have more than one color stop. Thefollowing snippet redefines drawGradients() to create a rainbow gradient with seven color stops, each1/6th of the distance along the gradient, as shown in Figure 3-3.

Figure 3-3 Rainbow gradient

function drawGradients() {

var grad = ctx.createLinearGradient(10,0, 390,0);

grad.addColorStop(0, 'red');

grad.addColorStop(1 / 6, 'orange');

grad.addColorStop(2 / 6, 'yellow');

grad.addColorStop(3 / 6, 'green')

grad.addColorStop(4 / 6, 'aqua');

grad.addColorStop(5 / 6, 'blue');

grad.addColorStop(1, 'purple');

ctx.fillStyle=grad;

ctx.fillRect(0,0, 400,75);

}

Gradients and PatternsGradients

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

32

Page 33: HTML Canvas Guide

Note that the rectangle extends a little beyond the ends of the gradient to display bands of pure red andpurple.

Important You can add color stops, but you cannot change a color stop, replace it, or delete it. If you needto change color stops, you must create a new gradient with the color stops you want.

Radial GradientsA radial gradient defines a color change along a cone between two circles. You create a radial gradient bycalling createRadialGradient() and passing in two circles, each defined by a center point and a radius.It’s common to use the same center point and different radii.

var grad = ctx.createRadialGradient(x,y,rad, x1,y1,rad1);

The next step is to add at least two color stops. A color stop consists of a color and a position between zeroand one, inclusive, where zero is the beginning of the gradient and one is the end.

grad.addColorStop(0, 'black');

grad.addColorStop(1, 'white');

The color change goes from the first color stop to the last, at the defined intervals along the gradient.

The example in Listing 3-2 defines a radial gradient then uses it to fill a circle. The result is shown in Figure 3-4.

Figure 3-4 Radial gradient

Listing 3-2 Creating a radial gradient

<html>

<head>

<title>Radial Gradient</title>

<script type="text/javascript">

var can;

var ctx;

Gradients and PatternsGradients

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

33

Page 34: HTML Canvas Guide

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

drawGradients();

}

function drawGradients() {

var grad = ctx.createRadialGradient(50,50,5, 50,50,50);

grad.addColorStop(0, 'white');

grad.addColorStop(1, 'black');

ctx.fillStyle=grad;

ctx.arc(50,50, 50, 0, 2 * Math.PI);

ctx.fill();

}

</script>

</head>

<body onload="init()" <body onload="init()" style="backgroundcolor:#e0e0e0">

<h2>Radial Gradient</h2>

<canvas id="can" height="200" width="400">

</canvas>

</body>

</html>

A radial gradient is commonly used to create a 3D lighting effect. The following snippet draws a blue-filledcircle, then superimposes a gradient that goes from white at 90% opacity to black at 60% opacity. The centerof the gradient is offset 10 pixels upward, to simulate a light source from above. The result is shown in Figure3-5 (page 34).

Figure 3-5 Ball with gradient overlay

Gradients and PatternsGradients

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

34

Page 35: HTML Canvas Guide

function drawGradients() {

ctx.fillStyle="blue";

ctx.beginPath;

ctx.arc(50,50, 50, 0, 2 * Math.PI);

ctx.fill();

var grad = ctx.createRadialGradient(50,40,5, 50,40,50);

grad.addColorStop(0, 'rgba(255,255,255,0.9)');

grad.addColorStop(1, 'rgba(0,0,0,0.6)');

ctx.beginPath;

ctx.fillStyle=grad;

ctx.arc(50,50, 50, 0, 2 * Math.PI);

ctx.fill();

}

Animating GradientsYou can’t change the existing color stops in a gradient after it’s created. To animate a gradient, you need toeither create multiple gradients in advance and replace one with another, or generate new gradients on thefly.

The example in Listing 3-3 creates an endlessly repeating animated sequence of gradients, illustrated in Figure3-6.

Figure 3-6 Animated gradient

Listing 3-3 Animating a gradient

<html>

Gradients and PatternsGradients

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

35

Page 36: HTML Canvas Guide

<head>

<title>Animated Gradient</title>

<script type="text/javascript">

var can;

var ctx;

var angle = 0;

var delay = 50;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

var t = setInterval("animate()", delay);

}

// animate() is called every 50 msec

function animate() {

// create a variable that cycles from -1 to 1

angle = angle + 0.03;

var scalar = Math.sin(angle);

// create gradient that goes from bottom to top of canvas

var grad = ctx.createLinearGradient(0,can.height, 0,0);

// start gradient at blue

grad.addColorStop(0, 'blue');

// create rgb color value that goes from black to white

// as the variable goes from -1 to 1

var r = parseInt(128 + 128 * scalar);

var g = parseInt(128 + 128 * scalar);

var b = parseInt(128 + 128 * scalar);

// add color stop with new rgb color

grad.addColorStop(1, "rgb(" + r + "," + g + "," + b + ")");

Gradients and PatternsGradients

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

36

Page 37: HTML Canvas Guide

// clear canvas, fill canvas with gradient

ctx.clearRect(0,0, can.width,can.height);

ctx.save();

ctx.fillStyle=grad;

ctx.fillRect(0,0, can.width, can.height);

ctx.restore();

}

</script>

</head>

<body onload="init()" style="background-color:#e0e0e0">

<h2>Animated Gradient</h2>

<canvas id="can" height=250 width=500>

</canvas>

</html>

PatternsPatterns let you use small images in place of much larger graphics. A pattern can be made from a small, naturallyrepetitive image of sand, grass, or wood grain, for example, and used like a 2D texture map to generatephoto-realistic landscapes or interiors.

You can choose a pattern to use as a stroke or fill style. A pattern consists of an image source—such as a JPG,GIF, PNG, SVG, or the currently playing frame of a video—and a repetition setting, to create a pattern fromtiles of the image.

To create a pattern, you first need to create an image source, such as an img or video element, then get theelement into a JavaScript object.

You create a pattern using the context’s createPattern() method, passing in the image object and a"repeat" parameter.

var dot = document.getElementById("dot");

Gradients and PatternsPatterns

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

37

Page 38: HTML Canvas Guide

var pat = ctx.createPattern(dot,"repeat");

The "repeat" string repeats the image both horizontally and vertically, but you can specify "repeat-x","repeat-y", or "repeat-none" to cause the pattern to be repeat horizontally only, vertically only, or notat all.

The example in Listing 3-4 loads three images—dot.png, block.png, and cork.png—and makes them intopatterns that are each used to stroke a line and fill a rectangle. The result is shown in Figure 3-7.

Figure 3-7 Patterns

Listing 3-4 Creating patterns

<html>

<head>

<title>Patterns</title>

<script type="text/javascript">

function init() {

var can = document.getElementById("can");

var ctx = can.getContext("2d");

// get images into objects

var dot = document.getElementById("dot");

var block = document.getElementById("block");

var cork = document.getElementById("cork");

// create patterns from images

var pat = ctx.createPattern(dot,"repeat");

var pat2 = ctx.createPattern(block,"repeat");

Gradients and PatternsPatterns

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

38

Page 39: HTML Canvas Guide

var pat3 = ctx.createPattern(cork,"repeat");

// make line thicker so pattern shows

ctx.lineWidth=3;

// stroke line and fill rect with dot pattern

ctx.beginPath();

ctx.moveTo(10,10);

ctx.lineTo(100,10);

ctx.strokeStyle=pat;

ctx.stroke();

ctx.fillStyle=pat;

ctx.fillRect(10,20, 100,150);

// stroke line and fill rect with block pattern

ctx.beginPath();

ctx.moveTo(150,10);

ctx.lineTo(250,10);

ctx.strokeStyle=pat2;

ctx.stroke();

ctx.fillStyle=pat2;

ctx.fillRect(150,20, 100,150);

// stroke line and fill rect with cork pattern

ctx.beginPath();

ctx.moveTo(300,10);

ctx.lineTo(400,10);

ctx.strokeStyle=pat3;

ctx.stroke();

ctx.fillStyle=pat3;

ctx.fillRect(300,20, 100,150);

}

</script>

</head>

Gradients and PatternsPatterns

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

39

Page 40: HTML Canvas Guide

<body onload="init()">

Dot: <img id="dot" src="dot.png"><br>

Block: <img id="block" src="block.png"><br>

Cork: <img id="cork" src="block.png"><br>

<canvas id="can" height="200" width="300">

</canvas>

</body>

</html>

Gradients and PatternsPatterns

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

40

Page 41: HTML Canvas Guide

You can add predrawn images to the canvas, such as GIFs, JPEGs, PNGs, SVGs, or the current frame of a video.By default, such images are drawn at their native size, and are clipped if they extend beyond the canvas. Youcan specify a height and width at which to display the image, however, or specify a region of the image todraw and a region of the canvas to draw it on.

Image SourcesYou can use an img element, a video element, or another canvas element as an image source. If your imageis an HTML element, get it into a JavaScript object using a method such as getElementById. You can alsocreate an image source entirely in JavaScript, using such methods as new Image.

This chapter describes using images specified using the <img> tag. For a description of how to use video asan image source, see “Putting Video on Canvas” (page 188).

If you use an animated GIF as an image source, only the first frame of the animation is shown. Transparencyis supported.

When you use a PNG as an image source, it is recommended that it have a transparent background, so that itcomposites smoothly with other elements. PNG image sources are preferred to GIFs or JPGs.

If your animation uses a lot of scaling and rotation, the ideal image source is an SVG file. SVGs are inherentlyresolution-independent, so they appear smooth and sharp at any size or rotation.

Scaling images other than SVGs to a size greater than 1 can result in pixelation. Unless your source is an SVG,if the image is displayed at multiple sizes, the best results will come from using a source whose native size isthe largest version displayed and scaling the image down when it needs to appear smaller.

You can use another canvas as an image source, but you cannot use a canvas as an image source inside itself.The main reason to use a canvas as an image source is as an offscreen buffer for image processing. You canload an image into a hidden canvas, manipulate the image at the pixel level, and display the results in anothercanvas.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

41

Using Predrawn Images

Page 42: HTML Canvas Guide

Preparing to Draw an ImageInclude an <img> tag in your HTML. If you don’t want the image displayed outside of the canvas, set thedisplay property to none. It’s a good idea to give the image an id, to make it easier to manipulate usingJavaScript. Get the image into a JavaScript object so you can use it with the canvas.

Listing 4-1 shows how to add an image for use in your canvas.

Listing 4-1 Drawing an image

<html>

<head>

<title>Drawing An Image</title>

<script type="text/javascript">

var can;

var ctx;

var sprite;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

sprite = document.getElementById("sprite");

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="300" width="300">

</canvas>

<img id="sprite" src="lem.png" style="display:none">

</body>

</html>

Using Predrawn ImagesPreparing to Draw an Image

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

42

Page 43: HTML Canvas Guide

Drawing an Image NormallyTo draw an image at its native height and width, clipping any areas that fall outside the canvas, call the context’sdrawImage(image, x,y) method, passing in the x,y coordinate at which to draw the image.

ctx.drawImage(image,x,y)

The following snippet draws an image at 0,0—by default, the upper-left corner of the canvas.

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

sprite = document.getElementById("sprite");

drawSprite();

}

function drawSprite() {

ctx.drawImage(sprite,0,0);

}

Drawing an Image with a Given Height and WidthYou can specify a height and width for an image by passing two additional parameters into drawImage().

ctx.drawImage(image, x,y, width,height)

The image is scaled to the specified height and width. If part of the image falls outside the canvas, the imageis clipped at the canvas boundary.

The following snippet draws an image at 0,0 with a width of 200 and a height of 100.

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

Using Predrawn ImagesDrawing an Image Normally

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

43

Page 44: HTML Canvas Guide

sprite = document.getElementById("sprite");

drawSprite();

}

function drawSprite() {

ctx.drawImage(sprite,0,0,200,100);

}

Drawing an Image with Region MappingYou can specify a region of the image and a region of the canvas as part of the drawImage() method. Onlythe specified region of the image is displayed, scaled to fill the specified region of the canvas.

To draw an image with region mapping, call drawImage(), passing in the image; the x, y, width, and heightof the source region within the image; and the x, y, width, and height of the destination region in the canvas,as illustrated in Figure 4-1.

ctx.drawImage(image, sx,sy,sWide,sHi, dx,dy,dWide,dHigh)

Figure 4-1 Region mapping

Destination canvasSource imagesx

sy

sw

sh

dy

dx

dw

dh

The following snippet clips an image to a 100 x 100 area and draws it at 0,0 scaled up by a factor of 2.

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

sprite = document.getElementById("sprite");

Using Predrawn ImagesDrawing an Image with Region Mapping

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

44

Page 45: HTML Canvas Guide

drawSprite();

}

function drawSprite() {

ctx.drawImage(sprite, 0,0,100,100, 0,0,200,200);

}

The example in Listing 4-2 uses region mapping to divide an image of a soccer ball into 25 tiles. The tiles areinitially mapped to the regions where they would be drawn as part of the whole image, so the ball appears tobe drawn normally. When the BOOM button is clicked, the tiles are redrawn several times, mapped further andfurther from each other each time. The canvas is rotated slightly between drawings so the tiles fly away fromeach other with a circular motion, as illustrated in Figure 4-2.

Figure 4-2 Exploding soccer ball

Listing 4-2 Exploding an image

<html>

<head>

<title>Exploding Ball</title>

<script type="text/javascript">

var can;

var ctx;

var ball;

var x;

var y;

var scalar;

var ballHi = 150;

var ballWide = 150;

// offset so ball center is drawn at x,y

Using Predrawn ImagesDrawing an Image with Region Mapping

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

45

Page 46: HTML Canvas Guide

var xOff = -1 * ballWide / 2;

var yOff = -1 * ballHi / 2;

var tiles = 25;

var grid;

var tHi;

var tWide;

function init() {

ball = document.getElementById("ball");

can = document.getElementById("can");

ctx = can.getContext("2d");

x = can.width / 2;

y = can.height / 2;

// tiles must be a perfect square--4, 9, 16, 25, etc.

grid = Math.sqrt(tiles);

tHi = ballHi / grid;

tWide = ballWide / grid;

// when scalar is 1, everything is drawn normally

scalar = 1;

drawTiles();

}

function drawTiles(){

ctx.clearRect(0,0,can.width,can.height);

ctx.save();

ctx.translate(x,y);

ctx.scale = scalar;

ctx.rotate(scalar - 1);

for (i=0;i<grid;i++) {

for (j=0;j<grid;j++) {

var tileX = i * tWide;

var tileY = j * tHi;

ctx.drawImage(ball,tileX,tileY,tWide,tHi,

scalar * (xOff + tileX),

Using Predrawn ImagesDrawing an Image with Region Mapping

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

46

Page 47: HTML Canvas Guide

scalar * (yOff + tileY),tWide,tHi);

}

}

ctx.restore();

}

function boom() {

scalar = scalar + 0.05;

drawTiles();

if (scalar < 3)

setTimeout("boom()",50);

else

ctx.clearRect(0,0,can.width,can.height);

}

</script>

</head>

<body onload="init()">

<h2>Exploding Ball</h2>

<p>

<img id="ball" src="soccerball1.png" style="display:none">

<canvas id="can" height="300" width="400" style="position:relative;top:-50">

</canvas>

</p>

<input type="button" value="BOOM" onclick="boom();">

<input type="button" value="Reset" onclick="init();">

</body>

</html>

Using Predrawn ImagesDrawing an Image with Region Mapping

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

47

Page 48: HTML Canvas Guide

The canvas element supports basic text rendering on a line-by-line basis. Just enter a line of text and an x,ycoordinate for the text box using the fillText("text", x,y) or strokeText("text", x,y) method.The text is rendered using the current stroke or fill style, which can be a color, gradient, or pattern.

You can specify a number of text settings, such as the font family, size, and weight, and the text alignmentand baseline.

For example, the following snippet prints “Figure 1” on the canvas at coordinates 10, 250 in 24-point Helvetica.The default alignment and baseline are used.

function drawText() {

ctx.fillStyle = "blue";

ctx.font="24pt Helvetica";

ctx.fillText("Figure 1", 10, 250);

}

Note Text can be rotated, scaled, or transformed like any other element of the canvas. For details,see “Translation, Rotation, and Scaling” (page 60) and “Matrix Transforms” (page 69).

Text is not word-wrapped at the boundaries of the canvas. The text box extends automatically to hold thespecified text on a single line. You need to create a separate text box for each line of text.

Text SettingsBefore you add text to the canvas, you need to set either the fill or stroke style (see “Set the Stroke and FillStyles” (page 19) for details). You probably also want to specify text settings, unless you want to use thebrowser’s default settings.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

48

Adding Text

Page 49: HTML Canvas Guide

Note The default canvas text settings for Safari are 10-pixel sans-serif, left justified, with the alphabeticbottom of the text baseline at the y-coordinate.

Font SettingsYou can specify font settings by setting the context’s font property. The font property is set using the samesyntax as the all-in-one CSS font property. You can specify any or all of the font settings, space or commadelimited, as appropriate. For example:

ctx.font="24pt Helvetica bold, sans-serif"

specifies a preference for 24-point, bold Helvetica, falling back to a generic sans-serif font if Helvetica isunavailable.

Important Individual font settings, such as font-size and font-family, are not supported, and will causeerrors. Set all desired font properties at once, using a space-delimited or comma-delimited list.

You are not limited to the fonts the user has installed on the host device. The canvas has access to any fontsloaded in your webpage using the CSS @font-face property, once the fonts have loaded.

Text DirectionThe default text direction is left-to-right. You can change this by setting the canvas dir property to rtl.

For example, document.getElementsByTagName('canvas')[0].dir = 'rtl';would change the textdirection of the first canvas element to right-to-left.

Possible values for dir are rtl and ltr.

Note that text direction is a standard HTML property, not specific to the canvas, and can be inherited fromparent elements such as the document body.

Text AlignmentSet the text alignment (sometimes called justification) by setting the context’s textAlign property. Possiblevalues are start, end, left, right, and center.

The start and end values are the same as the left and right values, but are dependent on the text direction.If the text direction is left-to-right, start is the same as left, and if the text direction is right-to-left, startis the same as right.

Adding TextText Settings

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

49

Page 50: HTML Canvas Guide

The textAlign property changes the meaning of the x-coordinate specified in strokeText("text", x,y)or fillText("text", x,y). The x-coordinate specifies the left, right, or center of text box, depending onthe textAlign setting.

Figure 5-1 shows the different text alignment settings applied to text drawn at the same x-coordinate, shownby the red line.

Figure 5-1 Text alignment

Text BaselineThe text baseline is specified by the y-coordinate in fillText(text, x,y) or strokeText(text, x,y).

The default text baseline is the alphabetic baseline—the bottom of most letters in the roman alphabet (thedescender of a lowercase y, p, or q drops below it).

You can change the text baseline by setting the context’s textBaseline property. Legal values are:

● top

● hanging

● middle

● alphabetic

● ideographic

● bottom

Adding TextText Settings

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

50

Page 51: HTML Canvas Guide

The position of each baseline value, relative to the bounding box, is shown in Figure 5-2.

Figure 5-2 Text baselines

Top of bounding box

Top of em square

Hanging baselineMiddleAlphabetic baseline

Ideographic baseline

Bottom of em square

Bottom of bounding box

The following snippet positions the text baseline so the text is vertically centered in the bounding box.

ctx.textBaseline = "middle";

Bounding Box WidthSuppose that your canvas needs to include text that’s supplied at runtime. You might want to break the textinto multiple lines, or scale the text, depending on its length, or you might need to position the text relativeto a graphic element. To do any of these things, you need to know how large the bounding box for the textwould be.

The measureText() method takes a string as an argument and returns a metric. The width property of themetric is the width in pixels of the needed bounding box with the current font settings.

The following snippet iterates through an array of player names, measures the width of the bounding boxneeded to hold each name, prints the name, and positions an image ten pixels to the right of the boundingbox.

for (i=0;i<players;i++) {

var name = playerName[i];

var metric = measureText(name);

var edge = metric.width;

ctx.fillText(name, 0, 50 * i);

ctx.drawImage(playerIcon, edge + 10, 50 * i);

}

Adding TextText Settings

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

51

Page 52: HTML Canvas Guide

Drawing TextThe context has two methods for drawing a line of text. Both methods define a text box.

The fillText(text, x,y [,maxWidth]) method fills the text in the current fill style. ThestrokeText(text, x,y [,maxWidth])method outlines the text in the current stroke style. Both methodsaccept the same arguments.

The text parameter must be a string, and can contain Unicode characters.

The x parameter is the alignment point for the text box, as set in the context’s textAlign property. Thedefault is the left edge of the text box.

The y parameter is the text baseline within the bounding box, as set in the context’s textBaseline property.The default setting is alphabetic—in line with the bottom of roman letters without descenders.

You can pass in an optional maxWidth value, which scales the text, if needed, to fit in a text box no wider thanmaxWidth.

Listing 5-1 displays the text “Hello, world.” in 24-point Helvetica, centered horizontally and vertically, filled inblue on a white background. The result is shown in Figure 5-3.

Figure 5-3 Hello world

Listing 5-1 Drawing “Hello, world.”

<html>

<head>

<title>Hello world</title>

<script type="text/javascript">

var can;

var ctx;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

drawText();

Adding TextDrawing Text

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

52

Page 53: HTML Canvas Guide

}

function drawText() {

ctx.fillStyle = "blue";

ctx.font = "24pt Helvetica";

ctx.textAlign = "center";

ctx.textBaseline = "middle";

ctx.fillText("Hello, world.", can.width / 2 , can.height / 2);

}

</script>

</head>

<body onload="init()" style="background-color:black">

<canvas id="can" height="200" width="400" style="backgroundcolor:white">

</canvas>

</body>

</html>

Animating TextLike any other visual element, text can be rotated, scaled, transformed, and animated.

Note It’s best to use vector fonts when scaling or rotating text, because bitmapped fonts can appearjagged when scaled up or rotated.

The usual sequence for animation applies:

1. Clear the canvas.

2. Save the context.

3. Change the context settings.

4. Draw the next frame.

Adding TextAnimating Text

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

53

Page 54: HTML Canvas Guide

5. Restore the context.

To repeat the animation, the steps are grouped into a function, and the function is called periodically after asuitable delay—using setInterval() for endless animations, or setTimeout() for animations that repeatconditionally.

When rotating an element, the simplest approach is to follow these steps:

1. Translate the context to the x and y coordinates of the element.

2. Rotate the context.

3. Draw the element, centered at 0,0.

(For a detailed explanation of why you should follow these steps, see “Rotation” (page 63).)

Listing 5-2, displays the string “Hello, world!” in 48 point Helvetica, filled in blue. The text spins in, starting smalland growing to its full size as it spins, as illustrated in Figure 5-4.

Figure 5-4 Animated text

To achieve this effect, the text is scaled and rotated repeatedly. The rotation goes from 0 to 2 x Pi radians(0-360°). The animation takes place over a fixed number of steps, so a rotation increment of 2 * Pi / stepsis calculated. A scale increment of 1 / steps is calculated, so the scale will grow to 1 in steps.

Listing 5-2 Animating text

<html>

<head>

<title>Hello world!</title>

<script type="text/javascript">

var can;

var ctx;

var addAngle;

var addScale;

var step;

var steps = 50;

var delay = 20;

Adding TextAnimating Text

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

54

Page 55: HTML Canvas Guide

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

addAngle = Math.PI * 2 / steps;

addScale = 1 / steps;

step = 0;

ctx.fillStyle = "blue";

ctx.font = "48pt Helvetica";

ctx.textAlign = "center";

ctx.textBaseline = "middle";

spinText();

}

function spinText() {

step++;

ctx.clearRect(0,0, can.width, can.height);

ctx.save();

ctx.translate(can.width / 2, can.height / 2);

ctx.scale(addScale * step, addScale * step);

ctx.rotate(addAngle * step);

ctx.fillText("Hello, world!", 0,0);

ctx.restore();

if (step < steps)

var t = setTimeout('spinText()', delay);

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="200" width="400">

</canvas>

Adding TextAnimating Text

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

55

Page 56: HTML Canvas Guide

</body>

</html>

Adding TextAnimating Text

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

56

Page 57: HTML Canvas Guide

Any visual element—a shape, a line, text, or an image—can be given a shadow. You can set the shadow color,the x and y offsets from the source, and set a Gaussian blur to make the shadow more realistic.

Even though the canvas drawing surface is 2D, careful use of shadows can create a sense of a third dimension,with elements seeming to lift off the page.

Shadows work well with SVG, PNG, or GIF images on transparent backgrounds, casting shadows the shape ofthe nontransparent parts of the images. JPG images cast rectangular shadows, however, even if the imagebackground matches the canvas background.

Shadows from a rectangle, lines, a PNG, a JPG, and text are illustrated in Figure 6-1.

Figure 6-1 Shadows

Shadow BasicsEnable shadows by setting the context’s shadowColor property to a nontransparent color—for example,ctx.shadowColor = "black";. The shadow color must be a CSS color. It cannot be a gradient or pattern.

Disable shadows by setting the shadowColor to any RGBa value with an alpha component of 0—for example,ctx.shadowColor = "rgba(80,80,80, 0)";.

The default x and y offsets of a shadow are zero. To make the shadow fall away from the element, set theshadowOffsetX and shadowOffsetY properties. For example:

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

57

Adding Shadows

Page 58: HTML Canvas Guide

ctx.shadowOffsetX = 5;

ctx.shadowOffsetY = 5;

Once you’ve set the shadow color, and an x or y offset of at least 1, any drawing operation will include a shadow.

Transparency and BlurYou can make shadows more realistic by adding transparency and blur to lighten and soften them.

If you set the shadowColor property to an RGBa color with an alpha value less than 1, the shadow is partlytransparent, allowing you to see the background, which is darkened by the shadow but not completely obscured.Greater transparency lightens the shadow and mimics softer lighting.

You can make a shadow softer by applying a Gaussian blur. Light from a point source causes sharp shadows.Diffuse lighting throws softer shadows. Set the blur using the context’s shadowBlur property. For example:ctx.shadowBlur=1;

The default value is 0 (no blur). A blur of 1 is barely noticeable. A blur of 5 is a soft shadow. A blur of 10 makesthe shadow a vague blob.

A canvas with four different alpha and blur values is shown in Figure 6-2.

Figure 6-2 Shadow hardness

Notice that the white background and black text are visible through the shadow, as long as the alpha value isless than 1.

Shadows and AnimationCanvas shadows are not affected by rotation. A real shadow falls from the light source onto the backgroundat the same angle, regardless of an object’s orientation. Canvas shadows mimic this real-wold behavior.

If you set the shadowOffsetX and shadowOffsetY properties both to 10, for example, the shadow fallsdown and to the right, no matter what rotation is applied to the context.

Adding ShadowsTransparency and Blur

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

58

Page 59: HTML Canvas Guide

The example illustrated in Figure 6-3 rotates a soccer ball at the center of the canvas. A blue rectangle orbitsthe soccer ball. Notice that the shadows retain their orientation, regardless of the rotation.

Figure 6-3 Shadow rotation

A shadow’s size grows or shrinks as the object casting it is scaled up or down, but the shadowOffsetX andshadowOffsetY properties do not scale with the context. This maintains the shadow angle, mimickingreal-world behavior.

Adding ShadowsShadows and Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

59

Page 60: HTML Canvas Guide

Imagine that you had a magic drawing surface; you could slide it effortlessly, so the center would be anywhereunder your drawing; you could spin it around its center, without disturbing anything you had already drawn,and any subsequent drawing would be rotated perfectly; you could expand or shrink it—again withoutdisturbing your existing drawing—and anything you drew subsequently would be scaled up or down precisely.

That magic drawing surface is built into the canvas element.

You reposition the center of your drawing surface—the 0,0 point, or origin—by calling the translate(x,y)method. The origin of the canvas’s coordinate system is moved to the point x,y.

You spin the drawing surface around the origin by calling the rotate(angle)method. The canvas’s coordinatesystem is spun clockwise by angle radians.

You scale the drawing surface by calling the scale(xScale,yScale) method. The canvas’s coordinatesystem is scaled wider or narrower by xScale, and taller or shorter by yScale.

When you change the rotation, translation, or scale, you are changing the underlying coordinate system ofthe canvas—the change affects all subsequent drawing operations, but it has no effect on anything alreadydrawn.

Translation, rotation, and scaling are all examples of two-dimensional transforms. If you perform more thanone transform on the canvas, the order in which you do things is important. For example, if you translate andthen rotate, you move the origin then rotate the coordinate system around it, but if you rotate and thentranslate, you spin the coordinate system around its existing origin, then move the origin in the rotatedcoordinate system.

Each transform is individually simple and intuitive, and for the most part, it’s easy to predict what will happenwhen you perform two or three transforms. After that, things get complicated very quickly. To avoid unintendedinteractions, it’s generally wise to save the context, perform two or three transforms, then restore the contextand repeat the process.

For more about transformations generally, see “Matrix Transforms” (page 69).

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

60

Translation, Rotation, and Scaling

Page 61: HTML Canvas Guide

TranslationTranslation changes the origin, or 0,0 point, of the canvas’s coordinate system. Call the context’stranslate(x,y) method to make the point x,y the new origin.

One use for translation is to redraw a path at a new location without having to respecify the endpoints. Youspecify a path as a series of endpoints using x and y coordinates. The simplest way to draw a path at a newlocation—offset from its original location by x,y—is to translate the coordinate system to x,y, then draw theexact same path again, as exemplified in Listing 7-1 and illustrated in Listing 7-1.

Listing 7-1 Drawing a path at a new location

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

drawPath();

ctx.clearRect(can.width, can.height);

ctx.translate(120,80);

drawPath();

}

function drawPath() {

ctx.strokeStyle="black";

ctx.beginPath();

ctx.moveTo(0,40);

ctx.lineTo(80,40);

ctx.lineTo(40,80);

ctx.closePath();

ctx.stroke();

Translation, Rotation, and ScalingTranslation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

61

Page 62: HTML Canvas Guide

}

Figure 7-1 Translating a path

ScalingYou expand or contract the canvas’s coordinate system by calling the context’s scale(xScale, yScale)method. Anything you draw subsequently is made bigger or smaller by the xScale and yScale values. IfxScale = 2, for example, everything you draw comes out twice as wide. If yScale = 0.5 everything comesout half as tall.

If you scale an element, you need to compensate for the changes to the coordinate system when placing theelement on the canvas.

An easy way to draw an element at point x,y, scaled up or down, is to first translate to x,y, then set the scale,and draw the element at 0,0.

Alternatively, you can set the scale without translating the coordinate system first, then draw the element atthe point x / xScale, y / yScale.

Translation, Rotation, and ScalingScaling

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

62

Page 63: HTML Canvas Guide

RotationYou rotate the canvas’s coordinate system around its origin by calling the context’s rotate(angle) method.The coordinate system is rotated by angle radians, clockwise. Anything already on the canvas is unaffected,but subsequent drawing operations are rotated.

When you rotate the canvas, the x and y coordinates change in complex ways, except at the origin, whichremains at 0,0. The simplest way to draw an element at a point x,y, rotated at an angle, is to translate to thepoint x,y, set the rotation, and draw the element at 0,0.

Alternatively, you can rotate the canvas without translating the coordinate system first, then draw the elementat x * cos(-angle) - y * sin(-angle), y * cos(-angle) + x * sin(-angle). But that’s reallydoing it the hard way.

In other words, if you want to draw something at x,y rotated at an angle, follow these steps:

1. Call ctx.save().

2. Call ctx.translate(x,y).

3. Call ctx.rotate(angle).

4. Draw the path, shape, or image at 0,0.

5. Call ctx.restore().

The example in Listing 7-2 draws an image and some accompanying text twice, once at 0 degrees and onceat 90 degrees of rotation.

Translation, Rotation, and ScalingRotation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

63

Page 64: HTML Canvas Guide

Listing 7-2 Rotating an image and text

<html>

<head>

<title>Rotation</title>

<script type="text/javascript">

var can;

var ctx;

var sprite;

var x;

var y;

var rot = 0;

function init() {

sprite = document.getElementById("sprite");

can = document.getElementById("can");

ctx = can.getContext("2d");

ctx.fillStyle="blue";

ctx.font="12pt Helvetica";

ctx.textBaseline="top";

x = 100;

y = 0;

rot = 0;

drawSprite();

x = 100;

y = 100;

rot = Math.PI / 2;

drawSprite();

}

function drawSprite() {

ctx.save();

ctx.translate(x,y);

ctx.rotate(rot);

Translation, Rotation, and ScalingRotation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

64

Page 65: HTML Canvas Guide

ctx.drawImage(sprite, 0,0);

ctx.fillText("fig. 1", 0,0);

ctx.restore();

}

</script>

</head>

<body onload="init()" >

<h2>Rotation</h2>

<canvas id="can" height="200" width="300" >

</canvas>

<img id="sprite" src="lem.png" style="display:none">

</body>

</html>

Rotation Around the CenterOnce the context’s rotate(angle)method is called, all subsequent drawing operations are rotated clockwiseby angle radians.

Images are rotated around their origin (the upper-left corner). Rectangles are rotated around their x,y coordinate(the upper-left corner, if height and width are positive). Other shapes are rotated around the first point on theshape’s path.

In many cases, you may want to rotate elements around their center instead. For images and rectangles, thisis easy. Offset the drawing by negative one-half the height and width of the image or rectangle. The followingsnippet draws an image rotated around its center at position x,y:

var xOffset = image.width / -2;

var yOffset = image.height / -2;

ctx.save();

ctx.translate(x,y);

ctx.rotate(angle);

ctx.drawImage(image.png, xOffset,yOffset);

ctx.restore();

Translation, Rotation, and ScalingRotation Around the Center

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

65

Page 66: HTML Canvas Guide

If you are scaling the image as well as rotating it, offset the drawing operation by negative one-half the heightand width, multiplied by the scale, as shown in the following snippet.

var xOffset = image.width / -2;

var yOffset = image.height / -2;

ctx.save();

ctx.translate(x,y);

ctx.rotate(angle);

ctx.scale(xScale,yScale);

ctx.drawImage(image.png, xOffset * xScale, yOffset * yScale);

ctx.restore();

If you are planning to rotate a path or complex shape about its center, define the points on the path relativeto the center point. Listing 7-3 and Listing 7-4 show two ways to define the same shape, but the second snippetis easily moved and rotated by setting new values for x and y.

Listing 7-3 Defining a shape literally

function drawPath() {

ctx.beginPath();

ctx.moveTo(0,0);

ctx.lineTo(50,0);

ctx.lineTo(50,50);

ctx.lineTo(0,50);

ctx.closePath();

}

Listing 7-4 Defining a shape relative to its center point

var x=25;

var y=25;

function drawPath() {

ctx.beginPath();

ctx.moveTo(x-25,y-25);

ctx.lineTo(x+25,y-25);

ctx.lineTo(x+25,y+25);

Translation, Rotation, and ScalingRotation Around the Center

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

66

Page 67: HTML Canvas Guide

ctx.lineTo(x-25,y+25);

ctx.closePath();

}

The following snippet draws the shape with its center at any point x,y, rotated at an angle.

ctx.save();

ctx.translate(x,y);

ctx.rotate(angle);

drawPath();

ctx.restore();

Combining Asymmetric Scaling and RotationMost combinations of rotation, translation, and scale behave as you would intuitively expect them to, if youconsider what each transform does and perform the operations in a logical sequence. The one exception iscombining rotation and asymmetric scaling—not just making things bigger or smaller, but stretching orcompressing the shape by using a different scale on the x-axis and y-axis.

If you rotate the coordinate system, then scale just the x-axis, for example, whatever you draw is stretched inthe direction of the rotated x-axis. If you rotate the coordinate system a quarter turn, for example, and thenincrease the x scale by a factor of two, things are drawn twice as tall. This is probably what you would intuitivelyexpect; whatever you draw is rotated, but retains its stretched shape.

Translation, Rotation, and ScalingCombining Asymmetric Scaling and Rotation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

67

Page 68: HTML Canvas Guide

If you scale before you rotate, however, whatever you draw is stretched in the direction the x-axis was pointingprior to the rotation. This causes the shape of things you draw to change when rotated. This is probably notwhat you would expect to happen. The interactions are illustrated in Figure 7-2.

Figure 7-2 Nonintuitive interaction

Unmodified rectangle

Rotated 90 degrees

X scaled x 2

Rotated 90 degress, then X scaled x 2

X scaled x 2, then rotated 90 degrees

To stretch something, then rotate it, retaining the stretched shape, you must perform the transforms by firstrotating, then scaling, in that order.

If you scale symmetrically, setting the x scale and y scale to the same value, you can perform the rotate()and scale() operations in either order.

Translation, Rotation, and ScalingCombining Asymmetric Scaling and Rotation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

68

Page 69: HTML Canvas Guide

Rotation, translation, and scaling are all accomplished using a transformation matrix—a set of nine numbersthat are used to transform a two-dimensional array, such as a bitmap, using linear algebra.

There are convenience methods for the most common transforms—rotation, translation, and scaling—butyou can use matrix transforms to achieve other effects as well, such as shearing or reflection.

Setting the Transformation MatrixYou can set the transformation matrix to new values by calling the context’s setTransform(a,b,c,d,e,f)method, passing in new values for the first two rows of the matrix (the third row is always 0 0 1).

The position of the parameters in the matrix is shown in Figure 8-1.

Figure 8-1 Matrix parameters and positions

c

100

ba

def

Important Other APIs that use transformation matrices may order the parameters differently. Be sure tocheck the position of parameters in the matrix when copying matrix settings from another source.

When you set the transformation matrix, it overrides any rotation, translation, or scale settings, as these settingsall use the transformation matrix.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

69

Matrix Transforms

Page 70: HTML Canvas Guide

Note If you set the rotation, scale, or translation after setting the transformation matrix, thetransformation matrix is modified, but is not reset—the matrix values used by the operation areoverridden, but other matrix values are not changed.

Example: Setting the Matrix for ReflectionTo set the transformation matrix to reflect everything around the y-axis, callctx.setTransform(1,0,0,-1,0,0). Thereafter, all drawing operations result in an upside-down image,and all y-coordinates are multiplied by -1.

To set up a transformation matrix to reflect everything around the x-axis, callctx.setTransform(-1,0,0,1,0,0). Thereafter, all drawing operations result in a mirror image, and allx-coordinates are multiplied by -1.

To set up a transformation matrix to reflect everything around both the x-axis and y-axis, callctx.setTransform(-1,0,0,-1,0,0). Thereafter, all drawing operations result in an upside-down mirrorimage, and all x and y-coordinates are multiplied by -1.

An example of text reflected around the y-axis is generated by Listing 8-1 and shown in Figure 8-2.

Figure 8-2 Reflected text

Listing 8-1 Reflecting text

<html>

<head>

<title>Reflection Matrix</title>

<script type="text/javascript">

var can;

var ctx;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

Matrix TransformsSetting the Transformation Matrix

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

70

Page 71: HTML Canvas Guide

ctx.fillStyle="blue";

ctx.font="48pt Helvetica";

ctx.fillText("Reflection", 0,100);

ctx.setTransform(1,0,0,-1,0,0);

ctx.fillStyle="red";

ctx.fillText("Reflection", 0,-100);

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="200" width="300">

</canvas>

</body>

</html>

You can set the scale after setting a reflection matrix, because the two operations use different parts of thematrix. Listing 8-2 adds a few lines to the previous example that set the y-scalar to 1.5 and sets the fill style toa gradient instead of a solid color, making the reflection taller and fading it from blue to transparent white.The result is shown in Figure 8-3

Figure 8-3 Reflection with scale and gradient

Listing 8-2 Adding a scalar and gradient

ctx.scale(1,1.5);

var grad = ctx.createLinearGradient(0,-50, 0,-140);

grad.addColorStop(0, 'blue');

grad.addColorStop(1, 'rgba(255,255,255,0)');

ctx.fillStyle=grad;

ctx.fillText("Reflection", 0,-102 / 1.5);

Matrix TransformsSetting the Transformation Matrix

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

71

Page 72: HTML Canvas Guide

Note The y-coordinates of the gradient and reflected text are modified because of the transformationmatrix—the y-coordinates are negated and divided by the y-scalar (1.5).

The Identity MatrixTo reset the transformation matrix to the identity matrix (no transformation), callctx.setTransform(1,0,0,1,0,0). The identity matrix and the parameter positions are illustrated in Figure8-4.

Figure 8-4 The identity matrix

c=0

100

b=0a=1

d=0e=0f=0

Transforming the Transformation MatrixThe transformation matrix can be treated as a two-dimensional array. You can transform the current matrix byapplying a second matrix to it.

To transform the current matrix by a second matrix, call the context’s transform(a,b,c,d,e,f) method,passing in values for the first two rows of the second matrix (the third row is always 0 0 1).

Transforming the transformation matrix potentially changes any rotation, scale, or translation settings.

Setting the rotation, scale, or translation after transforming the transformation matrix results in additionalchanges to the matrix—the matrix values used by the rotation, scale, or translation operation are overridden.

Transforming the transformation matrix is a method intended for developers well-versed in linear algebra. Adescription of the mathematics involved falls outside the scope of this document.

Matrix TransformsTransforming the Transformation Matrix

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

72

Page 73: HTML Canvas Guide

By default, images are layered on the canvas in drawing order, with new images layered on top of older images.Also by default, images that have areas with alpha values less than 1 are partly or wholly transparent, lettinglower layers show through to varying degrees.

The web page background is the lowest layer, followed by any HTML elements positioned beneath the canvaselement. Any CSS background applied to the canvas element comes next, followed by anything drawn on thecanvas, with the most recent image, line, or shape in the top layer.

The canvas element also supports a global alpha channel. The global alpha channel can be set to any valuebetween 0 and 1, inclusive, and defaults to 1. All drawing operations have their alpha values multiplied by theglobal alpha value, and so can be attenuated to any degree, up to full transparency. This allows you to drawsemitransparent images, even if the source image has no alpha channel.

The global alpha value, simple layering, and the default alpha-channel compositing mode provide sufficientcompositing capabilities for most applications, but canvas has ten other built-in compositing modes in additionto the default mode, and supports vendor-specific extensions as well.

Global AlphaThe global alpha value allows you to draw with varying degrees of transparency. Set the global alpha valueby assigning it to the context’s globalAlpha property.

ctx.globalAlpha=alpha

The alpha value must be a number between 0 and 1, inclusive.

A value of 1 does not change the native alpha value of images, colors, gradients, or patterns. No transparencyis added.

A value of less than 1 reduces the alpha values of all drawing operations proportionately.

A value of 0 causes all subsequent drawing operations to render transparently.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

73

Advanced Compositing

Page 74: HTML Canvas Guide

Compositing ModesThe canvas element has eleven built-in compositing modes, including the default mode, and support forvendor-specific extensions. You set a compositing mode by setting the context’s globalCompositeOperationproperty to one of these values:

● "source-over" (default)

● "source-atop"

● "source-in"

● "source-out"

● "destination-over"

● "destination-atop"

● "destination-in"

● "destination-out"

● "lighter"

● "copy"

● "xor"

● “vendorName-operationName”

Example: ctx.globalCompositeOperation = "copy";

Compositing deals with a source (the image being drawn), and a destination (the canvas and anything alreadyon it).

Source and destination pixels can be opaque (alpha = 1), transparent (alpha = 0), or translucent (0 < alpha <1).

The result of the composition can be to display the source pixel, the destination pixel, a transparent pixel (eraseboth source and destination to transparent), or a combination pixel, which uses values from the source anddestination, combined using an algorithm.

Advanced CompositingCompositing Modes

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

74

Page 75: HTML Canvas Guide

A detailed description of the composition modes follows.

● source-over—The default value for compositing. Display the source image wherever the source image isopaque. Display the destination image wherever the source is transparent. Display a blend of source anddestination wherever the source is translucent (0 < alpha < 1).

● source-atop—Display the source image wherever both images are opaque. Display a blend wherever thesource is translucent and the destination is either translucent or opaque. Display the destination imagewherever the source image is transparent. Display transparency where the destination is transparent.

● source-in—Display the source image wherever both the source image and destination image are opaque.Display a blend wherever the source and destination are both translucent. Display transparency wherevereither the source or destination are transparent.

● source-out—Display the source image wherever the source image is opaque and the destination imageis transparent. Display a blend wherever both the source and destination are translucent. Displaytransparency elsewhere.

● destination-over—Display the destination image wherever the destination image is opaque. Display thesource image wherever the destination is transparent. Display a blend of source and destination whereverthe destination is translucent.

● destination-atop—Display the destination image wherever both images are opaque. Display a blendwherever the destination is translucent and the source is either translucent or opaque. Display the sourceimage wherever the destination image is transparent.

● destination-in—Display the destination image wherever both the destination image and source imageare opaque. Display a blend wherever the destination and source are both translucent. Display transparencywherever either the destination or source are transparent.

● destination-out—Display the destination image wherever the destination image is opaque and the sourceimage is transparent. Display a blend wherever both the destination and source are translucent. Displaytransparency elsewhere.

● lighter—Display an image in which the RGBa color values of source and destination are summed, with amaximum RGB value of 255 and a maximum alpha value of 1.

● copy—Display the source image wherever it is opaque or translucent. Display the destination image onlywhere the source image is transparent.

● xor—Display the result of a bitwise exclusive-OR operation between the source and destination pixels.This operation is reversible—drawing the source a second time using xor composition restores thedestination exactly.

Advanced CompositingCompositing Modes

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

75

Page 76: HTML Canvas Guide

It’s easy to generate charts and graphs from data using the canvas element. The main difficulty is scaling thedata to fit neatly on the page. Graphs are usually presented with the y-axis going up, not down, and they-coordinates are scaled to fit the data, so you need a way to translate from the data value to a canvasy-coordinate.

There are three common solutions for resolving y-coordinates for graphs and charts:

● Use the transformation matrix to flip and scale the canvas’s y-axis.

● Write a routine that calculates a y-coordinate from the data.

● Use a data visualization JavaScript library.

This chapter shows you how to use the transformation matrix for graphing and how to calculate a y-coordinatefor a data point on a graph. If you’d prefer to use a JavaScript library, a web search for “canvas JavaScriptlibraries” or “canvas JavaScript libraries data visualization” will turn up a current list of libraries.

This chapter shows you how to create data plots, bar graphs, and pie charts.

Scaling Your DataA graph or chart normally contains lines or bars plotted against a grid. There is often text or artwork on thechart as well, including a vertical scale along the left edge.

You can use a single canvas element for the whole chart, or you can use a canvas element just as a grid forthe data, surrounding the grid with text and artwork using HTML and CSS, or positioning a canvas elementinside of another canvas element using CSS.

If you are using a canvas element primarily as a grid, the easy way to fit your data to the grid is to use thetransformation matrix. The transformation matrix can flip the y-axis, scale the y-axis so the vertical display areaequals your data range, and translate the y-axis zero point to the zero point for your data. Then your data valueis the y-coordinate.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

76

Creating Charts and Graphs

Page 77: HTML Canvas Guide

If you include text or art on the canvas, you probably don’t want to transform the text or art using the matrix—itwould be upside down and stretched vertically. You can either render the art or text before you change thetransformation matrix, or you can save the context, graph the data, restore the context, and draw your text orart.

If you draw art or text on the canvas, positioned relative to the data, you need to use JavaScript to calculatethe appropriate y-coordinates.

If your data needs to be plotted against a log scale instead of a linear scale, the transformation matrix won’tbe much help—the matrix is for linear transforms. You need to use JavaScript to calculate the propery-coordinates for your data.

Scaling Using the Transformation MatrixTo scale the y-axis to fit your data using the transformation matrix, you need to know the minimum andmaximum data values of your grid and the size of any header or footer area of the canvas that isn’t used forgraphing.

Listing 10-1 performs the transformation to scale the y-axis and position the zero point correctly, once you setthe variables.

Listing 10-1 Scaling by matrix

var can;

var ctx;

var maxVal;

var minVal;

var topMargin;

var botMargin;

var leftMargin;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

}

function transformYAxis() {

var displayHeight = can.height - topMargin - botMargin;

var yScalar = displayHeight / (maxVal - minVal);

//translate to 0,0 point on data graph

Creating Charts and GraphsScaling Your Data

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

77

Page 78: HTML Canvas Guide

ctx.translate(leftMargin,can.height + minVal * yScalar);

// scale canvas to match data graph and flip y-axis

ctx.scale(1,-1 * yScalar);

}

Scaling Using a FunctionTo calculate a canvas y-coordinate from a data value, you need to know the maximum and minimum valueson your graph and the height of any header and footer areas of the canvas that won’t be used for graphing.

Listing 10-2 sets the value of y from a given data value, once you fill in the maximum and minimum data valuesand header and footer heights.

Listing 10-2 Creating a scaling function

// set these four values

var maxVal;

var minVal;

var topMargin;

var botMargin;

//

var can;

var ctx;

var displayHeight;

var yScalar;

var bottom;

var y;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

displayHeight = can.height - topMargin - botMargin;

yScalar = displayHeight / (maxVal - minVal);

bottom = can.height - botMargin;

}

function calcY(value) {

Creating Charts and GraphsScaling Your Data

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

78

Page 79: HTML Canvas Guide

y = bottom - value * yScalar + yScalar * minVal;

}

Data PlotsA data plot is a graph showing your sample data plotted on a grid. If you have more than one data set, thedata sets are usually color coded. A sample data plot is shown in Figure 10-1.

Figure 10-1 Sample data plot

The advantage to using canvas for data plots, instead of drawing them using a graphics tool, is that you canupdate the artwork just by refreshing the data that it illustrates. This is especially useful for graphing rapidlychanging data, real-time data, or user-entered data.

A template for data plots is provided in Listing 10-3. All you have to do is supply the data and set a few variables,such as the minimum and maximum sample values, the number of samples, and any text for the columnheaders.

The example scales the canvas vertically to the range of sample values, and horizontally to the number ofsamples. To plot a given sample number x , of value y , you can simply call lineTo(x,y). The y-axis is scaledto -1 times the scalar, so the y-coordinate increases as you move up the graph.

Creating Charts and GraphsData Plots

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

79

Page 80: HTML Canvas Guide

Listing 10-3 Data plot template

<html>

<head>

<title>Plotting Data</title>

<script type="text/javascript">

var can;

var ctx;

var maxVal;

var minVal;

var numSamples;

var xScalar;

var yScalar;

// data sets -- set literally or obtain from a file or .asp call

var sanDiego = [72, 70, 74, 72, 75, 76, 77, 78, 74, 72, 70, 68];

var kansasCty = [20, 30, 40, 50, 60, 70, 80, 90, 70, 60, 50, 40];

var buffalo = [-10, -20, 0, 50, 50, 60, 90, 100, 50, 40, 30, 0];

function init() {

// set these values for your data

numSamples = 12;

maxVal = 120;

minVal = -30;

var stepSize = 10;

var colHead = 50;

var rowHead = 50;

var margin = 5;

var header = [" ", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",

"Sep", "Oct", "Nov", "Dec"]

//

can = document.getElementById("can");

ctx = can.getContext("2d");

ctx.fillStyle = "black"

ctx.font = "14pt Helvetica"

Creating Charts and GraphsData Plots

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

80

Page 81: HTML Canvas Guide

// set vertical scalar to available height / data points

yScalar = (can.height - colHead - margin) / (maxVal - minVal);

// set horizontal scalar to available width / number of samples

xScalar = (can.width - rowHead) / numSamples;

ctx.strokeStyle="rgba(128,128,255, 0.5)"; // light blue lines

ctx.beginPath();

// print column header and draw vertical grid lines

for (i=1;i<=numSamples;i++) {

var x = i * xScalar;

ctx.fillText(header[i], x,colHead - margin);

ctx.moveTo(x, colHead);

ctx.lineTo(x, can.height - margin);

}

// print row header and draw horizontal grid lines

var count = 0;

for (scale=maxVal;scale>=minVal;scale = scale - stepSize) {

var y = colHead + (yScalar * count * stepSize);

ctx.fillText(scale, margin,y + margin);

ctx.moveTo(rowHead,y)

ctx.lineTo(can.width,y)

count++;

}

ctx.stroke();

// set a color and make one call to plotData()

// for each data set

ctx.strokeStyle="green";

plotData(sanDiego);

ctx.strokeStyle="red";

plotData(kansasCty);

ctx.strokeStyle="purple";

plotData(buffalo);

}

Creating Charts and GraphsData Plots

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

81

Page 82: HTML Canvas Guide

function plotData(dataSet) {

ctx.beginPath();

ctx.moveTo(0, dataSet[0]);

for (i=1;i<numSamples;i++) {

ctx.lineTo(i * xScalar, dataSet[i]);

}

ctx.stroke();

}

</script>

</head>

<body onload="init()">

<div align="center">

<h2>Average Temperature By City</h2>

<canvas id="can" height="400" width="650">

</canvas>

<br>

<!-- identify your data sets -->

<span style="color:green"> San Diego = green </span> &nbsp;

<span style="color:red"> Kansas City = red </span> &nbsp;

<span style="color:purple"> Buffalo = purple </span>

</div>

</body>

</html>

Bar GraphsBar graphs are similar to data plots, but each sample is graphed as a rectangle scaled to the height or widthof the sample.

Creating Charts and GraphsBar Graphs

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

82

Page 83: HTML Canvas Guide

The example in Listing 10-4 (page 83) graphs data as vertical bars with a text label floating over each bar, asshown in Figure 10-2.

Figure 10-2 Sample bar graph

Listing 10-4 Bar graph template

<html>

<head>

<title>Bar Graph</title>

<script type="text/javascript">

var can;

var ctx;

var maxVal;

var minVal;

var numSamples;

var xScalar;

var yScalar;

var y;

// data sets -- set literally or obtain from a file or .asp call

var dataName = [ "Human", "Chimp", "Dolphin", "Cat" ];

var dataValue = [ 11000, 6200, 5800, 300 ];

Creating Charts and GraphsBar Graphs

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

83

Page 84: HTML Canvas Guide

function init() {

// set these values for your data

numSamples = 4;

maxVal = 12000;

var stepSize = 1000;

var colHead = 50;

var rowHead = 60;

var margin = 10;

var header = "Millions"

//

can = document.getElementById("can");

ctx = can.getContext("2d");

ctx.fillStyle = "black"

yScalar = (can.height - colHead - margin) / (maxVal);

xScalar = (can.width - rowHead) / (numSamples + 1);

ctx.strokeStyle="rgba(128,128,255, 0.5)"; // light blue line

ctx.beginPath();

// print column header

ctx.font = "14pt Helvetica"

ctx.fillText(header, 0,colHead - margin);

// print row header and draw horizontal grid lines

ctx.font = "12pt Helvetica"

var count = 0;

for (scale=maxVal;scale>=0;scale = scale - stepSize) {

y = colHead + (yScalar * count * stepSize);

ctx.fillText(scale, margin,y + margin);

ctx.moveTo(rowHead,y)

ctx.lineTo(can.width,y)

count++;

}

ctx.stroke();

// label samples

ctx.font = "14pt Helvetica";

ctx.textBaseline="bottom";

Creating Charts and GraphsBar Graphs

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

84

Page 85: HTML Canvas Guide

for (i=0;i<4;i++) {

calcY(dataValue[i]);

ctx.fillText(dataName[i], xScalar * (i+1),y - margin);

}

// set a color and a shadow

ctx.fillStyle="green";

ctx.shadowColor = 'rgba(128,128,128, 0.5)'

ctx.shadowOffsetX = 20;

ctx.shadowOffsetY = 1;

// translate to bottom of graph and scale x,y to match data

ctx.translate(0,can.height - margin);

ctx.scale(xScalar,-1 * yScalar);

// draw bars

for (i=0;i<4;i++) {

ctx.fillRect(i+1, 0, 0.5, dataValue[i]);

}

}

function calcY(value) {

y = can.height - value * yScalar;

}

</script>

</head>

<body onload="init()">

<div align="center">

<h2>Neurons in Cerebral Cortex</h2>

<canvas id="can" height="400" width="650">

</canvas>

</div>

</body>

</html>

Creating Charts and GraphsBar Graphs

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

85

Page 86: HTML Canvas Guide

Pie ChartsYou create a pie chart by treating each sample as a wedge of pie—add the samples together to get the sizeof the pie, determine the proportion of each slice, then render each portion as its part of a circle.

Use the arc(x,y, radius, startAngle, endAngle) method to draw the outside of each wedge of thepie.

Set x,y to the middle of the canvas, or wherever you want the center of your pie chart.

Set radius to no more than half the height or width of the canvas—less if you want to have room for labels.

For the first sample, startAngle can be any value—you can start anywhere you like on a circle. For subsequentsamples, startAngle equals the first startAngle plus the endAngle of all previous samples.

Set endAngle to the fraction of the circle represented by a given sample:

Math.PI * 2 * sample / total

Use lineTo(x,y) to connect the end of the arc to the center of the pie. Use closePath() to connect thecenter back to the start of the arc and complete the wedge.

Fill the wedge with a color using the fill() method. You can outline the shape in another color using thestroke() method.

Listing 10-5 draws a pie chart from an array of samples and an array of fill colors.

Listing 10-5 Drawing a pie chart

var oldAngle = 0;

var midX = can.width /2;

var midY = can.height /2;

var radius = midY;

// do for each sample:

for (i=0;i<numSamples;i++) {

// draw wedge

var portion = dataValue[i] / total;

var wedge = 2 * Math.PI * portion;

ctx.beginPath();

var angle = oldAngle + wedge;

ctx.arc(midX, midY, radius, oldAngle, angle);

Creating Charts and GraphsPie Charts

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

86

Page 87: HTML Canvas Guide

ctx.lineTo(midX, midY);

ctx.closePath();

ctx.fillStyle = fillColor[i];

ctx.fill(); // fill with wedge color

ctx.stroke(); // outline in black

oldAngle = oldAngle + wedge;

}

Labeling a pie chart is more art than science, but one approach is to label each sample with text outside thepie, aligned with the center of the wedge.

Positioning the text can be a bit tricky, as it depends on the height and width of the text—you don’t want torun into the pie or off the canvas. Listing 10-6 generates a series of pie charts from a menu of quarterly resultsand labels the samples as shown in Figure 10-3.

Figure 10-3 Sample pie chart

Listing 10-6 Pie chart generator

<html>

<head>

Creating Charts and GraphsPie Charts

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

87

Page 88: HTML Canvas Guide

<title>Pie Chart</title>

<script type="text/javascript">

var can;

var ctx;

var numSamples;

var xScalar;

var yScalar;

var radius;

var quarter;

// data sets -- set literally or obtain from a file or .asp call

var dataName = [ "East", "Midwest", "South", "West" ];

var q1Value = [ 1200000, 800000, 600000, 3000000 ];

var q2Value = [ 900000, 900000, 700000, 1800000 ];

var q3Value = [ 800000, 700000, 600000, 900000 ];

var fillColor = ["red", "blue", "green", "orange" ];

function init() {

// set this value for your data

numSamples = 4;

can = document.getElementById("can");

quarter = document.getElementById("quarter");

ctx = can.getContext("2d");

drawPie();

}

function drawPie() {

radius = can.height / 3;

var midX = can.width / 2;

var midY = can.height / 2;

ctx.strokeStyle = "black";

ctx.font = "18pt Helvetica";

ctx.textAlign = "center";

ctx.textBaseline = "middle";

Creating Charts and GraphsPie Charts

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

88

Page 89: HTML Canvas Guide

// get data set

var dataValue = q1Value;

if (quarter.value=="q2")

dataValue = q2Value;

if (quarter.value=="q3")

dataValue = q3Value;

// calculate total value of pie

var total = 0;

for (i=0;i<numSamples;i++) {

total = total + dataValue[i];

}

// get ready to draw

ctx.clearRect(0,0, can.width, can.height);

var oldAngle = 0;

// do for each sample:

for (i=0;i<numSamples;i++) {

// draw wedge

var portion = dataValue[i] / total;

var wedge = 2 * Math.PI * portion;

ctx.beginPath();

var angle = oldAngle + wedge;

ctx.arc(midX, midY, radius, oldAngle, angle);

ctx.lineTo(midX, midY);

ctx.closePath();

ctx.fillStyle = fillColor[i];

ctx.fill(); // fill with wedge color

ctx.stroke(); // outline in black

// print label

// set angle to middle of wedge

var labAngle = oldAngle + wedge / 2;

// set x,y for label outside center of wedge

// adjust for fact text is wider than it is tall

var labX = midX + Math.cos(labAngle) * radius * 1.5;

Creating Charts and GraphsPie Charts

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

89

Page 90: HTML Canvas Guide

var labY = midY + Math.sin(labAngle) * radius * 1.3 - 12;

// print name and value with black shadow

ctx.save();

ctx.shadowColor = "black";

ctx.shadowOffsetX = 1;

ctx.shadowOffsetY= -1;

ctx.fillStyle = fillColor[i];

ctx.fillText(dataName[i], labX, labY);

ctx.fillText("$" + dataValue[i], labX, labY + 25);

ctx.restore();

// update beginning angle for next wedge

oldAngle = oldAngle + wedge;

}

}

</script>

</head>

<body onload="init()">

<div align="center">

<h2>Sales by Region</h2>

<canvas id="can" height="400" width="500">

</canvas>

</div>

<br>

<select id="quarter" onchange="drawPie()" style="font:18pt Helvetica">

<option value="q1">Q1</option>

<option value="q2">Q2</option>

<option value="q3">Q3</option>

</select>

</body>

</html>

Creating Charts and GraphsPie Charts

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

90

Page 91: HTML Canvas Guide

Interactive Data Visualization and AnimationStudents and the general public find science more interesting when data is presented visually, especially whenit changes in response to user input. You can easily add user interaction to a graph by adding a few <input>elements to allow users to change variable values.

By adding a value that changes over time, you can often turn a static graph into an animation with a line ortwo of code. Adding animation and user input makes a graph much more engaging.

The example in Listing 10-7 (page 91) graphs three sine waves, then graphs the combination of the threewaves to illustrate frequency addition, as shown in Figure 10-4.

Figure 10-4 Frequency addition

Adding a few buttons makes the graph interactive by allowing the user to change the frequency and phaseof the sine waves and see the result.

Adding a global phase variable and incrementing it repeatedly, then redrawing the waves, turns the graphsof the waves into tiny oscilloscopes, transforming a static image into an animation that grabs the eye. For moreabout animation, see “Animating the Canvas” (page 99).

Listing 10-7 Performing interactive frequency addition

<!doctype html>

<html>

<head>

<title>Frequency Addition</title>

Creating Charts and GraphsInteractive Data Visualization and Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

91

Page 92: HTML Canvas Guide

<script type="text/javascript">

var canvas = [];

var ctx = [];

var cHeight = 50;

var canvasWidth = 180;

var canvasHeight = 150;

var aCircle = 2 * Math.PI;

var ninetyDeg = 0.5 * Math.PI;

var sixDeg = Math.PI / 30;

// vertical scale for 3 sine waves above and below x-axis without hitting edges

var vScale = (canvasHeight / 6) - 2;

var freq = [];

var color = [];

var phase = [];

var globalPhase = 0;

var label = [];

function init() {

color[0]="red";

color[1]="green";

color[2]="blue";

color[3]="white";

label[0] = document.getElementById("label0");

label[1] = document.getElementById("label1");

label[2] = document.getElementById("label2");

canvas[0] = document.getElementById("canvas0");

canvas[1] = document.getElementById("canvas1");

Creating Charts and GraphsInteractive Data Visualization and Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

92

Page 93: HTML Canvas Guide

canvas[2] = document.getElementById("canvas2");

canvas[3] = document.getElementById("canvas3");

for (i=0;i<4;i++) {

ctx[i] = canvas[i].getContext("2d");

ctx[i].fillStyle="black";

ctx[i].strokeStyle=color[i];

ctx[i].lineWidth=2;

}

freq[0]=1;

freq[1]=2;

freq[2]=3;

phase[0]=0;

phase[1]=0;

phase[2]=0;

labelWaves();

setInterval("animate()", 40);

}

function labelWaves() {

for (i=0;i<3;i++) {

var phaseDeg = parseInt(phase[i] / Math.PI * 180);

var labelString = 'Frequency: ' + freq[i] + ' &nbsp; Phase: ' + phaseDeg+ '&deg;';

label[i].innerHTML = labelString;

}

}

function animate() {

globalPhase = globalPhase + sixDeg;

drawSinWave(0);

drawSinWave(1);

drawSinWave(2);

drawAllWaves();

}

Creating Charts and GraphsInteractive Data Visualization and Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

93

Page 94: HTML Canvas Guide

function drawSinWave (index) {

var thisCtx = ctx[index];

// clear to black

thisCtx.fillRect(0,0,canvasWidth,cHeight);

// draw X axis

var xAxis = cHeight / 2;

thisCtx.beginPath();

thisCtx.moveTo(canvasWidth,xAxis);

thisCtx.lineTo(0,xAxis);

// plot graph of sine wave

var xCoord=0;

var steps=canvasWidth / freq[index];

for (i=0; i<freq[index]; i++) {

for (j=0; j<=steps; j++) {

var xCoord = i*steps+j;

var radians= (aCircle / steps) * j + phase[index] + (globalPhase *freq[index]);

var sinY= Math.sin(radians);

var yCoord= sinY * vScale + xAxis;

thisCtx.lineTo(xCoord,yCoord);

}

}

thisCtx.stroke();

}

function drawAllWaves() {

var thisCtx = ctx[3];

thisCtx.fillRect(0,0,canvasWidth,canvasHeight);

// draw X axis

thisCtx.beginPath();

var xAxis = canvasHeight / 2;

thisCtx.moveTo(canvasWidth,xAxis);

thisCtx.lineTo(0,xAxis);

// plot graph of all waves added together

Creating Charts and GraphsInteractive Data Visualization and Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

94

Page 95: HTML Canvas Guide

var xCoord=0;

for (i=0;i<canvasWidth;i++) {

var xCoord = i;

var yCoord = 0;

for (j=0;j<3;j++) {

var steps = canvasWidth/freq[j];

var radians = (aCircle / steps) * i + phase[j] + (globalPhase * freq[j]);

var sinY = Math.sin(radians);

yCoord = yCoord + sinY;

}

yCoord = yCoord * vScale + xAxis;

thisCtx.lineTo(xCoord,yCoord);

}

thisCtx.stroke();

}

function increment(index) {

freq[index]++;

drawSinWave(index);

labelWaves();

}

function decrement(index) {

freq[index]--;

if (freq[index] < 0)

freq[index] = 0;

drawSinWave(index);

labelWaves();

}

function addPhase(index) {

var thePhase = phase[index] + ninetyDeg;

if (parseInt(thePhase) == 6)

thePhase = 0;

Creating Charts and GraphsInteractive Data Visualization and Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

95

Page 96: HTML Canvas Guide

phase[index] = thePhase;

labelWaves();

drawSinWave(index);

}

</script>

</head>

<body onload="init()">

<P>

<EM>

The canvas element is well-suited to display scientific or numeric data, especiallyinteractive data.

</EM>

<H1>Frequency Addition</H1>

<div id="main"

style="border: 5px inset #80e080;

width:480px;" >

<div id="waves" style="margin: 5px; width: 200;">

<B>Wave 1</B><BR>

<canvas id="canvas0" width="180" height="50">

test

</canvas>

<BR>

<B>Wave 2</B><BR>

<canvas id="canvas1" width="180" height="50">

</canvas>

<BR>

<B>Wave 3</B><BR>

<canvas id="canvas2" width="180" height="50">

Creating Charts and GraphsInteractive Data Visualization and Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

96

Page 97: HTML Canvas Guide

</canvas>

<div>

<br><B>Addition of waves 1, 2, and 3</B><BR>

<canvas id="canvas3" width="180" height="150">

</canvas>

</div>

</div>

<div id="controls"

style="width: 120;

margin: 5px;

position:absolute; left:200px; top:118px;">

<p id="label0">Freq: 1 Phase: 0</p>

<input type="button" value=" ^ " onclick="increment(0)">

<input type="button" value=" v " onclick="decrement(0)">

<input type="button" value="+90&deg;" onclick="addPhase(0)">

<P id="label1">Freq: 2 Phase: 0</P>

<input type="button" value=" ^ " onclick="increment(1)">

<input type="button" value=" v " onclick="decrement(1)">

<input type="button" value="+90&deg;" onclick="addPhase(1)">

<P id="label2">Freq: 3 Phase: 0</P>

<input type="button" value=" ^ " onclick="increment(2)">

<input type="button" value=" v " onclick="decrement(2)">

<input type="button" value="+90&deg;" onclick="addPhase(2)">

</div>

</div>

<P>

Complex waveforms can be made by adding simple sine waves.

Creating Charts and GraphsInteractive Data Visualization and Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

97

Page 98: HTML Canvas Guide

<P>

Increase or decrease the frequencies and increment the phase of the component sinewaves to see how they add together.

</body>

</html>

Creating Charts and GraphsInteractive Data Visualization and Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

98

Page 99: HTML Canvas Guide

You can use canvas for animations as well as static images. Animation is a series of graphic images shown oneafter another, the same way that video is a series of still images. If the sequential images are similar to oneanother, and the time between images is short, the eye is fooled into seeing continuous motion.

You can easily use the canvas element to produce images that differ from each other slightly, and the canvaselement is designed to support smooth, rapid changes between images.

The JavaScript methods setTimeout() and setInterval() can be used to redraw the canvas at preciserepeating intervals.

The Animation SequenceAnimation involves repeatedly clearing the canvas and drawing an image on it. To create smooth animations,you need to minimize the time between clearing the canvas and completing the new drawing, and you needto keep the changes relatively small from image to image.

You can use a sequence of predrawn images to change the appearance of some elements from frame to frame,substituting one image for another. You can also change the rotation, scale, or position of a predrawn image,or of a path or shape.

For smooth animation, use the following sequence:

1. Model—Calculate small changes in image substitution, position, rotation, color, or scale.

2. Clear—Clear part or all of the canvas.

3. Draw—Draw any images, using the precalculated values. Stroke or fill any paths, shapes, or strings.

4. Repeat steps 1-3.

If you have an opaque background image or shape that fills the canvas, or the part of the canvas you areanimating, you can skip step 2. Drawing the background has the effect of “clearing” the canvas to yourbackground.

As with static images, you should draw the elements in order, from backmost to frontmost, so your foregroundimages are superimposed on any background.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

99

Animating the Canvas

Page 100: HTML Canvas Guide

When using predrawn images in the foreground, use SVG images or PNG images on transparent backgrounds,to allow automatic alpha-channel compositing.

When changing the scale or rotation of an element, save the context by calling save(), change the scale orrotation of the canvas, translating the canvas as needed, draw the element, then restore the context by callingrestore().

Use setTimeout() for animations that repeat conditionally, and setInterval() for animations that repeatindefinitely.

For examples, see “A Simple Animation” (page 102) and “Intermittent Animation” (page 107).

Animation TimingAnimation is smoothest when you make the changes between frames as incremental as possible, and the timebetween frames as short as possible.

Try not to move objects more than two or three pixels per frame. This means keeping incremental values suchas gravity or thrust quite small, to prevent them from accumulating and resulting in jerky movement.

Try to keep changes in rotation down to 0.1 radians per frame. Less is better.

Changes in scale should be kept to 0.2 or less per frame.

If making small changes results in slow animation, you can decrease the time between cycles.

A timeout value of 40 ms results in a refresh rate of 25 fps. This is a good minimum refresh rate—the same asPAL television and a touch faster than cinema’s 24 fps. A timeout value of 20 ms yields a frame rate of 50 fps,which should be fast enough for any animation.

Important Be careful when using small timeout values with setInterval(). If it takes longer to executeyour animation than the timeout value, operations begin to accumulate on the command stack—youranimation will gradually slow down and eventually freeze.

Be aware that it takes code longer to execute on handheld devices than it does on a desktop system, soleave a margin for error and test your website on all the devices it is targeted for.

You can see exactly how long your animation function takes to execute on the desktop by creating a JavaScriptprofile. To profile your script, follow these steps.

1. Enable the Developer menu in Safari preferences.

2. Choose Develop > Show Web Inspector.

Animating the CanvasAnimation Timing

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

100

Page 101: HTML Canvas Guide

3. Click the Profiles icon and enable profiling when prompted.

4. With your website loaded and your script running, click the record button (the black circle on the lowerleft), wait a second, and click it again. This generates a profile.

5. Click the icon of the profile.

6. Choose Tree view, and reveal the operations to find your animate() function.

7. Toggle the view from % to ms.

You should see a profile similar to the one shown in Figure 11-1.

Figure 11-1 Animation profile

The total time spent in your animate() function and all its subsidiary calls is shown in the Total column. Thenumber of calls is shown in the Calls column. Divide the total by the number of calls to get the timing for asingle call. (The Average column shows the average for the animate() function alone, with none of itssubsidiary calls.)

If your animation function takes longer to execute than the timeout value, you have a bug. If the values areclose, you may have a problem on handheld devices.

Animating the CanvasAnimation Timing

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

101

Page 102: HTML Canvas Guide

Note To learn more about debugging and profiling JavaScript using Safari’s built-in tools, see SafariDeveloper Tools Guide .

One way to avoid timing problems is to use setTimeout() instead of setInterval(), and set the timeoutas the last step in your animate() function. This guarantees that your animation function is completed beforebeing called again.

Using setTimeout() can result in uneven timing, however, especially if your modeling code has long andshort branches. Using setInterval() guarantees the same frame rate for all code branches, on all devices,but you must allow enough time for the longest branch to execute on the slowest device.

A Simple AnimationListing 11-1 (page 103), generates a continuous animation of a bouncing soccer ball, as shown in Figure11-2 (page 102).

Figure 11-2 Animated soccer ball

The soccer ball rotates as it falls, and bounces from the bottom and sides of the canvas. The rotation directionchanges when the ball bounces off the side.

The canvas has a clear background, allowing the webpage background to show through. The canvas elementis positioned using CSS to overlap the heading in the <h2> tag, so the ball covers part of the heading at times.

Animating the CanvasA Simple Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

102

Page 103: HTML Canvas Guide

During the model() step, an x-increment is added to the x position, and a y-increment is added to the yposition. A gravity constant is added to the y-increment, so the ball realistically moves faster as it drops andmoves slower as it bounces up, eventually stopping and falling again.

The model() step also checks for a bounce condition, and reverses the x or y increment if needed.

Because the animation is based on a model, instead of a canned series of images, it does not simply repeat—theball follows a slightly different path each time it bounces.

During the draw() step, the canvas is translated to the ball’s x,y-coordinate and rotated. The ball is then drawnwith its center at 0,0.

There is a -75 pixel offset from the upper-left corner of the ball image to the center of the ball. This pixel offsetis stored in a variable named centerOffset, and the image of the ball is drawn at centerOffset,centerOffset to put the ball’s center at 0,0.

Listing 11-1 Creating a simple animation

<html>

<head>

<title>Simple Animation</title>

<script type="text/javascript">

var can;

var ctx;

var ball;

var x;

var y;

var xVec;

var yVec;

var direc;

var rot = 0;

var gravity = 1;

var left=75;

var right=525;

var bottom=325;

var interval;

var centerOffset = -75;

Animating the CanvasA Simple Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

103

Page 104: HTML Canvas Guide

function init() {

ball = document.getElementById("ball");

can = document.getElementById("can");

ctx = can.getContext("2d");

ctx.strokeStyle="black";

// initialize position, speed, spin direction

x = 98;

y = 75;

xVec = 5.5;

yVec = 0;

direc = 1;

// draw lines for the ball to bounce off of

ctx.moveTo(0,bottom + 75);

ctx.lineTo(600,bottom+ 75);

ctx.lineTo(600,0)

ctx.stroke();

// set animation to repeat every 40 ms

interval = setInterval("animate()",40);

}

function animate() {

model();

// clear canvas except for lines at edge

ctx.clearRect(0,0,can.width -1 ,can.height -1);

draw();

}

function model() {

rot = rot + .1 * direc;

x = x + xVec;

yVec = yVec + gravity;

y = y + yVec;

bounceIf();

}

Animating the CanvasA Simple Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

104

Page 105: HTML Canvas Guide

function bounceIf() {

if (y >= bottom) {

y = bottom;

yVec = -1 * yVec - gravity

}

if (x >= right || x <= left) {

xVec = -1 * xVec;

direc = -1 * direc;

}

}

function draw() {

ctx.save();

ctx.translate(x,y);

ctx.rotate(rot);

ctx.drawImage(ball, centerOffset,centerOffset);

ctx.restore();

}

</script>

</head>

<body onload="init()" style="background-color:#e0e0e0">

<h2>Simple Animation</h2>

<img id="ball"

src="http://homepage.mac.com/qt4web/soccerball1.png"

style="display:none">

<canvas id="can" height="400" width="600"

style="position:relative;top:-50">

</canvas>

</body>

</html>

Animating the CanvasA Simple Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

105

Page 106: HTML Canvas Guide

Adding a GradientYou can overlay runtime-generated shapes on predrawn images. Let’s take the previous example and overlaythe soccer ball with a shape, filled with a gradient. This allows you to make the ball any color you like and adda lighting effect at the same time, as shown in Figure 11-3.

Figure 11-3 Animation with gradient fill

The following snippet defines a radial gradient the size of the soccer ball that goes from white at 80% opacityat the center to blue at 30% opacity at the outer edge.

var grad;

function makeGradient() {

grad = ctx.createRadialGradient(0,0,10,0,0,75);

grad.addColorStop(0, 'rgba(255,255,255,0.8)');

grad.addColorStop(1, 'rgba(0,0,255,0.3)');

}

This next snippet redefines draw() to draw a circle filled with the gradient on top of the soccer ball (the radiusof the circle is 73 pixels).

var twoPi = Math.PI * 2;

function draw() {

ctx.save();

ctx.translate(x,y);

Animating the CanvasAdding a Gradient

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

106

Page 107: HTML Canvas Guide

ctx.rotate(rot);

ctx.drawImage(ball, centerOffset,centerOffset);

ctx.fillStyle = grad;

ctx.beginPath();

ctx.arc(0,0, 73, 0, twoPi);

ctx.closePath;

ctx.fill();

ctx.restore();

}

To add the gradient-filled overlay to the example in Listing 11-1 (page 103), paste in the first snippet, add a callto makeGradient() in the init() function, and replace the draw() function with the second snippet.

Intermittent AnimationListing 11-2 animates a butterfly whose wings flap intermittently. The animation is achieved by substitutingimages of a butterfly with wings in different positions. The example uses shadows and rotation to make theanimation more realistic. The output is shown in Figure 11-4.

Figure 11-4 Flapping butterfly

Animating the CanvasIntermittent Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

107

Page 108: HTML Canvas Guide

Because the animation is intermittent, setTimeout() is used instead of setInterval(). A short timeoutof 40 ms is used during a flap, and a longer, random interval is used between flaps.

Listing 11-2 Creating an intermittent animation

<html><head>

<title>Butterfly</title>

<script type="text/javascript">

var can;

var ctx;

var sprite = new Array();

var counter=0;

var rot = 0;

var centerX = -200;

var centerY = -148;

function init() {

can=document.getElementById("can");

ctx=can.getContext("2d");

// get images into array

sprite[0]=document.getElementById("b1");

sprite[1]=document.getElementById("b2");

sprite[2]=document.getElementById("b3");

sprite[3]=document.getElementById("b4");

// set shadow color, offset, blur

ctx.shadowColor="rgba(80,80,80,.3)";

ctx.shadowBlur="5";

ctx.shadowOffsetX="10";

ctx.shadowYOffsetY="20";

// draw butterfly

draw();

// wait 3 sec, then begin animation

var t= setTimeout("flap()", 3000);

}

Animating the CanvasIntermittent Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

108

Page 109: HTML Canvas Guide

function flap() {

var wait = 40;

counter++;

if (counter==4) {

counter=0;

wait=wait + Math.random() * 1500;

rot=rot+.01;

}

draw();

setTimeout("flap()", wait);

}

function draw() {

ctx.clearRect(0,0, can.width,can.height);

ctx.save();

ctx.translate(can.width / 2,can.height / 2);

ctx.rotate(rot);

ctx.drawImage(sprite[counter],centerX,centerY);

ctx.restore();

}

</script>

</head>

<body onload="init()">

<h1>Butterfly</h1>

<P>Why are they called butterflies?</P>

<P>Shouldn't they be flutter-bys?</P>

<h2>but-ter-fly</h2>

<P><i>--noun</i></p>

<ol>

<li>any of numerous diurnal insects of the order Lepidoptera, characterized byclubbed antennae, a slender body, and large, broad, often conspicuously markedwings.

</li>

Animating the CanvasIntermittent Animation

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

109

Page 110: HTML Canvas Guide

<li>a person who flits aimlessly from one interest or group to another: a socialbutterfly.

</li>

<li>butterflies, ( used with a plural verb ) Informal . a queasy feeling, as fromnervousness, excitement, etc.

</li>

</ol>

<img id="b1" style="display:none"

src="http://homepage.mac.com/qt4web/butterfly/butterfly1.png" >

<img id="b2" style="display:none"

src="http://homepage.mac.com/qt4web/butterfly/butterfly2.png" >

<img id="b3" style="display:none"

src="http://homepage.mac.com/qt4web/butterfly/butterfly3.png" >

<img id="b4" style="display:none"

src="http://homepage.mac.com/qt4web/butterfly/butterfly4.png" >

<canvas id="can" height="450" width="450"

style="position:absolute;left:0;top:225">

</canvas>

</body>

</html>

Panning BackgroundSometimes you want to animate the background. To create a panning background, start by creating an imagethat is wider than the canvas, and whose left edge and right edge are very similar. Then follow these steps:

1. Draw the image at an ever-decreasing x-coordinate.

This pans the image to the left.

2. When the x-coordinate is less than the image width minus the canvas width, draw a second copy of theimage at x + the image width.

This draws a new copy with the left edge flush with the old copy’s right edge.

3. When the x-coordinate is less than the negative image width, add the image width to the x-coordinate.

The first copy has scrolled off the screen, so reset the x-coordinate and start over.

Animating the CanvasPanning Background

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

110

Page 111: HTML Canvas Guide

Simple Panning BackgroundThe example in Listing 11-3 creates a background image that pans smoothly across the canvas, as illustratedin Figure 11-5.

Figure 11-5 Panning background

Listing 11-3 Adding a panning background

<html>

<head>

<title>Simple Panning Background</title>

<script type="text/javascript">

var can;

var ctx;

var back;

var x;

var imgWidth = 1498;

function init() {

back = document.getElementById("back");

can = document.getElementById("can");

Animating the CanvasPanning Background

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

111

Page 112: HTML Canvas Guide

ctx = can.getContext("2d");

x = 0;

animate();

}

function animate() {

x = x - 1;

drawBack();

setTimeout("animate()",25);

}

function drawBack() {

ctx.drawImage(back, x,0);

if (x < -1 * (imgWidth - can.width))

ctx.drawImage(back, x + imgWidth,0);

if (x<= -1 * imgWidth)

x = x + imgWidth;

}

</script>

</head>

<body onload="init()" >

<h2>Simple Panning Background</h2>

<canvas id="can" height="500" width="500" >

</canvas>

<img id="back" style="display:none"

src="http://homepage.mac.com/qt4web/landscape.png">

</body>

</html>

Animating the CanvasPanning Background

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

112

Page 113: HTML Canvas Guide

Layered Panning BackgroundA simple panning background tends to have a flat look to it, because we expect nearby things to move fasterwhen panning. The example in Listing 11-4 creates a more realistic panning background by dividing thebackground image into three parts—the sky, the landscape, and the guardrail in the foreground—and panningeach part at a different speed.

To support three layers of background, the image, the x increment, and the x and y coordinates are made intoarrays, and the background drawing function is made into a loop that cycles through all three layers.

A line of text is added to illustrate an image floating over a panning background, as shown in Figure 11-6.

Figure 11-6 Layered background

Listing 11-4 Adding a layered panning background

<html>

<head>

<title>Layered Panning Background</title>

<script type="text/javascript">

var can;

var ctx;

var back = new Array;

Animating the CanvasPanning Background

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

113

Page 114: HTML Canvas Guide

var x = [0, 0, 0];

var y = [ 0, -2, 376];var xIncr = [0.3, 1, 4];

var imgWidth = 1498;

function init() {

back[0] = document.getElementById("sky");

back[1] = document.getElementById("back");

back[2] = document.getElementById("front");

can = document.getElementById("can");

ctx = can.getContext("2d");

animate();

}

function animate() {

drawBack();

setTimeout("animate()",25);

}

function drawBack() {

for (i=0;i<3;i++) {

x[i] = x[i] - xIncr[i];

ctx.drawImage(back[i], x[i],y[i]);

if (x[i] < -1 * (imgWidth - can.width))

ctx.drawImage(back[i], x[i] + imgWidth,y[i]);

if (x[i]<= -1 * imgWidth)

x[i] = x[i] + imgWidth;

}

}

</script>

</head>

<body onload="init()" >

<canvas id="can" height="500" width="600" >

</canvas>

Animating the CanvasPanning Background

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

114

Page 115: HTML Canvas Guide

<h1 style="position:relative; top:-300; left: 125; color:#ffffff">

Grand Canyon Vacation

</h2>

<img id="sky" style="display:none"

src="http://homepage.mac.com/qt4web/sky.png">

<img id="back" style="display:none"

src="http://homepage.mac.com/qt4web/landscape2.png">

<img id="front" style="display:none"

src="http://homepage.mac.com/qt4web/foreground.png">

</body>

</html>

Animating the CanvasPanning Background

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

115

Page 116: HTML Canvas Guide

Because the canvas is an HTML element, you can use CSS styles to modify its position, assign it a backgroundcolor or image, add a border, and so on.

In Safari and other WebKit-based browsers, you can use WebKit transitions to smoothly animate changes inCSS properties.

Because the canvas can have a transparent background, you can use CSS to create animated graphics thatroam freely across the webpage.

Assigning a Border and BackgroundThe example in Listing 12-1 uses CSS to assign a background image and a border to the canvas element, asillustrated in Figure 12-1.

A CSS background does not appear in the canvas bitmap, so it does not interfere with image processing.

The clearRect(x,y, width,height)method clears a section of the canvas, revealing the CSS background,allowing you to use a background image and clear small areas of the canvas quickly, without redrawing thebackground image.

Figure 12-1 CSS bulletin board

Listing 12-1 Adding a CSS background and border

<html>

<head>

<title>Background Image and Border</title>

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

116

Modifying the Canvas with CSS

Page 117: HTML Canvas Guide

<style>

canvas {

background-image:url('http://homepage.mac.com/qt4web/cork.png');

border: 10px inset brown;

}

</style>

<script type="text/javascript">

function init() {

var can = document.getElementById("can");

var ctx = can.getContext("2d");

ctx.fillStyle = "white";

ctx.fillRect(25,25,120,100);

ctx.font = "24pt Helvetica";

ctx.fillStyle = "black";

ctx.fillText("Notice:", 30,55);

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="200" width="400">

</canvas>

</body>

</html>

A Pop-Up Canvas—Animating Position and OpacityYou can make the canvas element into a pop-up that responds to a hover, click, or rollover event on anotherwebpage element.

Modifying the Canvas with CSSA Pop-Up Canvas—Animating Position and Opacity

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

117

Page 118: HTML Canvas Guide

Begin by using CSS to position the canvas element offscreen or make it transparent. When the event occursthat triggers the pop-up, use CSS to change the canvas top, left, or opacity properties to make the canvasappear at the appropriate place on the page.

If you set the position and opacity properties as -webkit-transition properties, any changes areautomatically animated in Safari and other WebKit-based browsers. In non-WebKit-based browsers, the changestake place immediately and the canvas simply appears in the specified position.

The example in Listing 12-2 creates a canvas and uses CSS to position it offscreen and make it transparent bydefault. Touching or clicking and holding a particular text element on the page changes the class name of thecanvas. The CSS class definition makes the canvas visible and positions it on the page. A mouseup eventanywhere on the page changes the canvas’s class name to hide it again.

All CSS properties are set as webkit-transition properties for the canvas element, so the canvas fades inand out over 1 second in Safari. The canvas is given a CSS background image and a border, and animates abouncing ball, just for fun.

Figure 12-2 Animating a pop-up canvas

Listing 12-2 Making the canvas a pop-up

<html>

<head>

<title>Pop-up Canvas</title>

<meta name="viewport" content="width=device-width″ />

<style>

canvas {

background-image:url('http://homepage.mac.com/qt4web/cork.png');

Modifying the Canvas with CSSA Pop-Up Canvas—Animating Position and Opacity

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

118

Page 119: HTML Canvas Guide

border: 10px inset brown;

position:absolute;

top: -225;

left: 200;

-webkit-transition: all 1s;

}

.can-hide {

top: -225;

opacity: 0;

}

.can-pop {

top: 120;

opacity:1;

}

</style>

<script type="text/javascript">

var can;

var ctx;

var x = 25;

var y = 25;

var xdir = 1;

var ydir = 1;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

var grad = ctx.createLinearGradient(0,10, 0,190);

grad.addColorStop(0, 'brown');

grad.addColorStop(1, 'red');

ctx.fillStyle=grad;

animate();

}

Modifying the Canvas with CSSA Pop-Up Canvas—Animating Position and Opacity

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

119

Page 120: HTML Canvas Guide

function animate() {

x = x + xdir;

if (x > 375) xdir = -1;

if (x < 25) xdir = 1;

y = y + ydir;

if (y > 175) ydir = -1;

if (y < 25) ydir = 1;

ctx.clearRect(0,0, 400,200);

ctx.beginPath();

ctx.arc(x,y, 25, 0,2 * Math.PI);

ctx.closePath();

ctx.fill();

setTimeout("animate()", 25);

}

function pop() {

can.className="can-pop";

}

function unpop() {

can.className="can-hide";

}

</script>

</head>

<body onload="init()" onmouseup="unpop()">

<canvas class="can-hide" id="can" height="200" width="400">

</canvas>

<h1>

Click to view:

<h1>

Modifying the Canvas with CSSA Pop-Up Canvas—Animating Position and Opacity

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

120

Page 121: HTML Canvas Guide

<h1 onmousedown="pop()" ontouchstart="pop()" onclick="void()">

&nbsp;[x] Bulletin Board

</h1>

</body>

</html>

Note The HTML <h1> element is set to respond to either a mousedown or a touchstart event, soit responds to either a click or a touch.

The onclick="void()" attribute is added to alert iOS that the element should be consideredclickable, and should act as a target for touch events.

The onmouseup event is registered on the body element, so a mouseup or touchend event anywhereon the page releases the pop-up.

Free Range CanvasThe static webpage is a paradigm we’re all familiar with. Video, animated banners, and pop-ups notwithstanding,things are laid out in boxes, and they tend to stay in boxes.

Because the canvas element can have a transparent background, and because it supports alpha channelcompositing on the fly, it offers a way to visually break the static boxes paradigm.

When the canvas itself is animated using CSS, animated images on the canvas appear to range freely aboutthe page, covering or casting shadows on other elements.

Modifying the Canvas with CSSFree Range Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

121

Page 122: HTML Canvas Guide

The example in Listing 12-3 (page 122) creates a simple animated butterfly, then moves the canvas using CSSto allow the butterfly to wander across the page, as shown in Figure 12-3.

Figure 12-3 Butterfly

Listing 12-3 Making a free-roaming canvas

<html>

<head>

<title>Butterfly</title>

<script type="text/javascript">

var can;

var ctx;

var sprite = new Array();

var counter=0;

var rot = 0;

var centerX = -176;

var centerY = -127

var canX = 0;

var canY = 225;

Modifying the Canvas with CSSFree Range Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

122

Page 123: HTML Canvas Guide

function init() {

can=document.getElementById("can");

ctx=can.getContext("2d");

// get images into array

sprite[0]=document.getElementById("b1");

sprite[1]=document.getElementById("b2");

sprite[2]=document.getElementById("b3");

sprite[3]=document.getElementById("b4");

// set shadow color, offset, blur

ctx.shadowColor="rgba(80,80,80,.3)";

ctx.shadowBlur="5";

ctx.shadowOffsetX="10";

ctx.shadowYOffsetY="20";

// draw butterfly

draw();

// wait 3 sec, then begin animation

var t= setTimeout("flap()", 3000);

}

function flap() {

// flap wings four times quickly, then wait randomly up to 1.5sec

var wait = 40;

counter = counter + 1;

if (counter==4) {

counter=0;

wait=wait + Math.random() * 1500;

rot=rot+.01;

}

// draw butterfly, move canvas, repeat

draw();

moveCan();

setTimeout("flap()", wait);

}

Modifying the Canvas with CSSFree Range Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

123

Page 124: HTML Canvas Guide

function draw() {

// draw rotated: translate to x,y; rotate; draw at 0,0, offset to center

ctx.clearRect(0,0, can.width,can.height);

ctx.save();

ctx.translate(can.width / 2,can.height / 2);

ctx.rotate(rot);

ctx.drawImage(sprite[counter],centerX,centerY);

ctx.restore();

}

function moveCan() {

canX = canX + 1;

canY = canY - 2;

if (canY < -80)

canY = -80;

if (canX > 400)

canX = 400;

// use CSS style to move canvas

can.style.left = canX;

can.style.top = canY;

}

</script>

</head>

<body onload="init()" onmousedown="moveToMouse()">

<H1>Butterfly</h1>

<P>Why are they called butterflies?</P>

<P>Shouldn't they be flutter-bys?</P>

<h2>but-ter-fly</h2>

<P><i>--noun</i></p>

<ol>

Modifying the Canvas with CSSFree Range Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

124

Page 125: HTML Canvas Guide

<li>any of numerous diurnal insects of the order Lepidoptera, characterized byclubbed antennae, a slender body, and large, broad, often conspicuously markedwings.

</li>

<li>a person who flits aimlessly from one interest or group to another: a socialbutterfly.

</li>

<li>butterflies, ( used with a plural verb ) Informal . a queasy feeling, as fromnervousness, excitement, etc.

</li>

</ol>

<img id="b1" style="display:none"

src="http://homepage.mac.com/qt4web/butterfly/butterfly1.png" >

<img id="b2" style="display:none"

src="http://homepage.mac.com/qt4web/butterfly/butterfly2.png" >

<img id="b3" style="display:none"

src="http://homepage.mac.com/qt4web/butterfly/butterfly3.png" >

<img id="b4" style="display:none"

src="http://homepage.mac.com/qt4web/butterfly/butterfly4.png" >

<canvas id="can" height="380" width="380"

style="position:absolute;left:0;top:225">

</canvas>

</body>

</html>

Modifying the Canvas with CSSFree Range Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

125

Page 126: HTML Canvas Guide

Because the canvas element works both on the desktop and in iOS, it can be interacted with by either mouseor touch. This section shows you how to create canvas webpages that respond equally well to both mouseand touch input.

Because the canvas element responds to JavaScript, you can include controls for the canvas anywhere on thepage. This chapter covers four input variants.

● Control using standard HTML inputs elsewhere on the page

● Superimposing standard inputs on the canvas

● Responding to mouse and touch events on the canvas generally

● Creating custom controls on the canvas

Using Standard Inputs with CanvasThere are a few things to bear in mind when using standard HTML inputs with canvas.

Text input fields bring up the soft keyboard on iOS-based devices, covering the lower half of the screen. Makesure the relevant parts of the canvas aren’t obscured by the keyboard, or choose a different kind of input.

Selection input fields with alternates bring up the rotary picker on iPhone and iPod touch, again covering thelower half of the screen. Make sure the relevant parts of the canvas aren’t obscured by the picker, or choosea different kind of input.

Button inputs with default settings tend to be quite small on iPhone and iPod touch. To make buttons easierto find with a finger, try setting a smaller viewport or a larger initial scale using the <meta> tag, or making thebutton font larger using the CSS font style.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

126

Adding Mouse and Touch Controls to Canvas

Page 127: HTML Canvas Guide

The example in Listing 13-1 sets the viewport width to 300, the initial scale to 2, and the input font to largerand bold, resulting in large, easy-to-push buttons on iPhone and iPod touch. The desktop version is illustratedin Figure 13-1.

Figure 13-1 Big buttons

Listing 13-1 Make big buttons

<html>

<head>

<title>Big Buttons</title>

<!-- zoom in for iOS-based devices -->

<meta name="viewport" content="width=300" />

<meta name="viewport" content="initial-scale=2" />

<style>

input {

font: larger bold;

}

</style>

<script type="text/javascript">

var can;

var ctx;

var n = 0;

var hun;

Adding Mouse and Touch Controls to CanvasUsing Standard Inputs with Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

127

Page 128: HTML Canvas Guide

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

hun = document.getElementById("hundred");

ctx.fillStyle="rgb(64,255,64)";

ctx.textAlign="center";

ctx.textBaseline="middle";

ctx.font="24pt Helvetica";

showN();

}

function showN() {

ctx.clearRect(0,0, can.width,can.height, 99);

ctx.fillText(n, can.width /2, can.height / 2);

}

function incr() {

n++;

showN();

}

function decr() {

n--;

showN();

}

function setHundred() {

n = hun.value;

showN();

}

</script>

</head>

<body onload="init()">

Adding Mouse and Touch Controls to CanvasUsing Standard Inputs with Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

128

Page 129: HTML Canvas Guide

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

style="background-color:black">

</canvas>

<br>

<input type="button" value=" + " onclick="incr()">

<input type="button" value=" - " onclick="decr()">

<select id="hundred" onchange="setHundred()">

<option value=0> -- </option>

<option value=100> 100 </option>

<option value=-100> -100 </option>

</select>

</body>

</html>

Using Standard Inputs on CanvasYou can superimpose standard HTML inputs—or custom inputs—on the canvas simply by declaring the inputsafter the <canvas> tag and using CSS to position the inputs on top of the canvas.

The inputs cover any graphics or animation drawn on the canvas.

The example in Listing 13-2 positions a pair of buttons and a selector on top of the canvas, as illustrated inFigure 13-2.

Figure 13-2 Controls on canvas

Adding Mouse and Touch Controls to CanvasUsing Standard Inputs on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

129

Page 130: HTML Canvas Guide

Listing 13-2 Putting controls on canvas

<html>

<head>

<title>Controls on Canvas</title>

<!-- fill iPhone screen with canvas -->

<meta name="viewport" content="width=200" />

<meta name="viewport" content="initial-scale=2" />

<script type="text/javascript">

var can;

var ctx;

var n = 0;

var hun;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

hun = document.getElementById("hundred");

showN();

}

function showN() {

// large, centered, bright green text

ctx.font="24pt Helvetica";

ctx.textAlign="center";

ctx.textBaseline="middle";

ctx.fillStyle="rgb(64,255,64)";

ctx.clearRect(0,0, can.width,can.height);

// draw text at center, max length to fit on canvas

ctx.fillText(n, can.width /2, can.height / 2, can.width - 2);

}

function incr() {

n++;

showN();

Adding Mouse and Touch Controls to CanvasUsing Standard Inputs on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

130

Page 131: HTML Canvas Guide

}

function decr() {

n--;

showN();

}

function setHundred() {

n = hun.value;

showN();

}

</script>

</head>

<body onload="init()">

<!-- give canvas rounded corners-->

<canvas id="can" height="200" width="200"

style= "background-color:black;

border-radius: 25 px;">

</canvas>

<br>

<!-- float inputs over canvas -->

<div style="position:relative; top:-50; left:25">

<input type="button" value=" + " onclick="incr()">

<input type="button" value=" - " onclick="decr()">

<select id="hundred" onchange="setHundred()">

<option value=0> -- </option>

<option value=100> 100 </option>

<option value=-100> -100 </option>

</select>

</div>

</body>

</html>

Adding Mouse and Touch Controls to CanvasUsing Standard Inputs on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

131

Page 132: HTML Canvas Guide

Responding to Mouse and Touch Events on CanvasFor some applications, you don’t need a specific input object—just a way to respond to mouse and touchevents on the canvas as a whole.

Install event listeners on the canvas element for mousedown or click events, and install event listeners onthe body element for mouseup events, in case a mouse event begins on the canvas and ends off the canvas.Similarly, listen for touchstart and touchend events on the canvas, but listen for touchcancel events onthe HTML body.

To obtain the mouse or touch coordinates in terms of the canvas, get the pageX and pageY properties, thensubtract the canvas’s offsetLeft and offsetTop properties.

By default, dragging a finger in iOS pans the browser window. To allow touch to flow smoothly over the canvason iOS, prevent the default panning behavior by adding preventDefault() to your touchstart eventhandler.

Tracking a Single TouchThe example in Listing 13-3 tracks mouse and touch movements that originate on the canvas, displaying thecanvas coordinates and whether the mouse button or finger is down. The results are displayed on the canvas,as shown in Figure 13-3.

Figure 13-3 Tracking events on canvas

The example listens for mousemove or touchmove events on the canvas to track the mouse or finger positionon the canvas. The example also listens for mousedown and mouseup, or touchstart and touchend, todetermine if the mouse button or finger is down. Note that the mouse is still tracked when the mouse buttonis released, but when the finger is lifted off the screen, there is no touch to track. The example then shows thecurrent x,y position and state (up or down) of the mouse or touch, and draws a white cursor at that positionon the canvas.

Adding Mouse and Touch Controls to CanvasResponding to Mouse and Touch Events on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

132

Page 133: HTML Canvas Guide

Only a single touch event is tracked; additional simultaneous touches are ignored. To obtain all the touchevents that begin on the canvas, iterate through the event’s targetTouches array. See “Tracking MultipleTouches and Testing for Hits” (page 135) for details.

Listing 13-3 Track mouse and touch events

<html>

<head>

<!-- fill iPhone screen with canvas -->

<meta name="viewport" content="width=400" />

<title>Tracking Mouse and Touch Events on Canvas</title>

<script type="text/javascript">

var can;

var ctx;

var canX;

var canY;

var mouseIsDown = 0;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

can.addEventListener("mousedown", mouseDown, false);

can.addEventListener("mousemove", mouseXY, false);

can.addEventListener("touchstart", touchDown, false);

can.addEventListener("touchmove", touchXY, true);

can.addEventListener("touchend", touchUp, false);

document.body.addEventListener("mouseup", mouseUp, false);

document.body.addEventListener("touchcancel", touchUp, false);

}

function mouseUp() {

mouseIsDown = 0;

mouseXY();

Adding Mouse and Touch Controls to CanvasResponding to Mouse and Touch Events on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

133

Page 134: HTML Canvas Guide

}

function touchUp() {

mouseIsDown = 0;

// no touch to track, so just show state

showPos();

}

function mouseDown() {

mouseIsDown = 1;

mouseXY();

}

function touchDown() {

mouseIsDown = 1;

touchXY();

}

function mouseXY(e) {

if (!e) var e = event;

canX = e.pageX - can.offsetLeft;

canY = e.pageY - can.offsetTop;

showPos();

}

function touchXY(e) {

if (!e) var e = event;

e.preventDefault();

canX = e.targetTouches[0].pageX - can.offsetLeft;

canY = e.targetTouches[0].pageY - can.offsetTop;

showPos();

}

function showPos() {

Adding Mouse and Touch Controls to CanvasResponding to Mouse and Touch Events on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

134

Page 135: HTML Canvas Guide

// large, centered, bright green text

ctx.font="24pt Helvetica";

ctx.textAlign="center";

ctx.textBaseline="middle";

ctx.fillStyle="rgb(64,255,64)";

var str = canX + ", " + canY;

if (mouseIsDown) str = str + " down";

if (!mouseIsDown) str = str + " up";

ctx.clearRect(0,0, can.width,can.height);

// draw text at center, max length to fit on canvas

ctx.fillText(str, can.width /2, can.height / 2, can.width - 10);

// plot cursor

ctx.fillStyle="white";

ctx.fillRect(canX -5,canY -5, 10,10);

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="200" width="300"

style= "background-color:black">

</canvas>

</body>

</html>

Tracking Multiple Touches and Testing for HitsThe example in Listing 13-4 draws an endless series of descending red bubbles on the canvas, as illustrated inFigure 13-4. Clicking the mouse on a bubble, or touching a bubble with a finger on iOS, pops the bubble.

This example tracks up to four simultaneous touch events, allowing the user to pop up to four bubbles at atime on iOS-based devices.

Adding Mouse and Touch Controls to CanvasResponding to Mouse and Touch Events on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

135

Page 136: HTML Canvas Guide

The example uses isPointInPath() to test each bubble against each touch. The length of the touches arrayis stored in a global variable, and the variable is updated whenever a touch starts, ends, or is canceled.

Figure 13-4 Bubbles

Listing 13-4 Track multiple touches

<html>

<head>

<!-- fill iPhone screen with canvas -->

<meta name="viewport" content="width=400" />

<title>Pop the Bubbles</title>

<script type="text/javascript">

var can;

var ctx;

var canX = [];

var canY = [];

var bubble = [];

var mouseIsDown = 0;

var len = 0;

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

can.addEventListener("mousedown", mouseDown, false);

can.addEventListener("mousemove", mouseXY, false);

can.addEventListener("touchstart", touchDown, false);

can.addEventListener("touchend", touchUp, false);

Adding Mouse and Touch Controls to CanvasResponding to Mouse and Touch Events on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

136

Page 137: HTML Canvas Guide

can.addEventListener("touchmove", touchXY, false);

document.body.addEventListener("mouseup", mouseUp, false);

document.body.addEventListener("touchcancel", touchUp, false);

for (i=0;i<4;i++) {

bubble[i] = 0;

}

animate();

}

function mouseUp() {

mouseIsDown = 0;

mouseXY();

}

function mouseDown() {

mouseIsDown = 1;

mouseXY();

}

function touchDown() {

mouseIsDown = 1;

touchXY();

}

function touchUp(e) {

if (!e) e = event;

len = e.targetTouches.length;

}

function mouseXY(e) {

if (!e) e = event;

canX[0] = e.pageX - can.offsetLeft;

canY[0] = e.pageY - can.offsetTop;

len = 1;

Adding Mouse and Touch Controls to CanvasResponding to Mouse and Touch Events on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

137

Page 138: HTML Canvas Guide

}

function touchXY(e) {

if (!e) e = event;

e.preventDefault();

len = e.targetTouches.length;

for (i=0; i<len; i++) {

canX[i] = e.targetTouches[i].pageX - can.offsetLeft;

canY[i] = e.targetTouches[i].pageY - can.offsetTop;

}

}

function animate() {

ctx.strokeStyle = "red";

ctx.clearRect(0,0, can.width, can.height);

// create a path for each bubble

for (i=0;i<4;i++) {

bubble[i]++;

if (bubble[i] >= can.height + 10)

bubble[i] = -10;

var y = bubble[i];

var x = (i + 1) * 50;

var radius = 20;

ctx.beginPath();

ctx.arc(x,y, radius, 0, 2 * Math.PI);

ctx.closePath();

// test each extant touch to see if it is on the bubble

for (j=0;j<len;j++) {

if (ctx.isPointInPath(canX[j], canY[j]) && mouseIsDown)

bubble[i]=-30;

}

ctx.stroke();

}

setTimeout("animate()", 40);

Adding Mouse and Touch Controls to CanvasResponding to Mouse and Touch Events on Canvas

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

138

Page 139: HTML Canvas Guide

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="200" width="300"

style= "background-color:black">

</canvas>

</body>

</html>

Creating Custom Canvas ControlsYou can draw any kind of control you like on the canvas, and use the techniques described in “Responding toMouse and Touch Events on Canvas” (page 132) to determine if the input is on your custom control, but thereis a faster and better way to implement most custom controls.

If your custom control is part of the canvas itself, the control is just a graphic, not a targetable element.Consequently, you need to track all touches on the canvas, and compare the coordinates of each touch toeach control you draw. It can get complicated.

A better approach is to build the control using HTML and CSS, then position the control on top of the canvasusing CSS. Your control can still be a graphic image—or multiple images—alpha channels in images areautomatically composited onto the underlying canvas.

By creating the control as an element in HTML, you can make the control a target for mouse and touch events.That way, Safari sorts the touches for you, and you can respond to touches on the control itself. There’s noneed to compare multiple touches with multiple controls, or to track the mouse or finger coordinates at all.

Adding a Custom ButtonTo add a custom button to the canvas, create an HTML div or img element and use CSS to style the elementand position it on top of the canvas.

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

139

Page 140: HTML Canvas Guide

Add a listener function for touchstart and mousedown events to detect a custom button being pressed,and add a listener function for touchend and mouseup events to detect the button being released. You mightwant to take an action when the button is pressed or when it is released, or both.

Add a state variable to track whether the button is pressed or released.

Add a listener function for mouseup events to the page as a whole, in case the user clicks your button, thenmoves the mouse pointer off your button before releasing the mouse button.

Similarly, add a listener function for touchcancel events to the page as a whole, in case the touch is canceledfor some reason (such as an incoming phone call, for example).

The example in Listing 13-5 creates a div element, styles it as a button, and positions it on the canvas. Whenthe button is clicked or touched, the button state changes and a different style is applied to the button. Thecanvas also changes, in this case to blue. When the touch ends or the mouse button is released, the buttonstyle reverts to the unpressed state and the canvas changes to black. The results are illustrated in Figure 13-5.

Figure 13-5 Clicking a custom button

The listener functions are added to the button and the HTML body using HTML attributes, such as onmousedownand ontouchstart.

Listing 13-5 Creating a custom button on canvas

<html>

<head>

<title>Custom Button</title>

<meta name="viewport" content="width=300" />

<meta name="viewport" content="initial-scale=2" />

<style>

.myButton {

position: relative;

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

140

Page 141: HTML Canvas Guide

top:-60;

left:10;

border: 4 px outset #c0c0c0;

background-color: #e0e0e0;

width:100 px;

padding: 10 px;

text-align:center;

border-radius: 18 px;

}

.mybutton.pressed {

border: 4 px outset black;

background-color: #808080;

}

</style>

<script type="text/javascript">

var can;

var ctx;

var but1;

// state variable tracks whether button is pressed

var but1press;

function init() {

but1 = document.getElementById("but1");

can = document.getElementById("can");

ctx = can.getContext("2d");

but1press = 0;

}

function press1() {

// change state variable

but1press = 1;

// change button style

but1.className="myButton pressed";

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

141

Page 142: HTML Canvas Guide

// do something on the canvas

ctx.fillStyle="beige";

ctx.fillRect(0,0, can.width,can.height);

}

function release1() {

// button 1 may or may not be pressed when mouse button comes up

// or touch ends.

// if button is pressed, release it and do something on the canvas

if (but1press) {

but1press = 0;

// revert button style

but1.className="myButton";

// do something on the canvas

ctx.fillStyle="white";

ctx.fillRect(0,0, can.width,can.height);

}

}

</script>

</head>

<body onload="init()" onmouseup="release1()" ontouchcancel="release1()">

<canvas id="can" height="200" width="300"

</canvas>

<div id="but1" class="myButton"

onmousedown="press1()" onmouseup="release1()"

ontouchstart="press1()" ontouchend="release1()">

Click Me

</div>

</body>

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

142

Page 143: HTML Canvas Guide

</html>

Adding a SliderA slider is a useful control for user input having a fixed range. A slider consists of a knob and a bar for the knobto slide on. The range of the slider can be anything, but the number of dragable steps is constrained by thelength of the bar. To create a slider with a range of n1 to n2 in 100 steps, for example, you need to create abar at least 100 pixels long—a single pixel is the minimum finger-controllable movement of the knob.

A slider can be a graphic image, but you can also use CSS to style a set of nested div elements to act as aslider, using only text.

On iOS-based devices, the minimum comfortable size for the knob on a slider is 44 x 44 pixels. A circle of radius25 meets these criteria and makes a comfortable target for a finger.

To build a slider, listen for mousedown, mousemove, touchbegin, and touchmove events on the slider element.Listen for mouseup events on the body element and track the mouse button state.

Begin your touch event handlers with preventDefault() to allow the finger to drag the slider instead ofscrolling the page.

Position the knob relative to the bar using CSS. The slider has a dragable range from 0 to the width of the bar,minus the width of the knob. For example, for a circular knob of radius 25, the minimum bar width is 150 pixelsto allow 100 dragable steps while keeping the knob on the bar.

The knob value is the mouse or touch event’s pageX property, minus the slider’s offsetLeft property. Theknob should be positioned half its width to the left of the knob value, so the mouse or finger drags the centerof the knob. The knob value should be clamped at 0 and the bar width minus the knob width.

The example in Listing 13-6 creates a slider using three nested div elements. The outermost div element isthe slider and is positioned absolutely. The two inner div elements are the bar and the knob, and are positionedrelatively within the slider. The bar div is styled into a horizontal line and the knob div is styled into a circleusing CSS.

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

143

Page 144: HTML Canvas Guide

The knob is 52 pixels wide, including the border, and the bar is 152 pixels wide, giving the slider a positionalrange of 0-100. The slider is used to scale a graphic from a size of 0.25 to 0.75 in 100 steps, to demonstratethat the value range driven by the slider is not constrained by the positional range. The result is illustrated inFigure 13-6.

Figure 13-6 Slider controlling image size

The knob value is displayed and the image is redrawn in an independent animation loop, so as not to overloadthe event handlers and make them unresponsive. The knob is repositioned using CSS, by setting theoffsetLeftproperty. Safari redraws the knob automatically.

Listing 13-6 Adding a slider using CSS

<html>

<head>

<title>Custom Slider</title>

<!-- Fill the iOS screen /-->

<meta name="viewport" content="width=400" />

<style>

canvas {

position:absolute; top:10; left:10;

background-color: beige;

border-radius: 25;

border: 1 px solid #404040;

}

.slider {

position: absolute;

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

144

Page 145: HTML Canvas Guide

top: 115;

left:85;

width: 152;

height = 52;

}

.bar {

position: relative;

top:30;

width: 152;

height: 2 px;

background-color: #404040;

}

.knob {

position: relative;

left:0;

border: 1 px solid #404040;

background-color: #C0C0C0;

width:50 px;

height: 50 px;

border-radius: 25 px;

}

</style>

<script type="text/javascript">

var can;

var ctx;

var image;

var slider;

var knob;

var mouseIsDown;

var knobMid;

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

145

Page 146: HTML Canvas Guide

function init() {

slider = document.getElementById("slider");

knob = document.getElementById("knob");

image = document.getElementById("image");

can = document.getElementById("can");

ctx = can.getContext("2d");

mouseIsDown = 0;

knobMid = knob.offsetWidth / 2;

margin = can.offsetLeft - 1;

textInit();

showVal();

}

function textInit() {

ctx.fillStyle = "blue";

ctx.font = "24pt Helvetica";

ctx.textAlign = "center";

ctx.textBaseline = "bottom";

}

function showVal() {

// value goes from 0 to slider-width minus knob width

var sliderVal = knob.offsetLeft;

ctx.save();

ctx.clearRect(0,0, can.width, can.height);

var scale= .25 + sliderVal / 200;

ctx.scale(scale, scale);

ctx.drawImage(image, 0,0);

ctx.restore();

ctx.fillText(sliderVal, can.width / 2, can.height - 5);

setTimeout("showVal()", 25);

}

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

146

Page 147: HTML Canvas Guide

function mouseDown() {

mouseIsDown = 1;

mouseXY();

}

function mouseUp() {

mouseIsDown = 0;

}

function mouseXY(e) {

if (mouseIsDown) {

if (!e) var e = event;

var mouseX = e.pageX - slider.offsetLeft;

if (mouseX >= 0 && mouseX <= slider.offsetWidth ) {

setKnob(mouseX);

}

}

}

function touchXY(e) {

if (!e) var e = event;

// slide, don't scroll

e.preventDefault();

var touchX = e.touches[0].pageX - slider.offsetLeft;

if (touchX >= 0 && touchX <= slider.offsetWidth) {

setKnob(touchX);

}

}

function setKnob(x) {

var knobX = x - knobMid;

knobX = Math.max(knobX, 0);

knobX = Math.min(knobX, slider.offsetWidth - knob.offsetWidth);

knob.style.left = knobX;

}

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

147

Page 148: HTML Canvas Guide

</script>

</head>

<body onload="init()" onmouseup="mouseUp()" >

<canvas id="can" height="200" width="300">

</canvas>

<div class="slider" id="slider"

onmousedown="mouseDown()" onmousemove="mouseXY()"

ontouchstart="touchXY()" ontouchmove="touchXY()">

<div class="bar">

</div>

<div id="knob" class="knob"

</div>

<div>

<img id="image" style="display:none"

src="http://homepage.mac.com/qt4web/butterfly/butterfly1.png" >

</body>

</html>

Adding Mouse and Touch Controls to CanvasCreating Custom Canvas Controls

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

148

Page 149: HTML Canvas Guide

Canvas provides the visual tools for creating animations, slide shows, and games, but to complete a rich mediaexperience you typically want to add sound as well. To add sound to a canvas-based animation or game, usethe HTML5 <audio> element, controlled by the same JavaScript that controls your canvas animation.

This section provides a brief summary of using HTML5 audio in the context of creating canvas-based rich mediawebsites. For a more in-depth discussion of HTML5 audio and video, see Safari HTML5 Audio and Video Guide .

Adding HTML5 audio to your canvas presentation on the desktop is simple—just include an <audio> tag anduse the JavaScript play() method.

On iOS, it’s slightly more complicated. Because the user may be viewing your website over a cellular connection,paying for data by the megabyte, and because audio uses a lot of data, Safari on iOS downloads audio only asa direct result of user action. You can’t programmatically load and play audio unless the user authorizes it byclicking a button.

Currently, iOS supports playback of a single audio stream at a time when playing audio inline on a website.

Currently, the loop attribute for HTML5 audio is not supported on iOS.

To include audio in your canvas presentation, and have it work on the desktop and iOS, follow these steps:

● Include an “Enable sound” button to initiate playback.

● Use a single HTML5 audio element for all your sounds.

● To play different sounds, change the src property of the audio element.

● If your game includes background music as well as sound effects, make the background music availablein iTunes or another iOS app that can play music in the background while the user plays your game, andtrigger the sound effects one by one from within your game.

● If you need to have your audio track loop, listen for the ended event on the audio element, then call theplay() method to restart the sound.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

149

Adding Sound to Canvas Animations

Page 150: HTML Canvas Guide

Note There is no software volume control for HTML5 audio on iOS—the user controls the audiovolume using the hardware volume control. Setting the audio element’s volume property on iOShas no effect; reading the volume property always returns a value of 1 (full volume).

Adding a SoundtrackA soundtrack is a single audio source—such as music or a voice-over—that plays during your presentation.The audio source can loop or be timed to match the canvas presentation.

To add a soundtrack, create an audio element in your HTML and use the play() method to start it. If yourpresentation includes a “Start” button, the same button can initiate the presentation and play the audio,providing iOS compatibility.

The simplest way to add a soundtrack is to include the controls attribute in the <audio> tag. You can useCSS to position the audio controller on the canvas if you like. By installing an event listener on the audioelement, you can use the audio controller’s start button to start your animation as well.

The amount of time between the user pressing the play button and the start of the sound varies—file size,audio format, and network speed are all factors. To synchronize your animation more closely with the start ofthe audio, install an event listener on the audio element and begin your animation when the "playing"event is triggered. You can also pause your animation when the audio element’s pause or ended events aretriggered.

The example in Listing 14-1 uses an <audio> tag with the controls attribute to add a user-initiated soundtrackto the canvas, and animates a gradient when the music is playing, as illustrated in Figure 14-1.

Figure 14-1 Soundtrack

Listing 14-1 Adding a simple soundtrack

<html>

<head>

Adding Sound to Canvas AnimationsAdding a Soundtrack

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

150

Page 151: HTML Canvas Guide

<title>Animated Gradient with Sound Track</title>

<!-- Fill the iOS screen /-->

<meta name="viewport" content="width=400" />

<style>

canvas {

position:absolute; top:10; left:10;

border-radius: 25;

border: 1 px solid #404040;

}

audio {

position: absolute;

top: 195;

left:60;

}

</style>

<script type="text/javascript">

var can;

var ctx;

var audio;

var angle = 0;

var timer;

function init() {

audio = document.getElementById("audio");

can = document.getElementById("can");

ctx = can.getContext("2d");

audio.addEventListener("playing", start, false);

audio.addEventListener("pause", stop, false);

audio.addEventListener("ended", stop, false);

Adding Sound to Canvas AnimationsAdding a Soundtrack

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

151

Page 152: HTML Canvas Guide

}

function start() {

drawGradient();

}

function stop() {

clearTimeout(timer);

}

function drawGradient() {

// increment angle slowly from 0 to 2 PI

angle = angle + 0.1;

if (angle >= 6.2)

angle = 0;

// create gradient that goes from bottom to top of canvas

var grad = ctx.createLinearGradient(0,can.height, 0,0);

// start gradient at black

grad.addColorStop(0, 'black');

// create changing rgb color values that go from 0 to 255

var gAngle = angle + Math.PI / 2;

var bAngle = gAngle + Math.PI;

var r = parseInt(255 * Math.abs(Math.sin(angle)));

var g = parseInt(255 * Math.abs(Math.sin(gAngle)));

var b = parseInt(255 * Math.abs(Math.sin(bAngle)));

var rgbCol = "rgb(" + r + "," + g + "," + b + ")";

// add color stop with new rgb colors

grad.addColorStop(1, rgbCol);

// fill canvas with gradient

ctx.save();

Adding Sound to Canvas AnimationsAdding a Soundtrack

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

152

Page 153: HTML Canvas Guide

ctx.fillStyle=grad;

ctx.fillRect(0,0, can.width, can.height);

ctx.restore();

// repeat while audio is not paused

if (!document.querySelector("audio").paused)

timer = setTimeout("drawGradient()", 100);

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="200" width="300">

</canvas>

<audio id="audio"

src="http://homepage.mac.com/qt4web/myAudio.m4a"

controls >

</audio>

</body>

</html>

Note that the previous example tests whether the audio is playing at the end of the animation cycle, insteadof just clearing the animation timeout in the "pause" and "ended" event handlers. The audio events canoccur while the animation cycle is in mid-execution, so it’s important not to reset the timer without testingthe play state.

Looping AudioSometimes sound isn’t tightly-coupled to the visual display—for example, looping audio may provide ambienceto your site, but not be a requirement to enjoy the visuals.

The loop attribute is not currently supported for inline HTML5 audio on iOS, so to make your audio loop youneed to install an event listener on the audio element—the event listener listens for the "ended" event andinvokes the play() method to immediately restart the audio at the beginning.

Adding Sound to Canvas AnimationsLooping Audio

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

153

Page 154: HTML Canvas Guide

The example in Listing 14-2 runs the visual part of the presentation whether or not the user chooses to playthe audio, but loops the audio when it is playing. Note that the audio loops only when the "ended" event istriggered. If the user stops playback manually, the "pause" event is triggered, and the audio is not automaticallyrestarted. The example is illustrated in Figure 14-2.

Figure 14-2 Looping audio

Listing 14-2 Adding looping audio

<html>

<head>

<title>Happy Holidays</title>

<!-- Fill the iOS screen /-->

<meta name="viewport" content="width=600" />

<style>

canvas {

position:absolute; top:10; left:10;

border-radius: 25;

}

audio {

position:absolute; top:307; left:160;

}

</style>

Adding Sound to Canvas AnimationsLooping Audio

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

154

Page 155: HTML Canvas Guide

<script type="text/javascript">

var can;

var ctx;

var flake;

var x = [];

var y = [];

var xIncr = [];

var yIncr = [];

var rot = 0;

var grad;

var rainbow;

var audio;

function init() {

flake = document.getElementById("flake");

audio = document.getElementById("audio");

can = document.getElementById("can");

ctx = can.getContext("2d");

// create blue background gradient

grad = ctx.createLinearGradient(0,can.height, 0,0);

grad.addColorStop(0, 'rgb(20,20,128)');

grad.addColorStop(1, 'rgb(140,140,255)');

// create rainbow gradient for letters

rainbow = ctx.createLinearGradient(20,0, can.width - 20,0);

rainbow.addColorStop(0, 'red');

rainbow.addColorStop(1 / 6, 'orange');

rainbow.addColorStop(2 / 6, 'yellow');

rainbow.addColorStop(3 / 6, 'green');

rainbow.addColorStop(4 / 6, 'aqua');

rainbow.addColorStop(5 / 6, 'blue');

rainbow.addColorStop(6 / 6, 'purple');

// set font properties

ctx.font="140pt Papyrus bold italic";

Adding Sound to Canvas AnimationsLooping Audio

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

155

Page 156: HTML Canvas Guide

ctx.textAlign="center";

ctx.textBaseline = "bottom";

// create snowflakes above screen

// drifting slowly down and randomly left or right

for (i=1;i<=16;i++) {

x[i] = Math.random() * can.width;

y[i] = Math.random() * can.height / -2;

yIncr[i] = Math.max(Math.random() * .4, 0.15);

xIncr[i] = Math.random() * 0.3 - 0.15;

}

// add listener function to loop on end

audio.addEventListener("ended", loop, false);

// set animation on perpetual loop

setInterval("animate()", 30);

}

function loop() {

audio.play();

}

function animate() {

model();

draw();

}

function model() {

// increment flakes x and y coords

for (i=1;i<=16;i++) {

y[i] = y[i] + yIncr[i];

x[i] = x[i] + xIncr[i];

// if off bottom, give new x coord and start above top

if (y[i] > can.height + 45) {

y[i] = -45;

x[i] = Math.random() * can.width;

Adding Sound to Canvas AnimationsLooping Audio

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

156

Page 157: HTML Canvas Guide

yIncr[i] = Math.max(Math.random() * .4, 0.15);

}

}

}

function draw() {

// draw background

ctx.fillStyle = grad;

ctx.fillRect(0,0,can.width,can.height);

// rotate flakes as they fall

rot=rot + 0.005;

// draw flakes offset so they rotate around center

for (i=1;i<=16;i++) {

ctx.save();

ctx.translate(x[i],y[i]);

ctx.rotate(rot);

ctx.drawImage(flake, -37, -43);

ctx.restore();

}

// draw word with black and white borders for depth

ctx.fillStyle = 'black';

ctx.fillText("Peace", can.width / 2 -2, can.height + 2);

ctx.fillStyle = 'white';

ctx.fillText("Peace", can.width / 2 + 2, can.height - 2);

ctx.fillStyle = rainbow;

ctx.fillText("Peace", can.width / 2, can.height );

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="300" width="500">

</canvas>

Adding Sound to Canvas AnimationsLooping Audio

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

157

Page 158: HTML Canvas Guide

<br>

<audio src="peace.mp4" controls id="audio">

</audio>

<img id="flake" src="flake.png" style="display:none">

</body>

</html>

Adding a Voice-OverFor a slideshow or a Keynote presentation, for example, you may want to add a voice-over that narrates eachslide individually. It’s easiest to create and maintain such a presentation if you record a separate audio file foreach slide—that way you can add, change, or rearrange your presentation on a slide-by-slide basis.

To synchronize the slides with your audio, install an event listener on the audio element and listen for the"ended" event. Respond to the event by changing the src property of the audio element to the audio filefor the next slide, and by changing the canvas to display the next slide.

When the last slide has played, reset the audio and visual elements so that, if the user presses play again, thepresentation starts from the beginning.

The example in Listing 14-3 plays a series of images exported from Keynote, synchronized to a series ofvoice-overs created using QuickTime Player. The example is illustrated in Figure 14-3.

Figure 14-3 Voice-over

Adding Sound to Canvas AnimationsAdding a Voice-Over

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

158

Page 159: HTML Canvas Guide

Listing 14-3 Displaying slides with voice-overs

<html>

<head>

<title>Slides with Voice-Over</title>

<!-- Fill the iOS screen /-->

<meta name="viewport" content="width=600" />

<style>

canvas {

position:absolute; top:10; left:10;

}

audio {

position: absolute;

top: 380;

left:160;

}

</style>

<script type="text/javascript">

var can;

var ctx;

var audio;

var image;

var maxSlides = 4;

var slide = [];

var voice = [];

var index = 0;

var base = "http://homepage.mac.com/qt4web/";

function init() {

audio = document.getElementById("audio");

Adding Sound to Canvas AnimationsAdding a Voice-Over

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

159

Page 160: HTML Canvas Guide

image = document.getElementById("image");

can = document.getElementById("can");

ctx = can.getContext("2d");

for (i=0;i<maxSlides;i++) {

// get filenames into JavaScript

slide[i] = base + "slides" + i + ".jpg";

voice[i] = base + "slide" + i + ".mp4";

// preload images

image.src=slide[i];

}

// draw initial "splash screen" slide

image.src = slide[0];

ctx.drawImage(image, 0,0, can.width, can.height);

audio.addEventListener("playing", start, false);

audio.addEventListener("ended", next, false);

}

function start() {

// respond to playing event only if index = 0

if (index==0) {

// set index to 1 and show next slide

index = 1;

image.src = slide[1];

ctx.drawImage(image, 0,0, can.width, can.height);

}

}

function next() {

// if audio for this slide ended, advance index

index++;

if (index<maxSlides) {

// increment slide image src

image.src = slide[index];

// increment audio src

Adding Sound to Canvas AnimationsAdding a Voice-Over

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

160

Page 161: HTML Canvas Guide

audio.src = voice[index];

// play new audio src

audio.play();

// show new slide

ctx.drawImage(image, 0,0, can.width, can.height);

}

// if last slide shown, reset index and audio src to start over

if (index==4) {

index = 0;

audio.src = voice[1];

}

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="384" width="512">

</canvas>

<img id="image" src="http://homepage.mac.com/qt4web/slides0.jpg"style="display:none">

<audio id="audio" src="http://homepage.mac.com/qt4web/slide1.mp4" controls >

</audio>

</body>

</html>

Adding Sound EffectsFor games, you typically want to trigger different sounds in response to events in the game. To make this workon iOS as well as the desktop, load the first sound in response to a user-activated control, such as a start button;play other sounds by changing the src property of the audio element and calling the play() method.

Adding Sound to Canvas AnimationsAdding Sound Effects

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

161

Page 162: HTML Canvas Guide

The example in Listing 14-4 includes an audio element and an Enable Sounds button. When sounds areenabled, the ball makes different sounds when it bounces off the bottom or the side. The example is illustratedin Figure 14-4.

Figure 14-4 Animation with sound effects

Listing 14-4 Creating animation with sound effects

<html>

<head>

<title>Animation with Sound Effects</title>

<script type="text/javascript">

var can;

var ctx;

var ball;

var x;

var y;

var xVec;

var yVec;

var direc;

var rot = 0;

var gravity = 1;

Adding Sound to Canvas AnimationsAdding Sound Effects

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

162

Page 163: HTML Canvas Guide

var left=70;

var right=525;

var bottom=325;

var interval;

var centerOffset = -75;

var audio;

var sound = 0;

var bing = "http://homepage.mac.com/qt4web/bing.mp4";

var boing = "http://homepage.mac.com/qt4web/boing.mp4";

var button;

function init() {

audio = document.getElementById("audio");

button = document.getElementById("button");

ball = document.getElementById("ball");

can = document.getElementById("can");

ctx = can.getContext("2d");

ctx.strokeStyle="black";

// initialize position, speed, spin direction

x = 98;

y = 75;

xVec = 5.5;

yVec = 0;

direc = 1;

// draw lines for the ball to bounce off of

ctx.moveTo(0,bottom + 75);

ctx.lineTo(600,bottom+ 75);

ctx.lineTo(600,0)

ctx.stroke();

// set animation to repeat every 50 msec

interval = setInterval("animate()",50);

}

function animate() {

model();

Adding Sound to Canvas AnimationsAdding Sound Effects

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

163

Page 164: HTML Canvas Guide

// clear canvas except for lines at edge

ctx.clearRect(0,0,can.width -1 ,can.height -1);

draw();

}

function model() {

rot = rot + .1 * direc;

x = x + xVec;

yVec = yVec + gravity;

y = y + yVec;

bounceIf();

}

function bounceIf() {

if (y >= bottom) {

y = bottom;

yVec = -1 * yVec - gravity

// set audio for bottom bounce

audio.src = boing;

if (sound) audio.play();

}

if (x >= right || x <= left) {

xVec = -1 * xVec;

direc = -1 * direc;

// set audio for side bounce

audio.src = bing;

if (sound) audio.play();

}

}

function draw() {

ctx.save();

ctx.translate(x,y);

ctx.rotate(rot);

Adding Sound to Canvas AnimationsAdding Sound Effects

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

164

Page 165: HTML Canvas Guide

ctx.drawImage(ball, centerOffset,centerOffset);

ctx.restore();

}

function soundToggle() {

if (!sound) {

audio.load();

sound = 1;

button.value="Turn Sounds Off";

} else {

sound = 0;

button.value="Enable Sounds";

}

}

</script>

</head>

<body onload="init()" style="background-color:#e0e0e0">

<h2>Animation with Sound</h2>

<img id="ball"

src="http://homepage.mac.com/qt4web/soccerball1.png"

style="display:none">

<canvas id="can" height="400" width="600"

style="position:relative;top:-50">

</canvas>

<audio id="audio" src="http://homepage.mac.com/qt4web/boing.mp4"style="display:none">

</audio>

<input id="button" type="button" value="Enable Sounds" onclick="soundToggle()">

</body>

</html>

Adding Sound to Canvas AnimationsAdding Sound Effects

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

165

Page 166: HTML Canvas Guide

You have direct access to the canvas bitmap as an array of RGBa pixels.

Getting Pixel Data From the CanvasYou can get an imageData object for any rectangular section of the canvas by calling the context’sgetImageData(x,y, width,height) method.

You obtain an array of pixel values using the imageData object’s data property. For example: var pixArray= myImageData.data

The imageData object’s data property is a one-dimensional array having four values per pixel—red, green,blue, and alpha—expressed as integers from 0-255. The alpha value is quantized to the nearest 1 / 255th, thenmultiplied by 255. If the first pixel in the array is RGBa (255,128,20,1.0) for example, the first four values in thearray are 255, 128, 20, 255.

The pixel array you obtain using the imageData.data property is not a copy of the pixel data, but an actualreference to the data. Any changes you make to the pixel array you obtain are also made immediately to theimageData object.

Note Alpha is normally expressed as a floating point value between 0.0 and 1.0, inclusive. The alphavalue in the imageData.data array is expressed as an integer between 0 and 255, inclusive—thenormal alpha value quantized to the nearest 1 / 255th, and multiplied by 255.

Creating Buffer ObjectsYou can create additional imageData objects by calling the context’s createImageData() method. You canpass in either a width and height or another imageData object, in which case the new imageData object hasthe same dimensions as the one you pass in.

New imageData objects are initialized to transparent black—each pixel in the imageData object’s dataproperty is represented by four consecutive zeroes.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

166

Pixel Manipulation

Page 167: HTML Canvas Guide

The following snippet gets the image data for the whole canvas, creates an image buffer, and copies the imagedata into the new buffer.

var theImage = ctx.getImageData(0,0,can.width,can.height);

var buffer = ctx.createImageData(theImage);

var pixArray = theImage.data;

var bufArray = buffer.data

for (i=0;i<pixArray.length;i++) {

bufArray[i] = pixArray[i];

}

Modifying Pixel DataModify pixel data on the canvas by assigning an array of pixel values to an imageData object’s pixel array andcalling the context’s putImageData() method.

To blit the entire imageData object to the screen, call putImageData(theImageData, x,y), specifyingthe x,y coordinates of the upper left corner of the rectangle into which you want to blit the image data.

To blit only a subsection of the imageData object to the screen, pass in the x, y, width, and height of thesubsection as well:

context.putImageData(imageData, x, y, subX, subY, subWidth, subHeight)

The subsection is blitted in at x+subX, y+subY, just as if you had blitted the whole imageData object at x,y.

Pixel ManipulationModifying Pixel Data

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

167

Page 168: HTML Canvas Guide

Example: Reading and Modifying Pixel DataThe example in Listing 15-1 gets an imageData object for the entire canvas and creates arrays of red, green,blue, and alpha values from the pixel data. When the Clear Green button is pushed, the example searches forpixels with the RGB value 0, 255, 0 and sets the alpha values for those pixels to 0. A Restore Green button setsthe same pixels’ alpha values to 255 (equivalent to RGBa alpha value of 1.0). The result is illustrated in figureFigure 15-1.

Figure 15-1 Green clear and restore

Listing 15-1 Manipulating pixels

<html>

<head>

<title>Pixel Manipulation</title>

<script type="text/javascript">

var can;

var ctx;

var theImage;

var pix;

var red = [];

var green = [];

var blue = [];

var alpha = [];

function init() {

can = document.getElementById("can");

ctx = can.getContext("2d");

// fill screen with red, green, and blue stripes

ctx.fillStyle="red";

Pixel ManipulationExample: Reading and Modifying Pixel Data

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

168

Page 169: HTML Canvas Guide

ctx.fillRect(0,0, can.width /3, can.height);

ctx.fillStyle='rgb(0,255,0)';

ctx.fillRect(can.width / 3,0, can.width * 2/3, can.height);

ctx.fillStyle="blue";

ctx.fillRect(can.width* 2/ 3,0, can.width, can.height);

// get pixel data for entire canvas

theImage=ctx.getImageData(0,0,can.width,can.height);

pix = theImage.data;

// separate out red, green, blue, and alpha values

// every four values equals one pixel

for (i=0;i<pix.length;i=i+4) {

red[i/4]=pix[i];

green[i/4]=pix[i+1];

blue[i/4]=pix[i+2];

alpha[i/4]=pix[i+3];

}

}

function clearGreen() {

// check each pixel's RGB value

for (i=0;i<can.width * can.height;i++) {

if (red[i]==0 && green[i]==255 && blue[i]==0)

// set alpha value for pixel to 0

pix[i*4 +3]=0;

}

// blit modified image object to screen

ctx.putImageData(theImage,0,0);

}

function restoreGreen() {

for (i=0;i<can.width * can.height;i++) {

if (red[i]==0 && green[i]==255 && blue[i]==0)

pix[i*4 +3]=255;

}

ctx.putImageData(theImage,0,0);

Pixel ManipulationExample: Reading and Modifying Pixel Data

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

169

Page 170: HTML Canvas Guide

}

</script>

</head>

<body onload="init()">

<canvas id="can" height="200" width="300">

</canvas>

<br>

<input type="button" value="Clear Green" onClick="clearGreen()">

<input type="button" value="Restore Green" onClick="restoreGreen()">

</body>

</html>

Pixel ManipulationExample: Reading and Modifying Pixel Data

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

170

Page 171: HTML Canvas Guide

Creating games involves all the subjects covered in this document up to this point: drawing shapes, images,and text; animation; adding touch and mouse controls; and triggering sounds. In addition, for many gamesyou need to detect collisions, which may involve reading the canvas pixels.

Collision DetectionThere are several ways to detect collisions between objects in a game.

You can compare the x and y coordinates of the various elements—this is best for detecting collisions withwalls or between rectangular objects.

If at least one of the objects is a shape, you can use the isPointInPath(x,y) method to see if a given pointis inside the path—this is best for detecting collisions between missiles and shapes.

You can examine the canvas bitmap, to see if a target color is anywhere inside a given area—this is best whenthe target is a unique color.

Space Arcade GameThe example in Listing 16-1 is the skeleton of an arcade game, as illustrated in Figure 16-1. It has a scrollingbackground, a ship that responds to touch or mouse control, missiles that fire when the mouse is clicked orthe screen is tapped, and targets that follow a path. The game detects collisions between missiles and targets,and maintains a score that advances when a target is hit.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

171

Creating Games

Page 172: HTML Canvas Guide

All the elements of a side-scrolling arcade game are present. With a little work, you can modify the skeletonto support any number of arcade shoot-em-ups.

Figure 16-1 Space arcade game

Listing 16-1 Space arcade game

<html>

<head>

<title>Space Arcade Game</title>

<meta name="viewport" content="width=600" />

<script type="text/javascript">

var can;

var ctx;

var back;

var xBack=0;

var xIncr = 1;

var imgWidth = 1498;

var shipX = 10;

var shipY = 140;

var target = [1, 1, 1, 1, 1];

var targetX = new Array;

var targetY = new Array;

var targetSpeed = 1.5;

var phase=0.1;

var targets = 4;

Creating GamesSpace Arcade Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

172

Page 173: HTML Canvas Guide

var bulletX;

var bulletY;

var score=0;

function init() {

back = document.getElementById("back");

can = document.getElementById("can");

ctx = can.getContext("2d");

ctx.font="14pt Helvetica";

newTargets();

animate();

can.addEventListener("mousemove",move, false);

can.addEventListener("touchmove",tMove, false);

can.addEventListener("mouseup",newBullet, false);

}

function newTargets() {

for (i=0;i<targets;i++) {

target[i] = 1;

targetX[i] = can.width + 10 + i * 50;

targetY[i] = i * Math.PI / 2;

}

}

// There is a maximum of 1 bullet

// If user shoots again, old bullet is gone

// If bulletX = 0, there is no bullet

// User clicked mouse or lifted finger

function newBullet(e) {

bulletX = 35;

bulletY = shipY + 10;

}

function animate() {

Creating GamesSpace Arcade Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

173

Page 174: HTML Canvas Guide

model();

drawBack();

drawShip();

drawBullet();

drawTarget();

drawScore();

setTimeout("animate()",15);

}

function model() {

// if there is a bullet, advance it

if (bulletX)

bulletX = bulletX + 2;

// if the bullet goes off the right edge, zero it

if (bulletX > can.width)

bulletX = 0;

// move targets

for (i=0;i<targets;i++) {

targetX[i] = targetX[i] - targetSpeed;

targetY[i] = targetY[i] + phase;

}

// if last target off left edge of screen,

// generate new set

if (targetX[targets -1] < 0)

newTargets();

}

function drawBack() {

// pan background

xBack = xBack - xIncr;

ctx.drawImage(back, xBack,0);

// draw new copy at right edge of old copy

ctx.drawImage(back, xBack + imgWidth,0);

// if background scrolled off screen, reset

Creating GamesSpace Arcade Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

174

Page 175: HTML Canvas Guide

if (xBack<= -1 * imgWidth)

xBack = xBack + imgWidth;

}

function drawTarget() {

ctx.strokeStyle = "red";

// for each target:

for (i=0;i<targets;i++) {

// if target not yet hit:

if (target[i]) {

// draw a circle

ctx.beginPath();

var tY = 150 + 25 * Math.sin(targetY[i]);

ctx.arc(targetX[i], tY, 10, 0, 2 * Math.PI);

ctx.closePath();

ctx.stroke();

// if bullet inside circle, target is hit

if (bulletX && ctx.isPointInPath(bulletX,bulletY)) {

target[i]=0;

score = score + 10;

}

}

}

}

function drawShip() {

ctx.fillStyle="white";

ctx.beginPath();

ctx.moveTo(shipX,shipY);

ctx.lineTo(shipX + 30, shipY + 10);

ctx.lineTo(shipX, shipY + 20);

ctx.closePath();

ctx.fill();

}

Creating GamesSpace Arcade Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

175

Page 176: HTML Canvas Guide

function drawBullet() {

if (bulletX)

ctx.fillRect(bulletX,bulletY,2,1);

}

function drawScore() {

var sc = "Score: " + score;

ctx.fillText(sc, 10,25);

}

// move ship in response to mouse

function move(e)

{

if (!e) e= event;

shipY = e.pageY;

return false;

}

// move ship in response to touch

function tMove(e)

{

if (!e) e= event;

shipY= e.touches[0].pageY;

return false;

}

</script>

</head>

<body onload="init()"

style="background-color:black">

<canvas id="can" height="300" width="500""

Creating GamesSpace Arcade Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

176

Page 177: HTML Canvas Guide

style="position:absolute;top:0;left:0">

</canvas>

<img id="back" style="display:none"

src="starback.png">

</body>

</html>

Loony Lander GameThe example in Listing 16-2 (page 177) is a complete game, including sound effects, simulating a Lunar ExcursionModule (LEM) landing on the moon. This game uses PNG images—scaled and rotated—as sprites over a fixedbackground. The game has two custom controls, a button and a slider, created using CSS-styled div elements,to control thrust and rotation. The controls respond to touch or mouse input. A very simple physics modeladds gravity to the LEM at regular intervals. The game is illustrated in Figure 16-2 (page 177).

Figure 16-2 Lander game

Listing 16-2 Lander game

<html>

<head>

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

177

Page 178: HTML Canvas Guide

<title>lander</title>

<meta name="viewport" content="width=900" />

<style>

.myButton {

position: absolute;

top:420;

left:170;

border: 4 px outset red;

background-color: #C0C0C0;

width:75 px;

padding: 10 px;

text-align:center;

border-radius: 18 px;

}

.myButton.pressed {

border: 4 px inset red;

background-color: #808080;

}

.slider {

position: absolute;

top: 410;

left:500;

width: 152;

height = 52;

}

.bar {

position: relative;

top:30;

width: 152;

height: 2 px;

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

178

Page 179: HTML Canvas Guide

background-color: red;

}

.knob {

position: relative;

left:0;

border: 2 px solid red;

background-color: #C0C0C0;

width:50 px;

height: 50 px;

border-radius: 25 px;

text-align:center;

}

</style>

<script type="text/javascript">

var can;

var ctx;

var back;

var sprite;

var flames;

var flag;

var x = 30;

var y = 30;

var offset;

var lemH=88;

var lemW=88;

var gravity = .02;

var yVector = 0;

var xVector = 1.2;

var thrust = .2;

var fuel = 250;

var delay = 100;

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

179

Page 180: HTML Canvas Guide

var t;

var interval;

var scale;

var theta = 0;

var bottom = 380;

var sound;

var mouseIsDown = 0;

var thrustIsDown = 0;

var thrustButton;

var knobMid;

var slider;

var knob;

var gameover = 0;

function init() {

can = document.getElementById("can");

ctx = can.getContext('2d');

back = document.getElementById("back");

sprite = document.getElementById("sprite");

flames = document.getElementById("flames");

flag = document.getElementById("flag");

sound = document.getElementById("sound");

thrustButton = document.getElementById("thrustButton");

slider = document.getElementById("slider");

knob = document.getElementById("knob");

knobMid = knob.offsetWidth / 2;

setKnob(75);

document.body.addEventListener("mouseup", mouseUp, true);

x = 30;

y = 30;

yVector = 0;

xVector = 1.2;

theta=0;

fuel = 250;

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

180

Page 181: HTML Canvas Guide

drawAll();

interval = setInterval("addGravity()",delay);

update();

}

function restart() {

sound.src="noise.m4a";

sound.load();

clearTimeout(t);

clearInterval(interval);

gameover = 0;

init();

}

function drawAll() {

drawBack();

drawStatus();

drawFuel();

drawSprite();

}

function drawBack() {

ctx.drawImage(back, 0, 0);

ctx.fillStyle="yellow";

ctx.font="36pt Helvetica";

ctx.fillText("Loony Lander", 300, 50);

}

function drawStatus() {

var dispX = parseInt(xVector * 100) / 100;

var dispY = parseInt(yVector * 100) / 100;

var dispRot = parseInt(theta * 100) / 100;

var status = "Velocity X: " + dispX + " Y: " + dispY + " Rot: " + dispRot;

ctx.font="18pt Helvetica";

ctx.fillText(status, 285, 100);

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

181

Page 182: HTML Canvas Guide

}

function drawFuel() {

ctx.shadowColor="rgba(0,0,0,0)";

ctx.font="14pt Helvetica";

ctx.globalAlpha = 0.5;

ctx.fillText("Fuel:", 52, 375);

ctx.fillRect(100, 360, fuel, 20);

ctx.globalAlpha = 1;

}

function drawSprite() {

ctx.save();

ctx.translate(x,y);

scale = 0.5 + (y / 600);

offset = -1 * lemW * scale / 2

ctx.shadowColor = "black";

ctx.shadowOffsetX= 10;

ctx.shadowOffsetY= -5;

ctx.rotate(theta);

ctx.drawImage(sprite, offset, offset, lemW * scale, lemH * scale);

ctx.restore();

}

function addGravity() {

yVector = yVector + gravity * scale;

}

function gameOver() {

gameover = 1;

clearTimeout(t);

clearInterval(interval);

if (y == bottom && yVector < 1 && Math.abs(xVector) < 1 && Math.abs(theta) <.1) {

theta = 0;

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

182

Page 183: HTML Canvas Guide

drawAll();

ctx.drawImage(flag, x -50, y -120);

ctx.font="14pt Helvetica";

ctx.fillStyle="green";

ctx.fillText("Nice landing!", x, 280);

sound.src="chord.m4a";

} else {

theta = 1.75;

drawAll();

ctx.font="14pt Helvetica";

ctx.fillStyle="red";

ctx.fillText("Oops. Hard landing.", x, 280);

sound.src="ohno.m4a"

}

sound.play();

}

function update() {

x = x + xVector;

y = y + yVector;

if (y >= bottom) {

y = bottom;

gameOver();

} else {

t = setTimeout("update()", delay);

drawAll();

}

}

function burn() {

if (fuel > 0 && y < bottom) {

clearTimeout(t);

yVector = yVector - (thrust * scale * Math.cos(theta));

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

183

Page 184: HTML Canvas Guide

xVector = xVector + (thrust * scale * Math.sin(theta));

fuel = fuel - 5;

ctx.save();

ctx.globalAlpha = .75;

ctx.shadowColor="rgba(0,0,0,0)";

ctx.translate(x,y);

ctx.rotate(theta);

ctx.drawImage(flames, offset, offset, lemW * scale, lemH * scale);

ctx.restore();

t = setTimeout("update()", delay);

if (thrustIsDown) {

sound.play();

setTimeout("burn()", 200);

}

}

}

function mouseDown() {

mouseIsDown = 1;

}

function mouseUp() {

mouseIsDown = 0;

}

function thrustDown() {

sound.play();

thrustIsDown = 1;

thrustButton.className="myButton pressed";

burn();

}

function thrustUp() {

thrustIsDown = 0;

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

184

Page 185: HTML Canvas Guide

thrustButton.className="myButton";

}

function mouseXY(e) {

if (mouseIsDown) {

if (!e) var e = event;

var mouseX = e.pageX - slider.offsetLeft;

if (mouseX >= 0 && mouseX <= slider.offsetWidth ) {

setKnob(mouseX);

}

}

}

function touchXY(e) {

if (!e) var e = event;

// slide, don't scroll

e.preventDefault();

var touchX = e.touches[0].pageX - slider.offsetLeft;

if (touchX >= 0 && touchX <= slider.offsetWidth) {

setKnob(touchX);

}

}

function setKnob(x) {

var knobX = x - knobMid;

knobX = Math.max(knobX, 0);

knobX = Math.min(knobX, slider.offsetWidth - knob.offsetWidth);

knob.style.left = knobX;

if (!gameover) {

theta = (x - 75) / 75;

drawSprite();

}

}

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

185

Page 186: HTML Canvas Guide

</script>

</head>

<body onload="init()">

<img id="back" src="lunarsurface.jpg" style="display:none">

<img id="sprite" src="lem.png" style="display:none">

<img id="flames" src="flames.png" style="display:none">

<img id="flag" src="flag.png" style="display:none">

<audio id="sound" src="noise.m4a">

</audio>

<canvas id="can" height="436" width="810">

</canvas>

<div id="thrustButton" class="myButton"

onmousedown="thrustDown()" onmouseup="thrustUp()"

ontouchstart="thrustDown()" ontouchend="thrustUp()">

Thrust

</div>

<div class="slider" id="slider"

onmousedown="mouseDown()" onmousemove="mouseXY()"

ontouchstart="touchXY()" ontouchmove="touchXY()">

<div class="bar">

</div>

<div id="knob" class="knob" >

<br>Spin

</div>

</div>

<br>

<h2 style="text-align:center">

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

186

Page 187: HTML Canvas Guide

To win, land with velocity X < 1, Y < 1, Rot < 0.1

</h2>

<input type="button" value="Restart" onclick="restart()" >

</body>

</html>

Creating GamesLoony Lander Game

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

187

Page 188: HTML Canvas Guide

On the desktop, you can display video on canvas by calling drawImage() using a video element as the imagesource. The frame currently under the playhead is rendered on the canvas. If you call the play() method onthe video, and call drawImage() at frequent intervals, the video is played back smoothly on the canvas. Youcan play a video without displaying it outside of the canvas by setting the video element’s display attributeto none.

Note Video as a source for the canvas drawImage() method is not currently supported on iOS.

Using video as a source for drawImage() involves a lot of system resources. Generally speaking, video is bestdisplayed using the video element, not the canvas element. To composite canvas text or animations overmoving video, it’s better to use a video element behind the canvas—the video shows through the transparentbackground of the canvas without the overhead of displaying video on the canvas itself.

On iOS-based devices with small screens—such as iPhone and iPod touch—video always plays in fullscreenmode, so the canvas cannot be superimposed on playing video. On iOS-based devices with larger screens,such as iPad, you can superimpose canvas graphics on playing video, just as you can on the desktop.

Use video as an image source for the canvas only when you need to access the pixel data of a video. Video oncanvas is useful for things such as realtime image processing—green screen effects, extracting the averagecolor value from a video frame, capturing a series of stills from a video—or special effects such as puttingsegments of a moving video image on tiles and moving them independently.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

188

Putting Video on Canvas

Page 189: HTML Canvas Guide

Canvas Over VideoWhen you want to superimpose canvas graphics or animation on a video, the best approach is to position acanvas element on top of a video element, so the video shows through the transparent parts of the canvas.The example in Listing 17-1 plays an MPEG-4 video stream behind the canvas, with spinning text overlaid onthe video. A simple play/pause button controls the video, and the animation is tied to the video. The outputis illustrated in Figure 17-1.

Figure 17-1 Video under canvas

Listing 17-1 Displaying video behind the canvas

<html>

<head>

<meta name = "viewport" content = "width = device-width">

<title>Canvas Over Video</title>

<script type="text/javascript">

var can;

var ctx;

var vid;

var playButton;

var vidTimer;

var text;

var angle = 0;

function init() {

vid = document.getElementById("vid");

playButton = document.getElementById("play");

Putting Video on CanvasCanvas Over Video

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

189

Page 190: HTML Canvas Guide

can = document.getElementById("can");

ctx = can.getContext('2d');

vid.addEventListener('ended', vidEnd, false);

vid.addEventListener('play', setAnimate, false);

ctx.fillStyle = "white";

ctx.strokeStyle = "black";

ctx.font = "20pt Helvetica";

ctx.textAlign = "center";

ctx.textBaseline = "middle" ;

animate();

}

// Draw spinning text

function animate() {

ctx.save();

// clear screen

ctx.clearRect(0,0,can.width,can.height);

// set origin at center of canvas

ctx.translate(can.width / 2, can.height / 2);

// spin the canvas

ctx.rotate(angle);

// draw white text with a black outline

ctx.fillText ("Animation over video!", 0,0);

ctx.strokeText ("Animation over video!", 0,0);

ctx.restore();

// increment rotation angle, reset to 0 at 2 * Pi

angle = angle + 0.1;

if (angle>6.28) angle = 0;

// if video not paused or ended, repeat in 50 msec

var vidState = document.querySelector("video");

if (!vidState.paused && !vidState.ended)

vidTimer = setTimeout("animate();", 50);

}

Putting Video on CanvasCanvas Over Video

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

190

Page 191: HTML Canvas Guide

// Start animation when video starts playing

function setAnimate() {

clearTimeout(vidTimer);

vidTimer = setTimeout("animate();", 50);

}

function playVideo() {

if (playButton.value=="Play") {

vid.play();

play.value = "Pause";

}

else {

vid.pause();

playButton.value = "Play";

}

}

function vidEnd() {

playButton.value = "Play";

clearTimeout(vidTimer);

}

</script>

</head>

<body onload="init()">

<video src="assets/myMovie.m4v" id="vid"

style="position:absolute; top: 10; left: 10;"

>

</video>

<canvas id="can" height="270" width="480"

style="position:absolute; top: 10; left: 10;"

>

Putting Video on CanvasCanvas Over Video

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

191

Page 192: HTML Canvas Guide

</canvas>

<p style="position:absolute; top: 300; left: 50;">

<input type="button" value="Play" onclick="playVideo()" id="play"

style="font:18pt Helvetica">

</p>

</body>

</html>

Tiled VideoThere are a number of effects you can implement by displaying a video as a collection of independent tiles,such as explosions, particle effects, and moving jigsaw puzzles. You map parts of a video onto tiles by callingdrawImage(sourceX,sourceY,sourceWidth,sourceHeight, destX,destY,destWidth,destHeight).See “Drawing an Image with Region Mapping” (page 44) for details.

The example in Listing 17-2 divides a video image into four tiles and rotates each tile independently, asillustrated in Figure 17-2.

Figure 17-2 Rotating video tiles

Putting Video on CanvasTiled Video

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

192

Page 193: HTML Canvas Guide

Listing 17-2 Breaking video into tiles

<html>

<head>

<meta name = "viewport" content = "width = device-width">

<title>Tiled Video</title>

<script type="text/javascript">

var can;

var ctx;

var vid;

var playButton;

var vidTimer;

var angle = -1;

var imageWidth = 480;

var imageHeight = 270;

var tWide;

var tHigh;

// Offsets to rotate tiles around centers

var xOffset;

var yOffset;

// Offsets to center tiles on canvas

var topOffset = 72;

var leftOffset = 22;

var vidState;

function init() {

vid = document.getElementById("vid");

vidState = document.querySelector("video");

playButton = document.getElementById("play");

can = document.getElementById("can");

ctx = can.getContext('2d');

vid.addEventListener('ended', vidEnd, false);

vid.addEventListener('play', setAnimate, false);

// Tile width and height (divide image into 4 tiles)

Putting Video on CanvasTiled Video

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

193

Page 194: HTML Canvas Guide

tWide = imageWidth / 2;

tHigh = imageHeight / 2;

// Offset from center of tile image to upper left corner

xOffset = imageWidth / -4 ;

yOffset = imageHeight / -4 ;

animate();

}

// If video not paused, draw image in 4 tiles,

// rotating each tile about the center

function animate() {

if (!vidState.paused) {

// If video ended,draw image once with no rotation

if (vidState.ended) angle = 0;

// If angle is negative, increment but display without rotation

var dispAngle = Math.max(0,angle);

// Clear screen

ctx.clearRect(0,0,can.width,can.height);

// Draw four tiles, rotated about the center

ctx.save();

ctx.translate(tWide / 2 + leftOffset, tHigh / 2 + topOffset);

ctx.rotate(dispAngle);

ctx.drawImage(vid,0,0,tWide,tHigh,xOffset,yOffset,tWide,tHigh);

ctx.restore();

ctx.save();

ctx.translate(tWide * 3 / 2 + leftOffset, tHigh / 2 + topOffset);

ctx.rotate(dispAngle);

ctx.drawImage(vid,tWide,0,tWide,tHigh,xOffset,yOffset,tWide,tHigh);

ctx.restore();

ctx.save();

ctx.translate(tWide / 2 + leftOffset, tHigh * 3 / 2 + topOffset);

ctx.rotate(dispAngle);

ctx.drawImage(vid,0,tHigh,tWide,tHigh,xOffset,yOffset,tWide,tHigh);

ctx.restore();

Putting Video on CanvasTiled Video

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

194

Page 195: HTML Canvas Guide

ctx.save();

ctx.translate(tWide * 3 / 2 + leftOffset, tHigh * 3 / 2 + topOffset);

ctx.rotate(dispAngle);

ctx.drawImage(vid,tWide,tHigh,tWide,tHigh,xOffset,yOffset,tWide,tHigh);

ctx.restore();

// Increment angle

angle = angle + 0.02;

// If at full circle, reset angle

// and display without rotation for fifty cycles

if (angle>6.28) angle = -1;

// If video not paused (and video not ended), repeat animation in 50 ms

if (!vidState.ended)

vidTimer = setTimeout("animate();", 50);

}

}

// Set display angle to 0 for fifty cycles

function reset() {

angle = -1;

}

// Start animation when video starts playing

function setAnimate() {

clearTimeout(vidTimer);

vidTimer = setTimeout("animate();", 50);

}

function playVideo() {

if (playButton.value=="Play") {

vid.play();

play.value = "Pause";

}

else {

vid.pause();

Putting Video on CanvasTiled Video

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

195

Page 196: HTML Canvas Guide

playButton.value = "Play";

}

}

function vidEnd() {

playButton.value = "Play";

}

</script>

</head>

<body onload="init()">

<video src="assets/myMovie.m4v" id="vid"

style="display:none" >

</video>

<canvas id="can" height="420" width="522" >

</canvas>

<hr>

<p>

<input type="button" value="Play" onclick="playVideo()" id="play"

style="font:18pt Helvetica">

<input type="button" value="Reset" onclick="reset()" style="font:18pt Helvetica">

</p>

</body>

</html>

Putting Video on CanvasTiled Video

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

196

Page 197: HTML Canvas Guide

Image ProcessingIt’s possible to do realtime image processing on video by displaying the video on canvas and manipulatingthe pixels of the canvas bitmap. The canvas bitmap is accessed by calling getImageData(x,y,width,height) and accessing the imageData object’s data property, which is a pixel array. See “PixelManipulation” (page 166) for details.

The example in Listing 17-3 displays a video stream on the canvas and finds the combined rgb value for eachpixel. The example then uses a slider to control the opacity of only the dark pixels (those with a combined RGBvalue of less than 150). You can drag the slider while the video is playing and the alpha value of the dark areasof each frame changes dynamically. The results are illustrated in Figure 17-3.

Figure 17-3 Image processing

Listing 17-3 Realtime image processing

<html>

<head>

<title>Realtime Image Processing</title>

<style>

canvas {

border-radius: 25;

border: 1 px solid #404040;

}

.slider {

Putting Video on CanvasImage Processing

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

197

Page 198: HTML Canvas Guide

position: absolute;

top: 300;

left:85;

width: 152;

height = 52;

}

.bar {

position: relative;

top:30;

width: 152;

height: 2 px;

background-color: #404040 ;

}

.knob {

position: relative;

left:100;

border: 1 px solid #404040;

background-color: #C0C0C0 ;

width:50 px;

height: 50 px;

border-radius: 25 px;

text-align:center;

}

</style>

<script type="text/javascript">

var can;

var ctx;

var vid;

var play;

Putting Video on CanvasImage Processing

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

198

Page 199: HTML Canvas Guide

var vidTimer;

var mouseIsDown = 0;

var knobMid;

var pixData;

var pixels;

var knobX = 100;

function init() {

vid = document.getElementById("vid");

play = document.getElementById("play");

can = document.getElementById("can");

ctx = can.getContext('2d');

vid.addEventListener('ended', vidEnd, false);

vid.addEventListener('play', vidSet, false);

knobMid = knob.offsetWidth / 2;

showVid();

}

function vidSet() {

clearTimeout(vidTimer);

vidTimer = setTimeout("showVid()", 25);

}

function showVid() {

ctx.drawImage(vid, 0,0);

processImage();

if (!document.querySelector("video").paused)

// Repeat 40 times a second to oversample 30 fps video

vidTimer = setTimeout("showVid()", 25);

}

function processImage() {

// get pixel data for entire canvas

pixels=ctx.getImageData(0,0,can.width,can.height);

Putting Video on CanvasImage Processing

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

199

Page 200: HTML Canvas Guide

pixData=pixels.data;

// set alpha value using slider

var alphaVal = parseInt(knobX * 2.55)

// for each pixel

for (i=0;i<can.width * can.height;i++) {

// get combined rgb value to determine brightness

var rgbVal = pixData[i*4] + pixData[i*4 + 1] + pixData[i*4 + 2];

// set alpha value for dark pixels to knob value

if (rgbVal < 150)

pixData[i*4 +3]=alphaVal;

}

// put modified data back into image object

pixels.data = pixData;

// blit modified image object to screen

ctx.putImageData(pixels,0,0);

}

function playPauseVideo() {

if (play.value=="Play") {

vid.play();

play.value = "Pause";

}

else {

vid.pause();

play.value = "Play";

}

}

function vidEnd() {

console.log(playing);

play.value = "Play";

}

Putting Video on CanvasImage Processing

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

200

Page 201: HTML Canvas Guide

function mouseDown() {

mouseIsDown = 1;

mouseXY();

}

function mouseUp() {

mouseIsDown = 0;

}

function mouseXY(e) {

if (mouseIsDown) {

if (!e) var e = event;

var mouseX = e.pageX - slider.offsetLeft;

if (mouseX >= 0 && mouseX <= slider.offsetWidth ) {

setKnob(mouseX);

}

}

}

function touchXY(e) {

if (!e) var e = event;

// slide, don't scroll

e.preventDefault();

var touchX = e.touches[0].pageX - slider.offsetLeft;

if (touchX >= 0 && touchX <= slider.offsetWidth) {

setKnob(touchX);

}

}

function setKnob(x) {

knobX = x - knobMid;

knobX = Math.max(knobX, 0);

knobX = Math.min(knobX, slider.offsetWidth - knob.offsetWidth);

knob.style.left = knobX;

}

Putting Video on CanvasImage Processing

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

201

Page 202: HTML Canvas Guide

</script>

</head>

<body onload="init()" onmouseup="mouseUp()">

<canvas id="can" height="270" width="480"

</canvas>

<p>

<input type="button" value="Play" onclick="playPauseVideo()" id="play"

style="font:18 pt Helvetica">

</p>

<video src="assets/myMovie.m4v" id="vid" style="display:none">

</video>

<div class="slider" id="slider"

onmousedown="mouseDown()" onmousemove="mouseXY()"

ontouchstart="touchXY()" ontouchmove="touchXY()">

<div class="bar">

</div>

<div id="knob" class="knob">

<br>

Alpha

</div>

</div>

</body>

</html>

Putting Video on CanvasImage Processing

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

202

Page 203: HTML Canvas Guide

This table describes the changes to Safari HTML5 Canvas Guide .

NotesDate

Revised and expanded code examples.2011-03-14

Modified code listings.2011-03-08

New document explains how to use the HTML5 <canvas> element forgraphics, animations, and games.

2011-03-14 | © 2011 Apple Inc. All Rights Reserved.

203

Document Revision History

Page 204: HTML Canvas Guide

Apple Inc.© 2011 Apple Inc.All rights reserved.

No part of this publication may be reproduced,stored in a retrieval system, or transmitted, in anyform or by any means, mechanical, electronic,photocopying, recording, or otherwise, withoutprior written permission of Apple Inc., with thefollowing exceptions: Any person is herebyauthorized to store documentation on a singlecomputer for personal use only and to printcopies of documentation for personal useprovided that the documentation containsApple’s copyright notice.

The Apple logo is a trademark of Apple Inc.

No licenses, express or implied, are granted withrespect to any of the technology described in thisdocument. Apple retains all intellectual propertyrights associated with the technology describedin this document. This document is intended toassist application developers to developapplications only for Apple-labeled computers.

Apple Inc.1 Infinite LoopCupertino, CA 95014408-996-1010

Apple, the Apple logo, iPhone, iPod, iPod touch,iTunes, Keynote, Mac, QuickTime, and Safari aretrademarks of Apple Inc., registered in the UnitedStates and other countries.

iPad is a trademark of Apple Inc.

Helvetica is a registered trademark ofHeidelberger Druckmaschinen AG, available fromLinotype Library GmbH.

IOS is a trademark or registered trademark ofCisco in the U.S. and other countries and is usedunder license.

Java is a registered trademark of Oracle and/orits affiliates.

Even though Apple has reviewed this document,APPLE MAKES NO WARRANTY OR REPRESENTATION,EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THISDOCUMENT, ITS QUALITY, ACCURACY,MERCHANTABILITY, OR FITNESS FOR A PARTICULARPURPOSE. AS A RESULT, THIS DOCUMENT IS PROVIDED“AS IS,” AND YOU, THE READER, ARE ASSUMING THEENTIRE RISK AS TO ITS QUALITY AND ACCURACY.

IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIALDAMAGES RESULTING FROM ANY DEFECT ORINACCURACY IN THIS DOCUMENT, even if advised ofthe possibility of such damages.

THE WARRANTY AND REMEDIES SET FORTH ABOVEARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORALOR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,agent, or employee is authorized to make anymodification, extension, or addition to this warranty.

Some states do not allow the exclusion or limitationof implied warranties or liability for incidental orconsequential damages, so the above limitation orexclusion may not apply to you. This warranty givesyou specific legal rights, and you may also have otherrights which vary from state to state.