内容
さまざまな色を見ています。 空白のキャンバスを見ます。 それから、詩が生まれる言葉、音楽が生まれる音符として色を適用しようとします。
ジョアン・ミロ前の章の資料は、単純なWebアプリケーションを作成するために必要なすべてを提供します。 それが私たちのすることです。
アプリケーションは、Microsoft Paintに似たブラウザ描画プログラムになります。 その助けを借りて、画像でファイルを開き、マウスでそれらをペイントし、それらを保存することが可能になります。 これは次のようになります。

簡単な描画プログラム
コンピューターにクールドロー。 材料、スキル、才能について心配する必要はありません。 あなたはちょうどそれを取り、あなたはがらくたを始めます。
実装
プログラムインターフェイスの上部に大きな要素が表示されます
, . , , . , , ..
"mousedown" , , . , , "mousemove", , .
. fillStyle, strokeStyle, lineWidth .
. file, . URL .
. save . , . , .
DOM
30 DOM. - .
DOM HTML. HTML – DOM , - . querySelector , DOM .
DOM JavaScript, . DOM JavaScript. 13, DOM . , .
– elt 13. , , , , .
function elt(name, attributes) { var node = document.createElement(name); if (attributes) { for (var attr in attributes) if (attributes.hasOwnProperty(attr)) node.setAttribute(attr, attributes[attr]); } for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; }
, .
– createPaint, DOM, . , controls, .
var controls = Object.create(null); function createPaint(parent) { var canvas = elt("canvas", {width: 500, height: 300}); var cx = canvas.getContext("2d"); var toolbar = elt("div", {class: "toolbar"}); for (var name in controls) toolbar.appendChild(controls[name](cx)); var panel = elt("div", {class: "picturepanel"}, canvas); parent.appendChild(elt("div", null, panel, toolbar)); }
, – . – , ( fillStyle) ( lineWidth).
, , .
, – , . controls, , , . , .
var tools = Object.create(null); controls.tool = function(cx) { var select = elt("select"); for (var name in tools) select.appendChild(elt("option", null, name)); cx.canvas.addEventListener("mousedown", function(event) { if (event.which == 1) { tools[select.value](event, cx); event.preventDefault(); } }); return elt("span", null, "Tool: ", select); };
tool , "mousedown" , event context. preventDefault, .
– , . , . 13 getBoundingClientRect . , , . clientX clientY , .
function relativePos(event, element) { var rect = element.getBoundingClientRect(); return {x: Math.floor(event.clientX - rect.left), y: Math.floor(event.clientY - rect.top)}; }
"mousemove", . trackDrag .
function trackDrag(onMove, onEnd) { function end(event) { removeEventListener("mousemove", onMove); removeEventListener("mouseup", end); if (onEnd) onEnd(event); } addEventListener("mousemove", onMove); addEventListener("mouseup", end); }
. – , "mousemove", – , . .
.
tools.Line = function(event, cx, onEnd) { cx.lineCap = "round"; var pos = relativePos(event, cx.canvas); trackDrag(function(event) { cx.beginPath(); cx.moveTo(pos.x, pos.y); pos = relativePos(event, cx.canvas); cx.lineTo(pos.x, pos.y); cx.stroke(); }, onEnd); };
lineCap “round”, - , , . , . , , lineCap .
, "mousemove", , , , strokeStyle lineWidth, .
onEnd , trackDrag. , undefined, . , erase, .
tools.Erase = function(event, cx) { cx.globalCompositeOperation = "destination-out"; tools.Line(event, cx, function() { cx.globalCompositeOperation = "source-over"; }); };
globalCompositeOperation , . , "source-over", , , , . , , , .
“erase” globalCompositeOperation "destination-out", , .
. ( strokeStyle lineWidth ), . , , .
, , .
18 . . , . - . - "date", "email", "url" "number". . – “text”, , , . , , , , .
controls.color = function(cx) { var input = elt("input", {type: "color"}); input.addEventListener("change", function() { cx.fillStyle = input.value; cx.strokeStyle = input.value; }); return elt("span", null, "Color: ", input); };
color fillStyle strokeStyle .
.
controls.brushSize = function(cx) { var select = elt("select"); var sizes = [1, 2, 3, 5, 8, 12, 25, 35, 50, 75, 100]; sizes.forEach(function(size) { select.appendChild(elt("option", {value: size}, size + " pixels")); }); select.addEventListener("change", function() { cx.lineWidth = select.value; }); return elt("span", null, "Brush size: ", select); };
, , lineWidth .
, , URL . http: https:, URL , . URL , HTML :
data:text/html,<h1 style="color:red">Hello!</h1>
URL , , , . , , - .
toDataURL, URL , . . URL . , href , .
controls.save = function(cx) { var link = elt("a", {href: "/"}, "Save"); function update() { try { link.href = cx.canvas.toDataURL(); } catch (e) { if (e instanceof SecurityError) link.href = "javascript:alert(" + JSON.stringify("Can't save: " + e.toString()) + ")"; else throw e; } } link.addEventListener("mouseover", update); link.addEventListener("focus", update); return link; };
, , , , .
, URL , . .
. URL , , (. 17), , , .
( ). , .
, , , , «». , URL , «» . , .
try/catch update . «», toDataURL , SecurityError. URL javascript:. , , , .
URL. , URL .
function loadImageURL(cx, url) { var image = document.createElement("img"); image.addEventListener("load", function() { var color = cx.fillStyle, size = cx.lineWidth; cx.canvas.width = image.width; cx.canvas.height = image.height; cx.drawImage(image, 0, 0); cx.fillStyle = color; cx.strokeStyle = color; cx.lineWidth = size; }); image.src = url; }
, . - (fillStyle lineWidth), .
FileReader 18. readAsText readAsDataURL – , . , , URL , loadImageURL .
controls.openFile = function(cx) { var input = elt("input", {type: "file"}); input.addEventListener("change", function() { if (input.files.length == 0) return; var reader = new FileReader(); reader.addEventListener("load", function() { loadImageURL(cx, reader.result); }); reader.readAsDataURL(input.files[0]); }); return elt("div", null, "Open file: ", input); };
URL . , URL, “change”. , – Enter, load.
controls.openURL = function(cx) { var input = elt("input", {type: "text"}); var form = elt("form", null, "Open URL: ", input, elt("button", {type: "submit"}, "load")); form.addEventListener("submit", function(event) { event.preventDefault(); loadImageURL(cx, form.querySelector("input").value); }); return form; };
, , .
, , .
tools.Text = function(event, cx) { var text = prompt("Text:", ""); if (text) { var pos = relativePos(event, cx.canvas); cx.font = Math.max(7, cx.lineWidth) + "px sans-serif"; cx.fillText(text, pos.x, pos.y); } };
, sans-serif , . – 7 , .
- – “”. , , .
tools.Spray = function(event, cx) { var radius = cx.lineWidth / 2; var area = radius * radius * Math.PI; var dotsPerTick = Math.ceil(area / 30); var currentPos = relativePos(event, cx.canvas); var spray = setInterval(function() { for (var i = 0; i < dotsPerTick; i++) { var offset = randomPointInRadius(radius); cx.fillRect(currentPos.x + offset.x, currentPos.y + offset.y, 1, 1); } }, 25); trackDrag(function(event) { currentPos = relativePos(event, cx.canvas); }, function() { clearInterval(spray); }); };
setInterval 25 , . trackDrag , currentPos , .
, , 30. randomPointInRadius.
function randomPointInRadius(radius) { for (;;) { var x = Math.random() * 2 - 1; var y = Math.random() * 2 - 1; if (x * x + y * y <= 1) return {x: x * radius, y: y * radius}; } }
(-1,-1) (1,1). , , 1. , , .
. , Math.sin Math.cos . . , , .
. .
<link rel="stylesheet" href="css/paint.css"> <body> <script>createPaint(document.body);</script> </body>
. .
Rectangle, (. fillRect 16) . , , , . , .
, , . , , ?
, position: absolute, 13. , . pageX pageY , left, top, width height.
<script> tools.Rectangle = function(event, cx) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {

, . , , . , , ..
"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) {
