0

This canvas have been creating an error since I created it and I haven't found a solution after all my research. so I need a hand. I need the canvas to create a straight line but it end up creating multiple straight lines from the starting point when the mouse is moving instead of one straight line.

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]>      <html class="no-js"> <!--<![endif]-->
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title></title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="">
    </head>

    <style>
        *{
            box-sizing: border-box;
        }

        body,html{
            padding: 0;
            margin: 0;
            height: 100vh;
            width: 100vw;
        }


    </style>
    <body>
       
        <canvas>
            
        </canvas>
        
        <script>
            var colorLine = 'black'
            var fillLine = 'none'
            var prevX = ''
            var prevY = ''
            var isDrawing = false
            var snapshot;
            var canvas = document.querySelector('canvas');
            var ctx = canvas.getContext('2d');

            
            let backgroundSetUp = () => {
                
                canvas.height = window.innerHeight - 50; 
                canvas.width = window.innerWidth - 50;
                ctx.fillStyle = colorLine;
                ctx.fillStyle = fillLine
            };

            let startDrawing = e => {
                isDrawing = true
                
                prevX = e.offsetX 
                prevY = e.offsetY 
                ctx.beginPath();
                ctx.strokeStyle  = 'black'
                ctx.fillStyle = '#fff'
           
            }

            let Drawing = e => {
                if(isDrawing === false)
                return
                ctx.beginPath();
                ctx.moveTo(prevX, prevY);
                ctx.lineTo(e.offsetX, e.offsetY);
                ctx.stroke()

                
            }

            let stopDrawing = e => {
                isDrawing = false
            }






            canvas.onmousedown = ( e => {
                startDrawing(e)
            })
            canvas.onmousemove = ( e => {
                Drawing(e)
            })
            canvas.onmouseup = ( e => {
                stopDrawing(e)
            })

            backgroundSetUp()


        </script>
    </body>
</html>
  • You need to _clear_ your canvas before drawing it again, and probably store your starting position so you can reuse it with the new mouse position to draw a line to. In the future, this will also require a buffer so you can redraw the previous state of the canvas, draw your new line and repeat... – somethinghere Mar 26 '23 at 08:05

2 Answers2

1

You should clear your canvas drawing before you do another draw:

ctx.clearRect( 0, 0, canvas.width, canvas.height );

This will reset the canvas before you draw your 'corrected' line to the new mouse position. If you want to draw further lines you will need another canvas with a copy of the previous drawing, and draw it back into the output canvas instead of using clear.

var colorLine = 'black'
var fillLine = 'none'
var prevX = ''
var prevY = ''
var isDrawing = false
var snapshot;
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');


let backgroundSetUp = () => {
    canvas.height = window.innerHeight - 50; 
    canvas.width = window.innerWidth - 50;
    ctx.fillStyle = colorLine;
    ctx.fillStyle = fillLine
};

let startDrawing = e => {
    isDrawing = true
    prevX = e.offsetX 
    prevY = e.offsetY;
    ctx.beginPath();
    ctx.strokeStyle  = 'black'
    ctx.fillStyle = '#fff'

}

let Drawing = e => {
    if(isDrawing === false) return
    // This is where you redraw or clear the previous canvas
    ctx.clearRect( 0, 0, canvas.width, canvas.height );
    ctx.beginPath();
    ctx.moveTo(prevX, prevY);
    ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke()
}

let stopDrawing = e => {
    isDrawing = false
}






canvas.onmousedown = ( e => {
    startDrawing(e)
})
canvas.onmousemove = ( e => {
    Drawing(e)
})
canvas.onmouseup = ( e => {
    stopDrawing(e)
})

backgroundSetUp()
<canvas></canvas>

Here is one with a buffer instead, allowing you to continue drawing after the first line. I have also cleaned up some of your definitions (correctly using const and let, ignoring var and using function where it makes sense to create a little more concise and clear code):

function onResize(){

    canvas.height = window.innerHeight - 50; 
    canvas.width = window.innerWidth - 50;
    
    ctx.fillStyle = colorLine;
    ctx.fillStyle = fillLine;
    
}
function onPointerStart( e ){
  
    // Write the current state into the buffer when we start
    buffer.width = canvas.width;
    buffer.height = canvas.height;
    bufferCtx.drawImage( canvas, 0, 0 );
    
    isDrawing = true;
    prevX = e.offsetX;
    prevY = e.offsetY;
    ctx.beginPath();
    ctx.strokeStyle  = 'black';
    ctx.fillStyle = '#fff';

}
function onPointerMove( e ){

    if( isDrawing === false ) return;
    
    // Clear the screen, redraw the buffer into it
    ctx.clearRect( 0, 0, canvas.width, canvas.height );
    ctx.drawImage( buffer, 0, 0 );
    
    // Draw your path
    ctx.beginPath();
    ctx.moveTo(prevX, prevY);
    ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    
}
function onPointerEnd( e ){

    isDrawing = false;
    
}

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

// This will be our buffer
const buffer = document.createElement('canvas');
const bufferCtx = buffer.getContext('2d');

let colorLine = 'black';
let fillLine = 'none';
let prevX = 0;
let prevY = 0;
let isDrawing = false;
let snapshot;

window.addEventListener( 'mousedown', onPointerStart );
window.addEventListener( 'mousemove', onPointerMove );
window.addEventListener( 'mouseup', onPointerEnd );
window.addEventListener( 'resize', onResize );

onResize()
<canvas></canvas>
somethinghere
  • 16,311
  • 2
  • 28
  • 42
0

Store the position of existing line, so you can erase it

Disconcertingly, I needed to make the erasing-line a little thicker (3px) than the drawing line (1px) to get full erasure. Even 2px of erasing was not quite enough. I am open to explanations why this might be - it surprised me.

var colorLine = 'black'
var fillLine = 'none'
var prevX = ''
var prevY = ''
var currX = ''
var currY = ''
var isDrawing = false
var snapshot;
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');


let backgroundSetUp = () => {
  canvas.height = window.innerHeight - 50;
  canvas.width = window.innerWidth - 50;
  ctx.fillStyle = colorLine;
  ctx.fillStyle = fillLine
};

let startDrawing = e => {
  isDrawing = true
  prevX = e.offsetX
  prevY = e.offsetY
  currX = prevX
  currY = prevY

}

let Drawing = e => {
  if (isDrawing === false)
    return


  ctx.strokeStyle = 'white'
  ctx.lineWidth = 3;
  ctx.beginPath();
  ctx.moveTo(prevX, prevY);
  ctx.lineTo(currX, currY);
  ctx.stroke()

  currX = e.offsetX
  currY = e.offsetY
  ctx.strokeStyle = 'black'
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(prevX, prevY);
  ctx.lineTo(currX, currY);
  ctx.stroke()
}

let stopDrawing = e => {
  isDrawing = false
}


canvas.onmousedown = (e => {
  startDrawing(e)
})
canvas.onmousemove = (e => {
  Drawing(e)
})
canvas.onmouseup = (e => {
  stopDrawing(e)
})

backgroundSetUp()
* {
  box-sizing: border-box;
}

body,
html {
  padding: 0;
  margin: 0;
  height: 100vh;
  width: 100vw;
}
<canvas>

</canvas>

Alternatively, clear the canvas before drawing

If there was anything else on the canvas, you will lose it unless you store the information and re-draw it.

Why we need to erase with thicker lines and why XORing won't work

I have found this StackOverflow answer explaining why the erasing line needs to be wider. It also explains why we can't do a clever trick with XOR to allow us to draw and remove a line, without disturbing anything that was already on the canvas.

https://stackoverflow.com/a/16740592/7549483

In short, when we draw, the browser does anti-aliasing, which is a complex process that is not easily reversible. That requires the erasing line to be wider, and prevents XORing doing what we hope it would.

ProfDFrancis
  • 8,816
  • 1
  • 17
  • 26
  • 1
    The alternative is to store a buffer canvas with the data from when the click started, and fill the canvas again with that before starting. – somethinghere Mar 26 '23 at 08:15
  • Also, since canvasses aren't actually white but _transparent_ you have actually drawn a bunch of white lines...? You are just being fooled into it being cleared, but give your page a background color and boom, the same result but white with one black line. And even then, the line thickness etc... No, you need to `clear` it somehow and redraw the line. – somethinghere Mar 26 '23 at 08:32
  • Yes, we could clear but it makes me sad if there was something there before. Is there some way to draw it with XOR, so it is added and exactly removed, without disturbing what is behind? – ProfDFrancis Mar 26 '23 at 08:42
  • Yes, you could with `globalCompositeOperation`s, but that sounds really finnicky. A buffer is the commonly used way for this, and it makes sense. – somethinghere Mar 26 '23 at 08:59