简体   繁体   中英

JavaScript Canvas on draw vanishes

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/

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> 

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{ bw = mouse.x - bx; bh = mouse.y - by; } }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); 

Extra Note. Capture the mouse using the Document

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.

Burn some time.

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 >= bx && x < bx + bw && y >= by && y < by + bh){ 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{ bw = mouse.x - bx; bh = mouse.y - by; } } }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(bw) < 2 && Math.abs(bh) < 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); 

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.

 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/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM