表現力豊かなJavaScript:Canvasでの描画

内容







描画はデマです。

M.K.エッシャー



ブラウザを使用すると、さまざまな方法でグラフィックを描画できます。 スタイルを使用する最も簡単な方法は、標準のDOM要素を配置して色付けすることです。 前の章のゲームの例が示したように、多くのことを達成できます。 部分的に透明な画像をノードに追加することにより、希望する外観を与えることができます。 変換スタイルを使用して、ノードを回転または変形することもできます。



しかし、そのようなDOMの使用は、DOMの目的ではありません。 任意の2点間に線を引くなどの一部のタスクは、通常のHTML要素を使用して実行するのは非常に不便です。



2つの選択肢があります。 1つ目は、同じくDOMに基づくがHTMLを使用しないスケーラブルなベクターグラフィックスであるSVGです。 SVGは、テキストではなくフォームに焦点を当てたドキュメントを記述するための方言です。 SVGはHTMLに埋め込むか、タグを介して含めることができます。



2番目の選択肢はキャンバスです。 キャンバスは、画像が存在する1つのDOM要素です。 要素が占める場所にフォームを描画するためのAPIを提供します。 キャンバスとSVGの違いは、フォームの最初の記述がSVGに保存されることです-いつでもシフトまたはサイズ変更できます。 一方、キャンバスは、図形を描画するとすぐに図形をピクセル(ラスターの色付きドット)に変換し、これらのピクセルが何であるかを覚えていません。 キャンバス上の図形を移動する唯一の方法は、キャンバス(または図形を囲む部分)をクリアし、別の場所に再描画することです。



Svg



この本はSVGには深く入りませんが、その作業について簡単に説明します。 この章の最後で、特定のアプリケーション用の描画メカニズムを選択する際に考慮しなければならないメソッドの比較上の欠点に戻ります。



簡単なSVG画像を含むHTMLドキュメントを次に示します。



<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>
      
      







xmlns属性は、要素のデフォルト名前空間を変更します。 このスペースはURLを介して指定され、話している方言を示します。 HTMLに存在しないタグは、SVGでは意味があります。属性で指定されたスタイルと位置を使用してフォームを描画します。



HTMLタグと同じ方法でDOM要素を作成します。 たとえば、次のコードは要素の色をシアンに変更します。



var circle = document.querySelector( "circle");

circle.setAttribute( "fill"、 "cyan");



キャンバス要素キャンバス



キャンバスグラフィックスを要素に描画できます。
 .      ,       . 
      



, .



. , 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.















All Articles