Issue with canvas straight lines

54 Views Asked by At

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>

2

There are 2 best solutions below

0
somethinghere On BEST ANSWER

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>

4
ProfDFrancis On

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.