javascriptcanvasgraphics2d

I have a small issue with drawingin Javascript Graphics 2D


Just for fun and learning, I am trying to create a web based forms editor in the manner of Visual Studio. First requirement is the ability to draw a rectangle so that the outline follows the mouse movement while drawing. So with every mouse displacement, the previously drawn rectangle should be wiped. To achieve this, before drawing a new rectangle, I restore a snapshot of the canvas that was taken when the mouse button was pressed down.

This works well enough, the flickering is understandable but not too disturbing as long as I keep some continuous movement in the mouse. But what I fail to understand, and what is really annoying, is that the rectangle disappears as soon as I stop the mouse movement for more than a split second. I put a minimal test set in this Codepen [https://codepen.io/cbreemer/pen/mdgOQdQ] so the issue will be clear even is maybe my explanation wasn't.

I am hoping somebody can tell me what I am missing here.

I thought that maybe moving the rectangle-drawing code inside the image load handler (in the mouse move handler) might fix it. But sadly, then I don't see anything until the mouse button is released. I can't think of anything else to try.

    var ctx;
    var startX, startY, endX, endY, lastX, lastY;
    var rect;
    var lastCanvas;
    var isMouseDown = false;

    function init()
    {
        rect = myCanvas.getBoundingClientRect();

        ctx = myCanvas.getContext("2d");
        ctx.lineWidth = 1;
        ctx.strokeStyle = "black";
        ctx.fillStyle = "wheat";
        ctx.fillRect(0, 0, rect.width, rect.height);

        myCanvas.addEventListener("mousemove",(e)=>
        {
            if ( isMouseDown )
            {
                var canvasPic = new Image();
                canvasPic.src = lastCanvas;
                canvasPic.addEventListener("load", (e)=> { ctx.drawImage(canvasPic, 0, 0); });

                var x = e.clientX - rect.left;
                var y = e.clientY - rect.top;
                ctx.strokeRect(startX, startY, x-startX, y-startY);
                lastX = x;
                lastY = y;
            }
        });

        myCanvas.addEventListener("mousedown",(e)=>
        {
            lastCanvas = myCanvas.toDataURL();
            startX = e.clientX - rect.left;
            startY = e.clientY - rect.top;
            lastX  = startX;
            lastY  = startY;
            isMouseDown = true;
        });

        myCanvas.addEventListener("mouseup",(e)=>
        {
            isMouseDown = false;
            endX = e.clientX - rect.left;
            endY = e.clientY - rect.top;
            ctx.strokeRect(startX, startY, endX-startX, endY-startY);
        });
    }
<!DOCTYPE html>
<html>
<body onLoad="init();">
<canvas id="myCanvas"  width="600" height="400" style="border:1px solid black"></canvas>
</body>
</html>


Solution

  • Taken from another answer of mine, Here's the basic idea of redrawing the canvas every tick of the requestAnimationFrame. It also demonstrated how to better redraw image (taken using getImageData.

    let canvas = document.querySelector(".result");
    var ctx = canvas.getContext("2d");
    
    //Loading of the home test image - img1
    var img1 = new Image();
    var imageData
    
    //drawing of the test image - img1
    img1.onload = function() {
      ctx.fillStyle = "rgba(0, 0, 0, 255)";
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(img1, 0, 0);
        
      imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
      drawRect()
    
    };
    
    img1.crossOrigin = "Anonymous";
    img1.src = 'https://picsum.photos/400/200';
    
    
    function drawRect() {
      let SldrVal = document.getElementById("MySldr").value;
      //ctx.strokeStyle = "#305ef2";
      ctx.clearRect(0, 0, canvas.width, canvas.height);
    
      ctx.putImageData(imageData, 0, 0);
    
    
      ctx.beginPath();
      ctx.arc(canvas.width / 2, canvas.height / 2, SldrVal, 0, 2 * Math.PI);
      ctx.stroke();
      ctx.font = "32px Arial";
      ctx.fillText(SldrVal, 10, 50);
    
      requestAnimationFrame(drawRect)
    }
    <input type="range" id="MySldr" value="15" min="1" max="100">
    <br>
    <canvas class="result" width="400" height="160"></canvas>