πŸ“œ ⬆️ ⬇️

Expressive JavaScript: Project: Paint

Content




I look at the variety of colors. I look at the blank canvas. Then I try to put colors like words from which poems arise, like notes from which music comes.

Joan Miro
')
The material in the previous chapters gives you everything you need to create a simple web application. This is what we are going to do.

Our application will be a browser paint program similar to Microsoft Paint. With it, you can open files with images, daub them and save them back. Here is what it will look like:



Simple drawing program


Draw on the computer cool. No need to worry about the materials, skills, talent. You just take it, and you start to shuffle.

Implementation


The program interface displays a large element at the top.
 ,      .    ,     ,      .     ,   ,    .. 

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

Source: https://habr.com/ru/post/245767/


All Articles