Search code examples
angularhtml5-canvasmouseeventrectangles

Angular, drawing multiples rectangles with canvas on mouse event


I'm trying to create a drawing tool with angular. I created a tool that you can draw a rectangle on a canvas element, the tool is working ok, but I can only draw one rectangle at a time, I just can't figure out how to "not clear" the previous rectangle and keep multiples drawings on my canvas. I cannot clear many canvas because i'm gonna have more drawings, not just rectangles.

rectangleDrawing() {

    // first coordinates when clicked
    let startX = 0;
    let startY = 0;

    const rect = this.canvasEl.getBoundingClientRect();

    fromEvent(this.canvasEl, "mousedown")
        .pipe(
            switchMap((e:MouseEvent) => {

                startX = e.clientX - rect.left;
                startY = e.clientY - rect.top;

                return fromEvent(this.canvasEl, 'mousemove').pipe(

                    takeUntil(fromEvent(this.canvasEl, 'mouseup')),
                    takeUntil(fromEvent(this.canvasEl, 'mouseleave'))
                )

            })
        ).subscribe((event:MouseEvent) => {

            let x = event.clientX - rect.left;
            let y = event.clientY - rect.top;

            let width = x - startX;
            let height = y - startY;


            this.setCanvasProperties(10, 'square', 'rgba(255,158,43,0.6)');

            this.cx.beginPath();

            // if I comment this line, the rectangles will stay, but they 
            // won't be clear, making multiples rectangles inside the 
            // main rectangle
            this.cx.clearRect(0,0, this.canvasEl.width, this.canvasEl.height);

            this.cx.rect(startX, startY, width, height);

            this.cx.stroke();

        });

}

setCanvasProperties(lineWidth, lineCap, strokeStyle) {
    this.cx = this.canvasEl.getContext('2d');
    this.cx.lineWidth = lineWidth;
    this.cx.lineCap = lineCap;
    this.cx.strokeStyle = strokeStyle;
}

This link is a good example: https://jsfiddle.net/richardcwc/ukqhf54k/, if you comment the clear line, you can see main problem. Many examples seems to permit only one rectangle at a time.


Solution

  • On mouse up, you could store the drawn rect in an array, and then draw that array and the current rect.

    First, lets set the array and keep the width and height as a global variable:

    var width;
    var height;
    var drawItems = [];
    

    Then, the mouseup function will be like this:

    $(canvas).on('mouseup', function(e) {
      mousedown = false;
    
      drawItems.push({
        x0: last_mousex,
        x1: width,
        y0: last_mousey,
        y1: height
      });
    });
    

    And this is the mousemove function:

    $(canvas).on('mousemove', function(e) {
        mousex = parseInt(e.clientX-canvasx);
        mousey = parseInt(e.clientY-canvasy);
        if(mousedown) {
            ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
    
            ctx.strokeStyle = 'black';
            ctx.lineWidth = 10;
    
            for(var i=0; i<drawItems.length; i++) {
                ctx.beginPath();
                ctx.rect(drawItems[i].x0, drawItems[i].y0, drawItems[i].x1, drawItems[i].y1);
                ctx.stroke();
            }
    
            width = mousex-last_mousex;
            height = mousey-last_mousey;
            ctx.beginPath();
            ctx.rect(last_mousex,last_mousey,width,height);
            ctx.stroke();
        }
        //Output
        $('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
    });
    

    Here is the updated fiddle

    I hope it helps