Reputation: 287
I have a canvas function which draws a square if I click on the canvas field and move the mouse, that works so far.
My Problem is that if I release the mouse and click at the canvas again the old drawn rectangle vanishes.
How do I make it possible that the old drawn does not get vanished.
My function:
function foo() {
var tool = this;
this.started = false;
var canvasx = canvas.offsetLeft;
var canvasy = canvas.offsetTop;
var last_mousex = 0;
var last_mousey = 0;
var mousex = 0;
var mousey = 0;
this.mousedown = function (ev) {
if(checkboxSquare.checked) {
last_mousex = parseInt(ev.clientX-canvasx);
last_mousey = parseInt(ev.clientY-canvasy);
context.strokeStyle = $('#selectColor').val();
context.lineWidth = $('#selectWidth').val();
tool.started = true;
}
};
this.mousemove = function (ev) {
if (tool.started && checkboxSquare.checked) {
mousex = parseInt(ev.clientX-canvasx);
mousey = parseInt(ev.clientY-canvasy);
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
context.beginPath();
var width = mousex-last_mousex;
var height = mousey-last_mousey;
context.rect(last_mousex,last_mousey,width,height);
context.stroke();
}
};
this.mouseup = function (ev) {
if (tool.started && checkboxSquare.checked) {
tool.mousemove(ev);
tool.started = false;
}
};
}
It Looks something like this: http://jsfiddle.net/AbdiasSoftware/kqW4X/
Upvotes: 0
Views: 721
Reputation: 54026
Just create a background canvas same as the main canvas. When you drag out a new box, first draw the background canvas (with all the past boxes) on the main canvas then the current box being drawn. When you finish dragging the box, just daw it to the background canvas.
const canvas = document.createElement("canvas");
const background = document.createElement("canvas");
canvas.style.border="2px solid black";
canvas.style.cursor = "crosshair";
background.width = canvas.width = innerWidth - 24;
background.height = canvas.height = innerHeight - 24;
const ctx = canvas.getContext("2d");
background.ctx = background.getContext("2d");
document.body.appendChild(canvas);
const bounds = canvas.getBoundingClientRect();
var currentBox;
const boxStyle = {
fillStyle : "#4aF",
strokeStyle : "black",
lineWidth : 3,
lineJoin : "round",
}
const mouse = { x : 0, y : 0,button : false, changed : false };
["mousemove","mousedown","mouseup"].forEach(en => document.addEventListener(en, mouseEvent));
function createBox(x,y,w,h,style){ return {x,y,w,h,style,draw : drawBox} }
function drawBox(ctx){
setStyle(ctx, this.style);
ctx.beginPath();
ctx.rect(this.x,this.y,this.w,this.h);
ctx.fill();
ctx.stroke();
}
function setStyle(ctx, style){ Object.keys(style).forEach(key => ctx[key] = style[key]) }
function mouseEvent(event) {
mouse.x = event.pageX - bounds.left - scrollX;
mouse.y = event.pageY - bounds.top - scrollY;
if(event.type === "mousedown"){ mouse.button = true }
else if(event.type === "mouseup"){ mouse.button = false }
mouse.changed = true;
}
function mainLoop(){
var b = currentBox; // alias for readability
if(mouse.changed){
if(mouse.button){
if(!b){
b = currentBox = createBox(mouse.x,mouse.y,0,0,boxStyle);
}else{
b.w = mouse.x - b.x;
b.h = mouse.y - b.y;
}
}else if(b){
b.draw(background.ctx);
b = currentBox = undefined;
}
if(b){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(background,0,0);
b.draw(ctx);
canvas.style.cursor = "none";
}else{
canvas.style.cursor = "crosshair";
}
mouse.changed = false;
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
When you create canvas drawing apps you should listen to the document mouse events rather than the canvas. When the mouse button is down the mouse is captured and will continue to send mouse events while the mouse is down, even if you have moved off the canvas, document, or event outside the browser window.
This means you can drag content of the canvas and not worry about losing the mouseup event.
I have some time to burn so will extend the demo above to include selecting and moving existing boxes. Draw boxes as normal. Mouse over boxes will highlight them, click to select them. When selected can be dragged. Uses the same method background image to hold old boxes. But have added a box list to hold old boxes A more extensive example
const canvas = document.createElement("canvas");
const background = document.createElement("canvas");
canvas.style.border="2px solid black";
canvas.style.cursor = "crosshair";
background.width = canvas.width = innerWidth - 24;
background.height = canvas.height = innerHeight - 24;
const ctx = canvas.getContext("2d");
background.ctx = background.getContext("2d");
document.body.appendChild(canvas);
const bounds = canvas.getBoundingClientRect();
var currentBox;
var selectedBox;
var mouseOverBox;
const styles = {
box : {
fillStyle : "#4aF",
strokeStyle : "black",
lineWidth : 3,
lineJoin : "round",
},
highlight : {
strokeStyle : "white",
lineWidth : 1,
lineJoin : "round",
setLineDash : [[10,10]],
},
selected : {
strokeStyle : "red",
lineWidth : 2,
lineJoin : "round",
setLineDash : [[5,5]],
},
}
const boxes = {
items : [],
add(box){ // add a box and fix width and height to positive
if(box.w < 0){
box.x += box.w;
box.w = -box.w;
}
if(box.h < 0){
box.y += box.h;
box.h = -box.h;
}
boxes.items.push(box)
},
apply(name, ...args){
for(var i = 0; i < boxes.items.length; i ++ ){
boxes.items[i][name](...args);
}
},
};
const mouse = { x : 0, y : 0,button : false, changed : false };
["mousemove","mousedown","mouseup"].forEach(en => document.addEventListener(en, mouseEvent));
const boxBehaviours = {
draw(ctx, style = this.style){
if(!this.hide){
setStyle(ctx, style);
ctx.beginPath();
ctx.rect(this.x,this.y,this.w,this.h);
if(style.fillStyle) { ctx.fill() }
if(style.strokeStyle) {ctx.stroke() }
}
},
isPointOver(x,y){
var b = this;
if(x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h){
b.mouseOver = true;
boxBehaviours.topMouseBox = b;
}else {
b.mouseOver =false;
}
},
}
function createBox(x,y,w,h,style){
return {x,y,w,h,style, ...boxBehaviours};
}
function setStyle(ctx, style){
Object.keys(style).forEach(key => {
if(typeof ctx[key] === "function"){
ctx[key](...style[key]);
}else{
ctx[key] = style[key];
}
})
}
function mouseEvent(event) {
mouse.x = event.pageX - bounds.left - scrollX;
mouse.y = event.pageY - bounds.top - scrollY;
if(event.type === "mousedown"){ mouse.button = true }
else if(event.type === "mouseup"){ mouse.button = false }
}
function redrawBackground(){
background.ctx.clearRect(0,0,canvas.width,canvas.height)
boxes.apply("draw",background.ctx);
}
function mainLoop(time){
var b = currentBox; // alias for readability
var mob = mouseOverBox; // alias for readability
var sb = selectedBox; // alias for readability
// first check mouse button. If button down could be
// dragging a selected box or creating a new box
if(mouse.button){
if(sb){ // is selected box
if(!mouse.drag){ // start the drag
mouse.drag = {x : mouse.x - sb.x, y : mouse.y - sb.y}
}else{ // move the box
sb.x = mouse.x- mouse.drag.x;
sb.y = mouse.y- mouse.drag.y;
}
}else{ // else muse be create (or select click)
if(!b){
b = currentBox = createBox(mouse.x,mouse.y,0,0,styles.box);
}else{
b.w = mouse.x - b.x;
b.h = mouse.y - b.y;
}
}
}else if(b || sb){ // mouse up and there is a box
if(sb){ // if selected box
if(mouse.drag){ // is dragging then drop it
mouse.drag = undefined;
sb.hide = false;
redrawBackground();
sb = selectedBox = undefined;
}
// is the mouse is down and has not moved over 2 pixels
// and there is a mob (mouseOverBox) under it
// then dump the new box and select the mob box
}else if(Math.abs(b.w) < 2 && Math.abs(b.h) < 2 && mob){
sb = selectedBox = mob;
mob = mouseOverBox = undefined;
b = currentBox = undefined;
sb.hide = true;
redrawBackground();
}else{
// just a normal box add it to box array
// draw it and remove it from currentBox
boxes.add(b);
b.draw(background.ctx);
b = currentBox = undefined;
}
}
// clear andf draw background
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(background,0,0);
if(b){ // is there a current box then draw that
b.draw(ctx);
canvas.style.cursor = "none";
} else { // no current box so
// find any boxes under the mouse
boxBehaviours.topMouseBox = null;
boxes.apply("isPointOver",mouse.x, mouse.y);
// is there a selected box (sb)
if(sb){ // yes selected box then draw it
ctx.save();
styles.selected.lineDashOffset = time / 25;
sb.hide = false;
sb.draw(ctx,styles.selected);
sb.hide = true;
ctx.restore();
canvas.style.cursor = "move";
// no selected box sp then just high light the box under the
// mouse and assign it to mouseOverBox (mob);
}else if(boxBehaviours.topMouseBox){
mob = mouseOverBox = boxBehaviours.topMouseBox;
ctx.save();
styles.highlight.lineDashOffset = time / 20;
mob.draw(ctx, styles.highlight);
ctx.restore();
canvas.style.cursor = "pointer";
}else{
canvas.style.cursor = "crosshair";
}
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
Upvotes: 1
Reputation: 32859
The old drawn rectangle vanishes on click because, you are clearing the entire canvas each time before drawing a rectangle.
The easiest workaround would be to save the entire canvas as an image on mouseup and draw that image before drawing each rectangle.
var canvas;
var _foo = new foo();
canvas.onmousedown = _foo.mousedown;
canvas.onmousemove= _foo.mousemove;
canvas.onmouseup = _foo.mouseup;
function foo() {
canvas = $('#canvas')[0];
var context = canvas.getContext('2d');
var checkboxSquare = $('#checkboxSquare')[0];
var img = new Image();
var tool = this;
this.started = false;
var last_mousex = 0;
var last_mousey = 0;
var mousex = 0;
var mousey = 0;
this.mousedown = function (ev) {
if(checkboxSquare.checked) {
last_mousex = ev.offsetX;
last_mousey = ev.offsetY;
context.strokeStyle = $('#selectColor').val();
context.lineWidth = $('#selectWidth').val();
tool.started = true;
}
};
this.mousemove = function (ev) {
if (tool.started && checkboxSquare.checked) {
mousex = ev.offsetX;
mousey = ev.offsetY;
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
context.drawImage(img, 0, 0); // draw saved canvas (image)
context.beginPath();
var width = mousex-last_mousex;
var height = mousey-last_mousey;
context.rect(last_mousex,last_mousey,width,height);
context.stroke();
}
};
this.mouseup = function (ev) {
if (tool.started && checkboxSquare.checked) {
tool.mousemove(ev);
img.src = canvas.toDataURL(); // save canvas as image
tool.started = false;
}
};
}
canvas {
border: 1px solid black;
cursor: default;
margin-top: 5px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="checkbox" id="checkboxSquare">Square | Color
<select id="selectColor">
<option value="red">red</option>
<option value="green">green</option>
<option value="blue">blue</option>
</select> | Width
<select id="selectWidth">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<canvas id="canvas" width="400" height="400"></canvas>
Upvotes: 1
Reputation: 5487
I'm assuming that the foo()
function is being called for every frame, either through setInterval
or requestAnimationFrame
. If my assumption is right, the reason why your previously drawn square disappears is because you are only storing the x and y coordinates of one rectangle, and every time you click on the canvas again, it gets overwritten by the new values for the new rectangle.
To solve your problem, you should store the x and y coordinates as well as the dimensions of the square on mouseup. These coordinates can be stored in an array.
var squares = [];
this.mouseup = function (ev) {
// other code
var square = {
x: last_mousex,
y: last_mousey,
width: mousex - last_mousex,
height: mousey - last_mousey
};
squares.push(square);
};
Now every time you draw the square, draw the squares stored in the squares
array first.
this.mousemove = function (ev) {
if (tool.started && checkboxSquare.checked) {
// other code
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
// draw each square in the squares array after clearning the canvas
squares.forEach(function(square) {
context.beginPath();
context.rect(square.x, square.y, square.width, square.height);
});
context.beginPath();
var width = mousex - last_mousex;
var height = mousey - last_mousey;
context.rect(last_mousex, last_mousey, width, height);
context.stroke();
}
};
You'll see some code repetitions in drawing the squares, it's a good opportunity to abstract it into a separate function.
Upvotes: 0
Reputation: 15604
var point = [];
var clicks = 0;
var sketch = document.querySelector('#sketch');
var sketch_style = getComputedStyle(sketch);
// Creating a tmp canvas
var tmp_canvas = document.createElement('canvas');
var tmp_ctx = tmp_canvas.getContext('2d');
tmp_canvas.id = 'tmp_canvas';
tmp_canvas.width = parseInt(sketch_style.getPropertyValue('width'));
tmp_canvas.height = parseInt(sketch_style.getPropertyValue('height'));
sketch.appendChild(tmp_canvas);
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.id = 'paint';
canvas.width = parseInt(sketch_style.getPropertyValue('width'));
canvas.height = parseInt(sketch_style.getPropertyValue('height'));
sketch.appendChild(canvas);
tmp_canvas.addEventListener('mousedown', mousedown, false);
tmp_canvas.addEventListener('mousemove', mousemove, false);
tmp_canvas.addEventListener('mouseup', mouseup, false);
function mousemove(e) {
if (clicks == 1) {
x = e.layerX - this.offsetLeft;
y = e.layerY - this.offsetTop;
showRect(x, y);
}
}
function showRect(x, y) {
tmp_ctx.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
tmp_ctx.beginPath();
var width = x - point[0].x;
var height = y - point[0].y;
tmp_ctx.rect(point[0].x, point[0].y, width, height);
tmp_ctx.stroke();
}
function mousedown(e) {
x = e.layerX - this.offsetLeft;
y = e.layerY - this.offsetTop;
point.push({
x,
y
});
clicks++;
};
function mouseup() {
context.drawImage(tmp_canvas, 0, 0);
clicks = 0;
point.length = 0;
}
html, body {
width: 100% ;
height: 100% ;
}
#sketch {
border: 10px solid gray;
height: 100% ;
position: relative;
}
#tmp_canvas {
position: absolute;
left: 0px;
right: 0;
bottom: 0;
top: 0;
cursor: crosshair;
}
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="sketch">
</div>
</body>
</html>
Try to do in temporary canvas and redraw all in main.
jsfiddle:-https://jsfiddle.net/durga598/v0m06faz/
Upvotes: 0