You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 17_canvas.md
+27-29Lines changed: 27 additions & 29 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,7 +14,7 @@ quote}}
14
14
15
15
{{index CSS, "transform (CSS)", [DOM, graphics]}}
16
16
17
-
Browsers give us several ways to display ((graphics)). The simplest way is to use styles to position and color regular DOM elements. This can get you quite far, as the game in the [previous chapter](game) showed. By adding partially transparent background ((image))s to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes with the `transform` style.
17
+
Browsers give us several ways to display ((graphics)). The simplest way is to use styles to position and color regular DOM elements. This can get us quite far, as the game in the [previous chapter](game) showed. By adding partially transparent background ((image))s to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes with the `transform` style.
18
18
19
19
But we'd be using the DOM for something that it wasn't originally designed for. Some tasks, such as drawing a ((line)) between arbitrary points, are extremely awkward to do with regular HTML elements.
20
20
@@ -28,7 +28,7 @@ The second alternative is called a _((canvas))_. A canvas is a single DOM elemen
28
28
29
29
## SVG
30
30
31
-
This book will not go into ((SVG)) in detail, but I will briefly explain how it works. At the [end of the chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs that you must consider when deciding which ((drawing)) mechanism is appropriate for a given application.
31
+
This book won't go into ((SVG)) in detail, but I'll briefly explain how it works. At the [end of the chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs that you must consider when deciding which ((drawing)) mechanism is appropriate for a given application.
32
32
33
33
This is an HTML document with a simple SVG ((picture)) in it:
34
34
@@ -104,19 +104,19 @@ if}}
104
104
105
105
{{index SVG, coordinates}}
106
106
107
-
Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-((axis)) goes down from there. So (10,10) is 10 pixels below and to the right of the top-left corner.
107
+
Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-((axis)) goes down from there. This means (10,10) is 10 pixels below and to the right of the top-left corner.
108
108
109
109
{{id fill_stroke}}
110
110
111
111
## Lines and surfaces
112
112
113
113
{{index filling, stroking, drawing, SVG}}
114
114
115
-
In the ((canvas)) interface, a shape can be _filled_, meaning its area is given a certain color or pattern, or it can be _stroked_, which means a ((line)) is drawn along its edge. The same terminology is used by SVG.
115
+
In the ((canvas)) interface, a shape can be _filled_, meaning its area is given a certain color or pattern, or it can be _stroked_, which means a ((line)) is drawn along its edge. SVG uses the same terminology.
116
116
117
117
{{index "fillRect method", "strokeRect method"}}
118
118
119
-
The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, and then its height. A similar method called `strokeRect` draws the ((outline)) of a rectangle.
119
+
The `fillRect` method fills a ((rectangle)). It takes first the x- and y-((coordinates)) of the rectangle's top-left corner, then its width, then its height. A similar method called `strokeRect` draws the ((outline)) of a rectangle.
120
120
121
121
{{index [state, "of canvas"]}}
122
122
@@ -200,7 +200,7 @@ When filling a path (using the `fill` method), each ((shape)) is filled separate
200
200
</script>
201
201
```
202
202
203
-
This example draws a filled triangle. Note that only two of the triangle's sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn't be there when you stroke the path.
203
+
This example draws a filled triangle. Note that only two of the triangle's sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn't be there if you stroked the path.
204
204
205
205
{{if book
206
206
@@ -250,7 +250,7 @@ We draw a ((quadratic curve)) from the left to the right, with (60,10) as contro
250
250
251
251
{{index canvas, "bezierCurveTo method"}}
252
252
253
-
The `bezierCurveTo` method draws a similar kind of curve. Instead of a single ((control point)), this one has two—one for each of the ((line))'s endpoints. Here is a similar sketch to illustrate the behavior of such a curve:
253
+
The `bezierCurveTo` method draws a similar kind of curve. Instead of a single ((control point)), this method has two—one for each of the ((line))'s endpoints. Here is a similar sketch to illustrate the behavior of such a curve:
254
254
255
255
```{lang: html}
256
256
<canvas></canvas>
@@ -302,21 +302,23 @@ Those last two parameters make it possible to draw only part of the circle. The
The resulting picture contains a ((line)) from the right of the full circle (first call to `arc`) to the right of the quarter-((circle)) (second call). Like other path-drawing methods, a line drawn with `arc` is connected to the previous path segment. You can call `moveTo` or start a new path to avoid this.
305
+
The resulting picture contains a ((line)) from the right of the full circle (first call to `arc`) to the right of the quarter-((circle)) (second call).
306
306
307
307
{{if book
308
308
309
309
{{figure {url: "img/canvas_circle.png", alt: "Screenshot of a circle", width: "4.9cm"}}}
310
310
311
311
if}}
312
312
313
+
Like other path-drawing methods, a line drawn with `arc` is connected to the previous path segment.You can call `moveTo` or start a new path to avoid this.
314
+
313
315
{{id pie_chart}}
314
316
315
317
## Drawing a pie chart
316
318
317
319
{{index "pie chart example"}}
318
320
319
-
Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your first assignment is to draw a pie chart of its customer satisfaction ((survey)) results.
321
+
Imagine we've just taken a ((job)) at EconomiCorp, Inc. Your first assignment is to draw a pie chart of its customer satisfaction ((survey)) results.
320
322
321
323
The `results` binding contains an array of objects that represent the survey responses.
322
324
@@ -400,7 +402,7 @@ In computer ((graphics)), a distinction is often made between _vector_ graphics
The `drawImage` method allows us to draw ((pixel)) data onto a ((canvas)). This pixel data can originate from an `<img>` element or from another canvas. The following example creates a detached `<img>` element and loads an image file into it. But it cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a `"load"` event handler and do the drawing after the image has loaded.
405
+
The `drawImage` method allows us to draw ((pixel)) data onto a ((canvas)). This pixel data can originate from an `<img>` element or from another canvas. The following example creates a detached `<img>` element and loads an image file into it. But the method cannot immediately start drawing from this picture because the browser may not have loaded it yet. To deal with this, we register a `"load"` event handler and do the drawing after the image has loaded.
404
406
405
407
```{lang: html}
406
408
<canvas></canvas>
@@ -418,13 +420,13 @@ The `drawImage` method allows us to draw ((pixel)) data onto a ((canvas)). This
418
420
419
421
{{index "drawImage method", scaling}}
420
422
421
-
By default, `drawImage` will draw the image at its original size. You can also give it two additional arguments to set a different width and height.
423
+
By default, `drawImage` will draw the image at its original size. You can also give it two additional arguments to specify the width and height of the drawn image, when those aren't the same as origin image.
422
424
423
425
When `drawImage` is given _nine_ arguments, it can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source image that should be copied, and the sixth to ninth arguments give the rectangle (on the canvas) into which it should be copied.
424
426
425
427
{{index "player", "pixel art"}}
426
428
427
-
This can be used to pack multiple _((sprite))s_ (image elements) into a single image file and then draw only the part you need. For example, we have this picture containing a game character in multiple ((pose))s:
429
+
This can be used to pack multiple _((sprite))s_ (image elements) into a single image file and then draw only the part you need. For example, this picture contains a game character in multiple ((pose))s:
428
430
429
431
{{figure {url: "img/player_big.png", alt: "Pixel art showing a computer game character in 10 different poses. The first 8 form its running animation cycle, the 9th has the character standing still, and the 10th shows him jumping.", width: "6cm"}}}
430
432
@@ -472,7 +474,7 @@ The `cycle` binding tracks our position in the animation. For each ((frame)), it
472
474
473
475
{{indexsee flipping, mirroring}}
474
476
475
-
But what if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we can also instruct the ((canvas)) to draw the picture the other way round.
477
+
What if we want our character to walk to the left instead of to the right? We could draw another set of sprites, of course. But we could also instruct the ((canvas)) to draw the picture the other way round.
476
478
477
479
{{index "scale method", scaling}}
478
480
@@ -504,15 +506,15 @@ Scaling will cause everything about the drawn image, including the ((line width)
504
506
505
507
{{index "drawImage method"}}
506
508
507
-
So to turn a picture around, we can't simply add `cx.scale(-1, 1)` before the call to `drawImage` because that would move our picture outside of the ((canvas)), where it won't be visible. You could adjust the ((coordinates)) given to `drawImage` to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn't require the code that does the drawing to know about the scale change, is to adjust the ((axis)) around which the scaling happens.
509
+
To turn a picture around, we can't simply add `cx.scale(-1, 1)` before the call to `drawImage`. That would move our picture outside of the ((canvas)), where it won't be visible. We could adjust the ((coordinates)) given to `drawImage` to compensate for this by drawing the image at x position -50 instead of 0. Another solution, which doesn't require the code doing the drawing to know about the scale change, is to adjust the ((axis)) around which the scaling happens.
There are several other methods besides `scale` that influence the coordinate system for a ((canvas)). You can rotate subsequently drawn shapes with the `rotate` method and move them with the `translate` method. The interesting—and confusing—thing is that these transformations _stack_, meaning that each one happens relative to the previous transformations.
512
514
513
515
{{index "rotate method", "translate method"}}
514
516
515
-
So if we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 ((degree))s (about 0.1π ((radian))s), that rotation will happen _around_ point (50,50).
517
+
If we translate by 10 horizontal pixels twice, everything will be drawn 20 pixels to the right. If we first move the center of the coordinate system to (50,50) and then rotate by 20 ((degree))s (about 0.1π ((radian))s), that rotation will happen _around_ point (50,50).
516
518
517
519
{{figure {url: "img/transform.svg", alt: "Diagram showing the result of stacking transformations. The first diagram translates and then rotates, causing the translation to happen normally and rotation to happen around the target of the translation. The second diagram first rotates, and then translates, causing the rotation to happen around the origin and the translation direction to be tilted by that rotation.", width: "9cm"}}}
518
520
@@ -623,7 +625,7 @@ We define another display object type called `CanvasDisplay`, supporting the sam
623
625
624
626
{{index [state, "in objects"]}}
625
627
626
-
This object keeps a little more information than `DOMDisplay`. Rather than using the scroll position of its DOM element, it tracks its own ((viewport)), which tells us what part of the level we are currently looking at. Finally, it keeps a `flipPlayer` property so that even when the player is standing still, it keeps facing the direction it last moved in.
628
+
This object keeps a little more information than `DOMDisplay`. Rather than using the scroll position of its DOM element, it tracks its own ((viewport)), which tells us which part of the level we are currently looking at. Finally, it keeps a `flipPlayer` property so that even when the player is standing still, it keeps facing the direction in which it last moved.
627
629
628
630
```{sandbox: "game", includeCode: true}
629
631
class CanvasDisplay {
@@ -748,11 +750,11 @@ Tiles that are not empty are drawn with `drawImage`. The `otherSprites` image co
748
750
749
751
{{index scaling}}
750
752
751
-
Background tiles are 20 by 20 pixels since we will use the same scale that we used in `DOMDisplay`. Thus, the offset for lava tiles is 20 (the value of the `scale` binding), and the offset for walls is 0.
753
+
Background tiles are 20 by 20 pixels, since we'll use the same scale as in `DOMDisplay`. Thus, the offset for lava tiles is 20 (the value of the `scale` binding), and the offset for walls is 0.
We don't bother waiting for the sprite image to load. Calling `drawImage` with an image that hasn't been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few ((frame))s, while the image is still loading, but that is not a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes.
757
+
We don't bother waiting for the sprite image to load. Calling `drawImage` with an image that hasn't been loaded yet will simply do nothing. Thus, we might fail to draw the game properly for the first few ((frame))s while the image is still loading, but that isn't a serious problem. Since we keep updating the screen, the correct scene will appear as soon as the loading finishes.
@@ -818,7 +820,7 @@ When ((drawing)) something that is not the ((player)), we look at its type to fi
818
820
819
821
{{index viewport}}
820
822
821
-
We have to subtract the viewport's position when computing the actor's position since (0,0) on our ((canvas)) corresponds to the top left of the viewport, not the top left of the level. We could also have used `translate` for this. Either way works.
823
+
We have to subtract the viewport's position when computing the actor's position, since (0,0) on our ((canvas)) corresponds to the top left of the viewport, not the top left of the level. We could also have used `translate` for this. Either way works.
822
824
823
825
{{if interactive
824
826
@@ -848,7 +850,7 @@ if}}
848
850
849
851
## Choosing a graphics interface
850
852
851
-
Thus, when you need to generate graphics in the browser, you can choose between plain HTML, ((SVG)), and ((canvas)). There is no single _best_ approach that works in all situations. Each option has strengths and weaknesses.
853
+
When you need to generate graphics in the browser, you can choose between plain HTML, ((SVG)), and ((canvas)). There is no single _best_ approach that works in all situations. Each option has strengths and weaknesses.
852
854
853
855
{{index "text wrapping"}}
854
856
@@ -862,19 +864,15 @@ SVG can be used to produce ((crisp)) ((graphics)) that look good at any zoom lev
862
864
863
865
Both SVG and HTML build up a data structure (the DOM) that represents your picture. This makes it possible to modify elements after they are drawn. If you need to repeatedly change a small part of a big ((picture)) in response to what the user is doing or as part of an ((animation)), doing it in a canvas can be needlessly expensive. The DOM also allows us to register mouse event handlers on every element in the picture (even on shapes drawn with SVG). You can't do that with canvas.
864
866
865
-
{{index performance, optimization}}
866
-
867
-
But ((canvas))'s ((pixel))-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape.
868
-
869
-
{{index "ray tracer"}}
867
+
{{index performance, optimization, "ray tracer"}}
870
868
871
-
There are also effects, such as rendering a scene one ((pixel)) at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that are only practical with a canvas element.
869
+
But ((canvas))'s ((pixel))-oriented approach can be an advantage when drawing a huge number of tiny elements. The fact that it does not build up a data structure but only repeatedly draws onto the same pixel surface gives canvas a lower cost per shape. There are also effects that are only practical with a canvas element, such as rendering a scene one ((pixel)) at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it).
872
870
873
871
In some cases, you may want to combine several of these techniques. For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but show ((text))ual information by positioning an HTML element on top of the picture.
874
872
875
873
{{index display}}
876
874
877
-
For nondemanding applications, it really doesn't matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three ((graphics)) technologies since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements.
875
+
For nondemanding applications, it really doesn't matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three ((graphics)) technologies, since it does not need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements.
878
876
879
877
## Summary
880
878
@@ -918,7 +916,7 @@ Write a program that draws the following ((shape))s on a ((canvas)):
918
916
919
917
{{figure {url: "img/exercise_shapes.png", alt: "Picture showing the shapes you are asked to draw", width: "8cm"}}}
920
918
921
-
When drawing the last two, you may want to refer to the explanation of `Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes how to get coordinates on a circle using these functions.
919
+
When drawing the last two shapes, you may want to refer to the explanation of `Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes how to get coordinates on a circle using these functions.
922
920
923
921
{{index readability, "hard-coding"}}
924
922
@@ -1077,7 +1075,7 @@ One unfortunate thing about ((transformation))s is that they slow down the drawi
1077
1075
1078
1076
In a game like ours, where we are drawing only a single transformed sprite, this is a nonissue. But imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion.
1079
1077
1080
-
Think of a way to allow us to draw an inverted character without loading additional image files and without having to make transformed `drawImage` calls every frame.
1078
+
Think of a way to draw an inverted character without loading additional image files and without having to make transformed `drawImage` calls every frame.
0 commit comments