skip to Main Content

I want to use MS paint style line tool to draw straight line using UI on html canvas element using JS.

The issue is that line preview must be visible on canvas as mouse is being dragged after click at starting position (line preview is basically straight line from starting click point to current mouse position). Once mouse shifts to new position without release, the old line preview should vanish and new one should show. Finally, once mouse is released, the final line should be painted on the canvas. Instead, I get the kind of image you see below.

I tried various ways to perform the following steps in this order but no success:

Repeat until mouse released:

  1. Store current image on canvas.
  2. Paint straight line from starting point to current cursor position.
  3. Restore previous image seen in step #1 on canvas upon detecting mouse movement.
  4. Paint new straight line from starting point to current cursor position.
window.onload = function() {
    const canvas = document.getElementById('paintCanvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;

    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    let painting = false;
    let startX, startY;
    let canvasState = false;

    canvas.addEventListener('mousedown', startPosition);
    canvas.addEventListener('mouseup', endPosition);
    canvas.addEventListener('mousemove', draw);

    ctx.lineWidth = 5;
    ctx.lineCap = 'round';

    function startPosition(e) {
        painting = true;
        [startX, startY] = [e.offsetX, e.offsetY];
        ctx.save();
        ctx.beginPath();
        draw(e);
    }

    function endPosition() {
        ctx.closePath();
        ctx.restore();
        painting = false;
        canvasState = false;
    }

    function draw(e) {
        if (!painting) return;

        if (selectedTool.id === 'pencil') {
            ctx.lineTo(e.offsetX, e.offsetY);
            ctx.stroke();
        } else if (selectedTool.id === 'line') {
            // write code that works with a line 
            if (e.type === 'mousemove') {
                // remove old preview line
                if (canvasState) {
                    ctx.putImageData(canvasState, 0, 0);
                } else {
                    canvasState = ctx.getImageData(0,0,canvas.width,canvas.height);
                }
                // paint straight line from original start to current mouse position
                ctx.moveTo(startX, startY);
                ctx.lineTo(e.offsetX, e.offsetY);
                ctx.stroke();
            }
        } else if (selectedTool.id === 'eraser') {
            if (e.type === 'mousedown') {
                ctx.strokeStyle = ctx.fillStyle;
                ctx.lineWidth += 5;      
            } else {
                ctx.lineTo(e.offsetX, e.offsetY);
                ctx.stroke();    
            }
        }
    }

};

This is what gets painted on the canvas.

2

Answers


  1. I think, this is because of canvasState is restored inside the path building. The following fix solved the problem for me:

            // remove old preview line
            if (canvasState) {
                ctx.putImageData(canvasState, 0, 0);
            } else {
                canvasState = ctx.getImageData(0,0,canvas.width,canvas.height);
            }
            // paint straight line from original start to current mouse position
            ctx.beginPath();
            ctx.moveTo(startX, startY);
            ctx.lineTo(e.offsetX, e.offsetY);
            ctx.closePath();
            ctx.stroke();
    
    Login or Signup to reply.
  2. Why not make the preview line updates dynamically as the mouse moves and the final line drawn accurately when the mouse is released.

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Canvas Line Tool</title>
        <style>
            #paintCanvas {
                border: 1px solid black;
            }
        </style>
    </head>
    <body>
        <canvas id="paintCanvas" width="800" height="600"></canvas>
        <script>
            window.onload = function() {
                const canvas = document.getElementById('paintCanvas');
                const ctx = canvas.getContext('2d');
    
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;
    
                ctx.fillStyle = '#ffffff';
                ctx.fillRect(0, 0, canvas.width, canvas.height);
    
                let painting = false;
                let startX, startY;
                let canvasState = null;
    
                canvas.addEventListener('mousedown', startPosition);
                canvas.addEventListener('mouseup', endPosition);
                canvas.addEventListener('mousemove', draw);
    
                ctx.lineWidth = 5;
                ctx.lineCap = 'round';
    
                function startPosition(e) {
                    painting = true;
                    [startX, startY] = [e.offsetX, e.offsetY];
                    canvasState = ctx.getImageData(0, 0, canvas.width, canvas.height); 
                }
    
                function endPosition() {
                    painting = false;
                    ctx.putImageData(canvasState, 0, 0); 
                    drawFinalLine(); // Draw the final line
                }
    
                function draw(e) {
                    if (!painting) return;
                    ctx.putImageData(canvasState, 0, 0); 
                    ctx.beginPath();
                    ctx.moveTo(startX, startY);
                    ctx.lineTo(e.offsetX, e.offsetY);
                    ctx.stroke();
                }
    
                function drawFinalLine() {
                    ctx.beginPath();
                    ctx.moveTo(startX, startY);
                    ctx.lineTo(event.offsetX, event.offsetY);
                    ctx.stroke();
                }
            };
        </script>
    </body>
    </html>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search