Eraser for sketch app
canvas, html, javascript
Solution
Here is an illustration of using globalCompositeOperation=”destination-out” to create an eraser
This composite will “erase” any previous pixels that the eraser draws over.
Hint: It helps to draw the eraser as a circle so you don’t leave “snips” of sketched lines.
Here is a mousemove function that illustrates both sketched lines and erasing.
I see your handling your sketching differently (recording strokes), but this will illustrate how to use destination-out compositing as an eraser.
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
if(isMouseDown){
ctx.beginPath();
if(mode=="pen"){
ctx.globalCompositeOperation="source-over";
ctx.moveTo(lastX,lastY);
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
}else{
ctx.globalCompositeOperation="destination-out";
ctx.arc(lastX,lastY,5,0,Math.PI*2,false);
ctx.fill();
}
lastX=mouseX;
lastY=mouseY;
}
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/uSMxU/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<!--[if lt IE 9]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var lastX;
var lastY;
var strokeColor="red";
var strokeWidth=2;
var mouseX;
var mouseY;
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var isMouseDown=false;
function handleMouseDown(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
lastX=mouseX;
lastY=mouseY;
isMouseDown=true;
}
function handleMouseUp(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isMouseDown=false;
}
function handleMouseOut(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseOut stuff here
isMouseDown=false;
}
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
if(isMouseDown){
ctx.beginPath();
if(mode=="pen"){
ctx.globalCompositeOperation="source-over";
ctx.moveTo(lastX,lastY);
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
}else{
ctx.globalCompositeOperation="destination-out";
ctx.arc(lastX,lastY,5,0,Math.PI*2,false);
ctx.fill();
}
lastX=mouseX;
lastY=mouseY;
}
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
var mode="pen";
$("#pen").click(function(){ mode="pen"; });
$("#eraser").click(function(){ mode="eraser"; });
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas></br>
<button id="pen">Pen</button>
<button id="eraser">Eraser</button>
</body>
</html>
Problem
I have been working on a sketch app using canvas. I can't seem to get an eraser to work with my code. I have tried a lot of answers from Stack Overflow and most of them don't work. For example: - Setting the `globalcompositeoperation` - Setting `alpha` to 0 - Using `clearRect` to erase. this worked, but made the browser very slow. So I was hoping someone could help me out on this one. Here's a demo, and here is my code: ``` <html> <head> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <style type="text/css"> /*#canvas{background: #F6F683}*/ .canvasBackground{ position: absolute; top: 8px; left: 8px; width: 1000px; height: 2400px;z-index: -10;} #main { position: fixed; top: 5px; left: 1020px; width: 280px; height: 250px;} .icon{ cursor: pointer; cursor: hand; } </style> <script type="text/javascript" src="../js/jquery-1.9.1.js"></script> <script type="text/javascript"> var lWidthE = 15; //Line width eraser var lWidthM = 2; //Line width marker $(document).ready(function() { $('.canvasBackground').css("background-image", "url(http://s22.postimg.org/i83b7ztch/notepad_page.png)"); }); var canvas; var ctx; var started = false; var lastx = 0; var lasty = 0; var memCanvas; var memCtx; var pointerCanvas; var pCtx; var points = []; function init() { // Bind canvas to listeners canvas = document.getElementById('canvas'); canvas.addEventListener('mousedown', mouseDown, false); document.addEventListener('mousemove', mouseMove, false); document.addEventListener('mouseup', mouseUp, false); ctx = canvas.getContext('2d'); // create an in-memory canvas memCanvas = document.createElement('canvas'); memCanvas.width = 1000; memCanvas.height = 2400; memCtx = memCanvas.getContext('2d'); ctx.lineJoin = 'round'; ctx.lineCap = 'round'; } function ctx_stuff() { if (v) { ctx.lineWidth = lWidthE; ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = "rgba(246,246,131,0)"; } else { ctx.lineWidth = lWidthM; ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = "rgba(0,0,0,1)"; } } function mouseDown(e) { var m = getMouse(e, canvas); points.push({ x: m.x, y: m.y }); started = true; ctx.clearRect(0, 0, 1000, 2400); // put back the saved content ctx.drawImage(memCanvas, 0, 0); memCtx.clearRect(0, 0, 1000, 2400); memCtx.drawImage(canvas, 0, 0); drawPoints(ctx, points); } function mouseMove(e) { if (started) {//to doodle ctx.clearRect(0, 0, 1000, 2400); // // put back the saved content ctx.drawImage(memCanvas, 0, 0); var m = getMouse(e, canvas); points.push({ x: m.x, y: m.y }); drawPoints(ctx, points); } else {//to show where start point of doodle var m = getMouse(e, canvas); points.push({ x: m.x, y: m.y }); ctx.clearRect(0, 0, 1000, 2400); // put back the saved content ctx.drawImage(memCanvas, 0, 0); drawPoints(ctx, points); points = []; } } function mouseUp(e) { if (started) { started = false; // When the pen is done, save the resulting context // to the in-memory canvas memCtx.clearRect(0, 0, 1000, 2400); memCtx.drawImage(canvas, 0, 0); ctx.drawImage(memCanvas, 0, 0); points = []; } } // clear both canvases! function clear123() { ctx.clearRect(0, 0, 1000, 2400); memCtx.clearRect(0, 0, 1000, 2400); cleanUpArray(); } var small_x = 0, small_y = 0, big_x = 0, big_y = 0; function drawPoints(ctx, points) { ctx_stuff(); // draw a basic circle instead if (points.length < 6) { var b = points[0]; if (v) { ctx.beginPath(), ctx.arc(b.x, b.y, ctx.lineWidth / 2, 0, Math.PI * 2, !0), ctx.closePath(), ctx.fillStyle = "rgba(246,246,131,0)", ctx.fill(); // ctx.clearRect(b.x - (lWidthE / 2), b.y - (lWidthE / 2), (lWidthE), (lWidthE)); } else { ctx.beginPath(), ctx.arc(b.x, b.y, ctx.lineWidth / 2, 0, Math.PI * 2, !0), ctx.closePath(), ctx.fillStyle = "rgba(0,0,0,1)", ctx.fill(); } return; } ctx.beginPath(), ctx.moveTo(points[0].x, points[0].y); // draw a bunch of quadratics, using the average of two points as the control point for (var i = 1; i < points.length - 2; i++) { var c = (points[i].x + points[i + 1].x) / 2, d = (points[i].y + points[i + 1].y) / 2; // if (v) { // ctx.clearRect(points[i].x - (lWidthE / 2), points[i].y - (lWidthE / 2), lWidthE, lWidthE); // } // else { ctx.quadraticCurveTo(points[i].x, points[i].y, c, d); // } } // if (!v) { ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y), ctx.stroke(); // } } // Creates an object with x and y defined, // set to the mouse position relative to the state's canvas // If you wanna be super-correct this can be tricky, // we have to worry about padding and borders // takes an event and a reference to the canvas function getMouse(e, canvas) { var element = canvas, offsetX = 0, offsetY = 0, mx, my; // Compute the total offset. It's possible to cache this if you want if (element.offsetParent !== undefined) { do { offsetX += element.offsetLeft; offsetY += element.offsetTop; } while ((element == element.offsetParent)); } mx = e.pageX - offsetX; my = e.pageY - offsetY; ex = mx; ey = my; var tr; if (v) { tr = lWidthE + 250; } else { tr = lWidthM + 250; } if (mx < small_x || small_x === 0) { small_x = mx - tr; if (small_x < 0) { small_x = 0; } } if (mx > big_x || big_x === 0) { big_x = mx + tr; if (big_x > 1000) { big_x = 1000; } } if (my < small_y || small_y === 0) { small_y = my - tr; if (small_y < 0) { small_y = 0; } } if (my > big_y || big_y === 0) { big_y = my + tr; if (big_y > 2400) { big_y = 2400; } } // We return a simple javascript object with x and y defined return {x: mx, y: my}; } var v = false; function erase() { if (v) { v = false; } else { v = true; } } </script> </head> <body onload="init();"> <div class="canvasBackground"></div> <div class="canvasBackground"></div> <canvas id='canvas' width='1000' height='2400'></canvas> <div id="main"> <p> <label for="amountM">Marker size:</label> <input type="text" id="amountM" style="border: 0; color: #f6931f; font-weight: bold;"/> </p> <div title="Slide the bar to change size of marker" id="slider-range-minM"></div> <p> <label for="amountE">Eraser size:</label> <input type="text" id="amountE" style="border: 0; color: #f6931f; font-weight: bold;"/> </p> <div title="Slide the bar to change size of eraser" id="slider-range-minE"></div> <br/> <button title="Clear the canvas area." onclick='clear123();'>Clear</button> <input type="button" id="btnErase" title="Click to change between eraser and marker." onclick='erase();' value="Eraser/Marker"/> <img class="icon" src="icons/undo-icon.png" alt="Undo." title="Undo" onclick="javascript:undo();"> <img class="icon" src="icons/redo-icon.png" alt="Redo." title="Redo" onclick="javascript:redo();"> </div> </body> </html> ```