๐Ÿ“œ โฌ†๏ธ โฌ‡๏ธ

Expressive javascript: drawing on canvas

Content




Drawing is a hoax.
M.K. Escher

Browsers allow us to draw graphics in many ways. The easiest way to use styles is to position and color standard DOM elements. So much can be achieved, as the example of the game from the previous chapter showed. By adding partially transparent images to the nodes, we can give them any desired look. It is even possible to rotate or distort nodes through the transform style.
')
But this use of the DOM is not what it was created for. Some tasks, such as drawing a line between two arbitrary points, are extremely inconvenient to perform using ordinary HTML elements.

There are two alternatives. The first is SVG, scalable vector graphics, also based on DOM, but without HTML. SVG is a dialect for describing documents, which concentrates on forms, not text. SVG can be embedded in HTML, or included via a tag.

The second alternative is canvas. A canvas is a single DOM element in which a picture is located. It provides an API for drawing forms on the place that the element occupies. The difference between the canvas and the SVG is that the initial description of the forms is stored in the SVG - they can be shifted or resized at any time. The canvas converts forms to pixels (colored dots of the raster) as soon as it draws them, and does not remember what these pixels are of themselves. The only way to move the shape on the canvas is to clear the canvas (or the part that surrounds the shape) and redraw it elsewhere.

Svg


This book does not go deep into SVG in detail, but I will briefly explain its work. At the end of the chapter, I will return to the comparative shortcomings of the methods that need to be taken into account when choosing a drawing mechanism for a particular application.

Here is an HTML document containing a simple SVG image:

<p>Normal HTML here.</p> <svg xmlns="http://www.w3.org/2000/svg"> <circle r="50" cx="50" cy="50" fill="red"/> <rect x="120" y="5" width="90" height="90" stroke="blue" fill="none"/> </svg> 


The xmlns attribute changes the default namespace of an element. This space is set via the URL and denotes the dialect we are speaking. Tags and those that do not exist in HTML make sense in SVG โ€” they draw forms using the style and position specified by their attributes.

They create DOM elements in the same way as HTML tags. For example, this code changes the element color to cyan:

var circle = document.querySelector ("circle");
circle.setAttribute ("fill", "cyan");

Canvas element


Canvas graphics can be drawn on an element
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>


. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>


. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>


. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>


. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
 .      ,       . 

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.
. , .

, .

. , context โ€“ , . : โ€œ2dโ€ โ€œwebglโ€ OpenGL.

WebGL , . , WebGL. , JavaScript.

Context getContext .

<p>Before canvas.</p> <canvas width="120" height="60"></canvas> <p>After canvas.</p> <script> var canvas = document.querySelector("canvas"); var context = canvas.getContext("2d"); context.fillStyle = "red"; context.fillRect(10, 10, 100, 50); </script>


context 100 50, (10, 10).

HTML ( SVG), (0, 0) , Y . , (10,10) 10 .


, , , stroke โ€“ . SVG.

fillRect . x,y, . strokeRect .

. , ( ), context.

fillStyle, , . , , , CSS.

strokeStyle , , . lineWidth, .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.strokeStyle = "blue"; cx.strokeRect(5, 5, 50, 50); cx.lineWidth = 5; cx.strokeRect(135, 5, 50, 50); </script>

width height, โ€“ 300 150 .


โ€“ . . . โ€“ , . , - , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); for (var y = 10; y < 100; y += 10) { cx.moveTo(10, y); cx.lineTo(90, y); } cx.stroke(); </script>

, stroke. , lineTo, . โ€“ , moveTo. , moveTo.

. โ€“ moveTo . ( ), . , , , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(50, 10); cx.lineTo(10, 70); cx.lineTo(90, 70); cx.fill(); </script>

. , . , , โ€“ stroke.

closePath, , . stroke.


. , .

quadraticCurveTo . . , , . . . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control=(60,10) goal=(90,90) cx.quadraticCurveTo(60, 10, 90, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </script>

, (60,10), , . . : , , , .

bezierCurve . โ€“ . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); // control1=(10,10) control2=(90,10) goal=(50,90) cx.bezierCurveTo(10, 10, 90, 10, 50, 90); cx.lineTo(90, 10); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </script>

. , .

โ€“ , , . , .

, , . arcTo . โ€“ quadraticCurveTo. - , โ€“ . . โ€“ , , โ€“ . arcTo , .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=20 cx.arcTo(90, 10, 90, 90, 20); cx.moveTo(10, 10); // control=(90,10) goal=(90,90) radius=80 cx.arcTo(90, 10, 90, 90, 80); cx.stroke(); </script>

arcTo , . lineTo .

, arcTo, 90 . arc . , .

. , . , 2ฯ€, 2 * Math.PI, 6.28. , . , 0, 2ฯ€ ( , 7).

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); // center=(50,50) radius=40 angle=0 to 7 cx.arc(50, 50, 40, 0, 7); // center=(150,50) radius=40 angle=0 to ยฝฯ€ cx.arc(150, 50, 40, 0, 0.5 * Math.PI); cx.stroke(); </script>

( arc), ( ). , . moveTo.



, ยซ ยป, .

result , .

var results = [ {name: "", count: 1043, color: "lightblue"}, {name: "", count: 563, color: "lightgreen"}, {name: " ", count: 510, color: "pink"}, {name: " ", count: 175, color: "silver"} ];

, , . , (2ฯ€) , , .

<canvas width="200" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); // Start at the top var currentAngle = -0.5 * Math.PI; results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); // center=100,100, radius=100 // from current angle, clockwise by slice's angle cx.arc(100, 100, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); cx.fillStyle = result.color; cx.fill(); }); </script>

โ€“ . - .


fillText strokeText. , fillText. fillColor.

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText(" !", 10, 50); </script>

, font. . .

fillText ( strokeText) , . , ยซยป โ€“ . , textAlign "end" "center", โ€“ textBaseline "top", "middle", "bottom".

.


. โ€“ , , . โ€“ , .

drawImage . , . . , . โ€œloadโ€ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", function() { for (var x = 10; x < 200; x += 30) cx.drawImage(img, x, 10); }); </script>

drawImage . .

drawImage , . (x, y, ) , . โ€“ , .

, ( ) , . , :



, .

clearRect. fillRect, , .

, 24 30 . :

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { var cycle = 0; setInterval(function() { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle cycle * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); cycle = (cycle + 1) % 8; }, 120); }); </script>

cycle . 7 , . x, .


, , , ? . , .

scale , . โ€“ .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(50, 50, 40, 0, 7); cx.lineWidth = 3; cx.stroke(); </script>

, . . (0, 0), , . -1, , x = 100, , -100.

, cx.scale(-1, 1) drawImage โ€“ . , drawImage, . , , .

, . rotate translate. , , .

, 10 , 20 . (50, 50), 20 (0.1ฯ€ ), (50, 50).



20 , (50, 50), , . .

x, :

function flipHorizontally(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); }

Y , , , Y . , :



. Y, 1. flipHorizontally , 2. , 3. , . translate โ€“ ยซยป 4.

(100, 0), .

<canvas></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var img = document.createElement("img"); img.src = "img/player.png"; var spriteW = 24, spriteH = 30; img.addEventListener("load", function() { flipHorizontally(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </script>


. , , . .

, -, . , . , , . , , , , . .

save restore . , . save , restore .

branch , , ( , ), .

, , , โ€“ , , . , , 8.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); function branch(length, angle, scale) { cx.fillRect(0, 0, 1, length); if (length < 8) return; cx.save(); cx.translate(0, length); cx.rotate(-angle); branch(length * scale, angle, scale); cx.rotate(2 * angle); branch(length * scale, angle, scale); cx.restore(); } cx.translate(300, 0); branch(60, 0.5, 0.8); </script>

save restore, branch , . , , . , .


, . . drawImage , .

CanvasDisplay, , DOMDisplay 15, , drawFrame and clear.

, DOMDisplay. DOM, , , . , , . flipPlayer, , , .

function CanvasDisplay(parent, level) { this.canvas = document.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.canvas.height = Math.min(450, level.height * scale); parent.appendChild(this.canvas); this.cx = this.canvas.getContext("2d"); this.level = level; this.animationTime = 0; this.flipPlayer = false; this.viewport = { left: 0, top: 0, width: this.canvas.width / scale, height: this.canvas.height / scale }; this.drawFrame(0); } CanvasDisplay.prototype.clear = function() { this.canvas.parentNode.removeChild(this.canvas); };

15 drawFrame - animationTime, , DOMDisplay . drawFrame , .

CanvasDisplay.prototype.drawFrame = function(step) { this.animationTime += step; this.updateViewport(); this.clearDisplay(); this.drawBackground(); this.drawActors(); };

, , , . , , 15, , DOM .

โ€“ , ( ). .

updateViewport scrollPlayerIntoView DOMDisplay. , , .

CanvasDisplay.prototype.updateViewport = function() { var view = this.viewport, margin = view.width / 3; var player = this.level.player; var center = player.pos.plus(player.size.times(0.5)); if (center.x < view.left + margin) view.left = Math.max(center.x - margin, 0); else if (center.x > view.left + view.width - margin) view.left = Math.min(center.x + margin - view.width, this.level.width - view.width); if (center.y < view.top + margin) view.top = Math.max(center.y - margin, 0); else if (center.y > view.top + view.height - margin) view.top = Math.min(center.y + margin - view.height, this.level.height - view.height); };

Math.max Math.min , . Math.max(x, 0) , . Math.min , .

, , .

CanvasDisplay.prototype.clearDisplay = function() { if (this.level.status == "won") this.cx.fillStyle = "rgb(68, 191, 255)"; else if (this.level.status == "lost") this.cx.fillStyle = "rgb(44, 136, 214)"; else this.cx.fillStyle = "rgb(52, 166, 251)"; this.cx.fillRect(0, 0, this.canvas.width, this.canvas.height); };

, , , obstacleAt .

var otherSprites = document.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = function() { var view = this.viewport; var xStart = Math.floor(view.left); var xEnd = Math.ceil(view.left + view.width); var yStart = Math.floor(view.top); var yEnd = Math.ceil(view.top + view.height); for (var y = yStart; y < yEnd; y++) { for (var x = xStart; x < xEnd; x++) { var tile = this.level.grid[y][x]; if (tile == null) continue; var screenX = (x - view.left) * scale; var screenY = (y - view.top) * scale; var tileX = tile == "lava" ? scale : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };

(null) drawImage. otherSprites , . - , .




2020 , , DOMDisplay. , 20 ( scale), 0.

. drawImage . , , . , .

. , . . , animationTime display. , 12 , 12. , . , , , , .

โ€“ 24 16, , x (playerXOverlap).

var playerSprites = document.createElement("img"); playerSprites.src = "img/player.png"; var playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(x, y, width, height) { var sprite = 8, player = this.level.player; width += playerXOverlap * 2; x -= playerXOverlap; if (player.speed.x != 0) this.flipPlayer = player.speed.x < 0; if (player.speed.y != 0) sprite = 9; else if (player.speed.x != 0) sprite = Math.floor(this.animationTime * 12) % 8; this.cx.save(); if (this.flipPlayer) flipHorizontally(this.cx, x + width / 2); this.cx.drawImage(playerSprites, sprite * width, 0, width, height, x, y, width, height); this.cx.restore(); };

drawPlayer drawActors, .

CanvasDisplay.prototype.drawActors = function() { this.level.actors.forEach(function(actor) { var width = actor.size.x * scale; var height = actor.size.y * scale; var x = (actor.pos.x - this.viewport.left) * scale; var y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "player") { this.drawPlayer(x, y, width, height); } else { var tileX = (actor.type == "coin" ? 2 : 1) * scale; this.cx.drawImage(otherSprites, tileX, 0, width, height, x, y, width, height); } }, this); };

- , . 20, โ€“ 40.

, (0, 0) , . translate.

display runGame:

<body> <script> runGame(GAME_LEVELS, CanvasDisplay); </script> </body>


, โ€“ HTML, SVG . . .

HTML . . SVG , , . HTML .

SVG , . HTML, .

SVG HTML (DOM), . , . , . DOM ( , SVG). .

. , , .

, (, ) JavaScript ( ), .

. , SVG , , HTML .

, . , , , , .


, .

, . context, getContext. . fillStyle . strokeStyle lineWidth , .

. fillRect strokeRect , fillText strokeText . .

beginPath . . , lineTo . , fill stroke.

drawImage. , , . , , .

, -. , translate, scale rotate. . save restore.

clearRect .



, :

1.
2.
3.
4. 100
5.



, Math.cos Math.sin 13, .

. , , . .

<canvas width="600" height="200"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); // </script>



. , , . , . , 5% ( ).

Math.sin Math.cos.

<canvas width="600" height="300"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var total = results.reduce(function(sum, choice) { return sum + choice.count; }, 0); var currentAngle = -0.5 * Math.PI; var centerX = 300, centerY = 150; // results.forEach(function(result) { var sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = result.color; cx.fill(); }); </script>


requestAnimationFrame 13 15 . .

<canvas width="400" height="400"></canvas> <script> var cx = document.querySelector("canvas").getContext("2d"); var lastTime = null; function frame(time) { if (lastTime != null) updateAnimation(Math.min(100, time - lastTime) / 1000); lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); function updateAnimation(step) { // } </script>


, , . , , . , , -, .

, , . , .

, drawImage.

Source: https://habr.com/ru/post/244545/


All Articles