javascriptcanvashtml5-canvas

html canvas draw straight line


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:

Loop steps 2. and 3. 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 final straight line from starting point to current cursor position on mouse release.
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.


Solution

  • 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();