表現力豊かなJavaScript:プロジェクト:ペイント

内容







さまざまな色を見ています。 空白のキャンバスを見ます。 それから、詩が生まれる言葉、音楽が生まれる音符として色を適用しようとします。



ジョアン・ミロ



前の章の資料は、単純なWebアプリケーションを作成するために必要なすべてを提供します。 それが私たちのすることです。



アプリケーションは、Microsoft Paintに似たブラウザ描画プログラムになります。 その助けを借りて、画像でファイルを開き、マウスでそれらをペイントし、それらを保存することが可能になります。 これは次のようになります。







簡単な描画プログラム



コンピューターにクールドロー。 材料、スキル、才能について心配する必要はありません。 あなたはちょうどそれを取り、あなたはがらくたを始めます。



実装



プログラムインターフェイスの上部に大きな要素が表示されます
 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .



DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }



, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



































, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }



, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>































, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .





, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };



tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



































, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };



tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>































, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>



URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>































, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























, . , , . , , ..



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>



























 ,      .    ,     ,      .     ,   ,    .. 
      



"mousedown" , , . , , "mousemove", , .



. fillStyle, strokeStyle, lineWidth .



. file, . URL .



. save . , . , .







DOM

30 DOM. - .



DOM HTML. HTML – DOM , - . querySelector , DOM .



DOM JavaScript, . DOM JavaScript. 13, DOM . , .



– elt 13. , , , , .



function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }








, .





– createPaint, DOM, . , controls, .



var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }







, – . – , ( fillStyle) ( lineWidth).



, , .









, – , . controls, , , . , .



var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };








tool , "mousedown" , event context. preventDefault, .



– , . , . 13 getBoundingClientRect . , , . clientX clientY , .



function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }








"mousemove", . trackDrag .



function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }







. – , "mousemove", – , . .



.



tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };







lineCap “round”, - , , . , . , , lineCap .



, "mousemove", , , , strokeStyle lineWidth, .



onEnd , trackDrag. , undefined, . , erase, .



tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };







globalCompositeOperation , . , "source-over", , , , . , , , .



“erase” globalCompositeOperation "destination-out", , .



. ( strokeStyle lineWidth ), . , , .





, , .



18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .



controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };








color fillStyle strokeStyle .



.



controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };







, , lineWidth .





, , URL . http: https:, URL , . URL , HTML :



data:text/html,<h1 style="color:red">Hello!</h1>







URL , , , . , , - .



toDataURL, URL , . . URL . , href , .



controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };







, , , , .



, URL , . .



. URL , , (. 17), , , .



( ). , .



, , , , «». , URL , «» . , .



try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .





URL. , URL .



function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }







, . - (fillStyle lineWidth), .



FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .



controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };







URL . , URL, “change”. , – Enter, load.



controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };







, , .





, , .



tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };







, sans-serif , . – 7 , .



- – “”. , , .



tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };







setInterval 25 , . trackDrag , currentPos , .



, , 30. randomPointInRadius.



function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }







(-1,-1) (1,1). , , 1. , , .



. , Math.sin Math.cos . . , , .



. .



<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









. .





Rectangle, (. fillRect 16) . , , , . , .



, , . , , ?



, position: absolute, 13. , . pageX pageY , left, top, width height.



<script> tools.Rectangle = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









– , , . .



. toDataURL , URL . getImageData, width, height data. data 0 255, - red, green, blue alpha ().



, , ( – ), , .



function pixelAt(cx, x, y) { var data = cx.getImageData(x, y, 1, 1); console.log(data.data); } var canvas = document.createElement("canvas"); var cx = canvas.getContext("2d"); pixelAt(cx, 10, 10); // → [0, 0, 0, 0] cx.fillStyle = "red"; cx.fillRect(10, 10, 1, 1); pixelAt(cx, 10, 10); // → [255, 0, 0, 255]







getImageData x y, , .



, . color . , fillStyle strokeStyle , .



, , CSS, rgb(R, G, B), 15.



getImageData , toDataURL – , , . try/catch alert.



<script> tools["Pick color"] = function(event, cx) { // Your code here. }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>









, , . , , , - .



, . , , , ( ), , .



, , :







, , , .



getImageData . , , . 7, , . (x,y) (x + y × width) × 4



(), () .



, , , . . - , 9. , , , , .



. , . , , .



fillRect, - , , .



<script> tools["Flood fill"] = function(event, cx) { // }; </script> <link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>






























All Articles