171

I want to draw on a HTML Canvas using a mouse (for example, draw a signature, draw a name, ...)

How would I go about implementing this?

Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
MartinJoo
  • 2,784
  • 9
  • 33
  • 39

13 Answers13

307

Here is a working sample.

 <html>
    <script type="text/javascript">
    var canvas, ctx, flag = false,
        prevX = 0,
        currX = 0,
        prevY = 0,
        currY = 0,
        dot_flag = false;

    var x = "black",
        y = 2;
    
    function init() {
        canvas = document.getElementById('can');
        ctx = canvas.getContext("2d");
        w = canvas.width;
        h = canvas.height;
    
        canvas.addEventListener("mousemove", function (e) {
            findxy('move', e)
        }, false);
        canvas.addEventListener("mousedown", function (e) {
            findxy('down', e)
        }, false);
        canvas.addEventListener("mouseup", function (e) {
            findxy('up', e)
        }, false);
        canvas.addEventListener("mouseout", function (e) {
            findxy('out', e)
        }, false);
    }
    
    function color(obj) {
        switch (obj.id) {
            case "green":
                x = "green";
                break;
            case "blue":
                x = "blue";
                break;
            case "red":
                x = "red";
                break;
            case "yellow":
                x = "yellow";
                break;
            case "orange":
                x = "orange";
                break;
            case "black":
                x = "black";
                break;
            case "white":
                x = "white";
                break;
        }
        if (x == "white") y = 14;
        else y = 2;
    
    }
    
    function draw() {
        ctx.beginPath();
        ctx.moveTo(prevX, prevY);
        ctx.lineTo(currX, currY);
        ctx.strokeStyle = x;
        ctx.lineWidth = y;
        ctx.stroke();
        ctx.closePath();
    }
    
    function erase() {
        var m = confirm("Want to clear");
        if (m) {
            ctx.clearRect(0, 0, w, h);
            document.getElementById("canvasimg").style.display = "none";
        }
    }
    
    function save() {
        document.getElementById("canvasimg").style.border = "2px solid";
        var dataURL = canvas.toDataURL();
        document.getElementById("canvasimg").src = dataURL;
        document.getElementById("canvasimg").style.display = "inline";
    }
    
    function findxy(res, e) {
        if (res == 'down') {
            prevX = currX;
            prevY = currY;
            currX = e.clientX - canvas.offsetLeft;
            currY = e.clientY - canvas.offsetTop;
    
            flag = true;
            dot_flag = true;
            if (dot_flag) {
                ctx.beginPath();
                ctx.fillStyle = x;
                ctx.fillRect(currX, currY, 2, 2);
                ctx.closePath();
                dot_flag = false;
            }
        }
        if (res == 'up' || res == "out") {
            flag = false;
        }
        if (res == 'move') {
            if (flag) {
                prevX = currX;
                prevY = currY;
                currX = e.clientX - canvas.offsetLeft;
                currY = e.clientY - canvas.offsetTop;
                draw();
            }
        }
    }
    </script>
    <body onload="init()">
        <canvas id="can" width="400" height="400" style="position:absolute;top:10%;left:10%;border:2px solid;"></canvas>
        <div style="position:absolute;top:12%;left:43%;">Choose Color</div>
        <div style="position:absolute;top:15%;left:45%;width:10px;height:10px;background:green;" id="green" onclick="color(this)"></div>
        <div style="position:absolute;top:15%;left:46%;width:10px;height:10px;background:blue;" id="blue" onclick="color(this)"></div>
        <div style="position:absolute;top:15%;left:47%;width:10px;height:10px;background:red;" id="red" onclick="color(this)"></div>
        <div style="position:absolute;top:17%;left:45%;width:10px;height:10px;background:yellow;" id="yellow" onclick="color(this)"></div>
        <div style="position:absolute;top:17%;left:46%;width:10px;height:10px;background:orange;" id="orange" onclick="color(this)"></div>
        <div style="position:absolute;top:17%;left:47%;width:10px;height:10px;background:black;" id="black" onclick="color(this)"></div>
        <div style="position:absolute;top:20%;left:43%;">Eraser</div>
        <div style="position:absolute;top:22%;left:45%;width:15px;height:15px;background:white;border:2px solid;" id="white" onclick="color(this)"></div>
        <img id="canvasimg" style="position:absolute;top:10%;left:52%;" style="display:none;">
        <input type="button" value="save" id="btn" size="30" onclick="save()" style="position:absolute;top:55%;left:10%;">
        <input type="button" value="clear" id="clr" size="23" onclick="erase()" style="position:absolute;top:55%;left:15%;">
    </body>
    </html>
Kauê Gimenes
  • 1,278
  • 1
  • 13
  • 30
user1083202
  • 3,102
  • 1
  • 14
  • 2
  • 14
    Why do you do switch? You can also do x = obj.id. Done. – MarijnS95 Feb 09 '14 at 21:01
  • 1
    I'm trying to use this code, but have issues with the drawing being off vertically when I'm not scrolled all the way down on the page. What should I change in this code? – Cameron Darlington Dec 11 '14 at 22:13
  • I know its a bit late, but I've tried this in Safari for the iPad and it does not work. Any fix for this? – masmrdrr Mar 08 '16 at 11:29
  • @masmrdrr I believe it is because it uses mouse events, which do not work well with touch devices. – 16patsle Mar 12 '17 at 16:11
  • seems variable `dot_flag` is not needed? Or am I missing something? – Gnijuohz May 05 '17 at 23:06
  • @CameronDarlington See this question for a few quality ways of grabbing the mouse position: https://stackoverflow.com/questions/17130395/real-mouse-position-in-canvas I had the same problem. – Kulahan Jul 22 '17 at 09:00
  • @Gnijuohz the dot_flag is used to create the initial drawing. It's not necessary, but your drawing will always be off from where you click if you don't use it, so it's a bit inaccurate – Kulahan Jul 22 '17 at 09:01
  • great resource but it doesn't consider the scroll effect for mouse x and y – code4j Aug 24 '17 at 06:43
  • 10
    I changed `canvas.offsetLeft;` and `canvas.offsetTop;` with `canvas.getBoundingClientRect().left;` and `canvas.getBoundingClientRect().top;` respectively to fix the scrolling problem. – KWILLIAMS Nov 15 '17 at 19:27
  • 2
    This code got me started on the right path. But i have to say that this code is pretty rough and ugly. For anyone finding this and struggling with mouse position, take a look at this answer: https://stackoverflow.com/a/17130415/5552144 – LuqJensen Dec 11 '18 at 00:22
  • 1
    Does not work on a touch screen. Not referring to tablets. I mean a Windows 10 laptop with both a mouse and a touch screen. – Paul Mar 30 '20 at 20:07
  • 2
    to get this to work with a drawing pen(like a graphics tablet) the mouse events have to be replaced with touch events eg. `touchmove`, `touchstart`, `touchend` and then the `clientX` comes from `e.touches["0"].clientX` in the `findxy()` code, haven't thought of an easy way to detect what's being used though, as you can't listen to both events at the same time from what I've tested. I left `mouseout` as is. It's not perfect, but it does work – Jacob David C. Cunningham Apr 02 '20 at 06:13
  • working on this again, wanted to point out if your position is offset the places to edit are in findxy specifically the currX, currY for down/move. add or subtract there till it's right – Jacob David C. Cunningham Jun 16 '23 at 00:22
86

I think, other examples here are too complicated. This one is simpler and JS only...

// create canvas element and append it to document body
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);

// some hotfixes... ( ≖_≖)
document.body.style.margin = 0;
canvas.style.position = 'fixed';

// get canvas 2D context and set him correct size
var ctx = canvas.getContext('2d');
resize();

// last known position
var pos = { x: 0, y: 0 };

window.addEventListener('resize', resize);
document.addEventListener('mousemove', draw);
document.addEventListener('mousedown', setPosition);
document.addEventListener('mouseenter', setPosition);

// new position from mouse event
function setPosition(e) {
  pos.x = e.clientX;
  pos.y = e.clientY;
}

// resize canvas
function resize() {
  ctx.canvas.width = window.innerWidth;
  ctx.canvas.height = window.innerHeight;
}

function draw(e) {
  // mouse left button must be pressed
  if (e.buttons !== 1) return;

  ctx.beginPath(); // begin

  ctx.lineWidth = 5;
  ctx.lineCap = 'round';
  ctx.strokeStyle = '#c0392b';

  ctx.moveTo(pos.x, pos.y); // from
  setPosition(e);
  ctx.lineTo(pos.x, pos.y); // to

  ctx.stroke(); // draw it!
}
Matěj Pokorný
  • 16,977
  • 5
  • 39
  • 48
  • How do I get it to paint on mouse move, not by clicking? – RyanCameron.Me Apr 06 '17 at 18:47
  • 1
    @RyanCameron.Me just comment line `if (e.buttons !== 1) return;` ;-). – Matěj Pokorný Apr 06 '17 at 19:02
  • I got it working on my footer but now the line is about 10px away from my cursor – RyanCameron.Me Apr 07 '17 at 08:50
  • If my cursor is in the middle of the canvas it lines up perfectly with the line but if I move the cursor up or down, the line moves further away from the cursor. Any ideas? – RyanCameron.Me Apr 07 '17 at 11:19
  • 1
    @RyanCameron.Me Just tried newest Chrome, Firefox and Edge and everything working... What browser do you use? – Matěj Pokorný Apr 07 '17 at 11:32
  • I think it's because I made the canvas relative. Please scroll to the footer on this page to see what I am talking about: http://weight.ryancameron.me/ – RyanCameron.Me Apr 10 '17 at 05:16
  • 1
    @RyanCameron.Me It's more likely because my `resize` function. I'm setting width and height of canvas based on window size. You should set these based on your `
    `.
    – Matěj Pokorný Apr 10 '17 at 10:53
  • 1
    that helped with also subtracting about 250 from the y coordinate on the set position function. Thanks so much for your help! – RyanCameron.Me Apr 10 '17 at 11:36
  • this will only work if the canvas is the full width and height of the window – Taylor A. Leach May 14 '19 at 01:11
  • 1
    @TaylorA.Leach yep, if canvas is not placed in top right corner, you will need to add some `offset` in `setPosition` function... – Matěj Pokorný May 14 '19 at 07:57
  • What is resize() for? Why do you need to resize the canvas? It can be fixed in width and height. – Timo Feb 24 '21 at 19:50
  • 1
    @Timo Yep, you can have canvas with fixed width and height, but I choose version where canvas cover entire page. It did not add much complexity, but looks better for this demo :-). – Matěj Pokorný Feb 24 '21 at 23:17
  • 4
    This is great but makes assumptions about canvas positioning. More generic: `var rect = canvas.getBoundingClientRect(); pos.x = e.clientX - rect.left; pos.y = e.clientY - rect.top;` – darma Feb 20 '22 at 08:53
48

Here's the most straightforward way to create a drawing application with canvas:

  1. Attach a mousedown, mousemove, and mouseup event listener to the canvas DOM
  2. on mousedown, get the mouse coordinates, and use the moveTo() method to position your drawing cursor and the beginPath() method to begin a new drawing path.
  3. on mousemove, continuously add a new point to the path with lineTo(), and color the last segment with stroke().
  4. on mouseup, set a flag to disable the drawing.

From there, you can add all kinds of other features like giving the user the ability to choose a line thickness, color, brush strokes, and even layers.

jakub.g
  • 38,512
  • 12
  • 92
  • 130
Eric Rowell
  • 5,191
  • 23
  • 22
13

I was looking to use this method for signatures as well, i found a sample on http://codetheory.in/.

I've added the below code to a jsfiddle

Html:

<div id="sketch">
    <canvas id="paint"></canvas>
</div>

Javascript:

 (function() {
    var canvas = document.querySelector('#paint');
    var ctx = canvas.getContext('2d');

    var sketch = document.querySelector('#sketch');
    var sketch_style = getComputedStyle(sketch);
    canvas.width = parseInt(sketch_style.getPropertyValue('width'));
    canvas.height = parseInt(sketch_style.getPropertyValue('height'));

    var mouse = {x: 0, y: 0};
    var last_mouse = {x: 0, y: 0};

    /* Mouse Capturing Work */
    canvas.addEventListener('mousemove', function(e) {
        last_mouse.x = mouse.x;
        last_mouse.y = mouse.y;

        mouse.x = e.pageX - this.offsetLeft;
        mouse.y = e.pageY - this.offsetTop;
    }, false);


    /* Drawing on Paint App */
    ctx.lineWidth = 5;
    ctx.lineJoin = 'round';
    ctx.lineCap = 'round';
    ctx.strokeStyle = 'blue';

    canvas.addEventListener('mousedown', function(e) {
        canvas.addEventListener('mousemove', onPaint, false);
    }, false);

    canvas.addEventListener('mouseup', function() {
        canvas.removeEventListener('mousemove', onPaint, false);
    }, false);

    var onPaint = function() {
        ctx.beginPath();
        ctx.moveTo(last_mouse.x, last_mouse.y);
        ctx.lineTo(mouse.x, mouse.y);
        ctx.closePath();
        ctx.stroke();
    };

}());
Sam Jones
  • 4,443
  • 2
  • 40
  • 45
  • 1
    The best part is you can draw straight connected lines by right clicking at different locations. :) – Aidiakapi May 14 '14 at 15:26
  • Does not work on a touch screen. Not referring to tablets. I mean a Windows 10 laptop with both a mouse and a touch screen. – Paul Mar 30 '20 at 20:08
  • I get: Uncaught TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'. – Timo Feb 24 '21 at 19:55
6

Here is my very simple working canvas draw and erase.

https://jsfiddle.net/richardcwc/d2gxjdva/

//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var canvasx = $(canvas).offset().left;
var canvasy = $(canvas).offset().top;
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
var tooltype = 'draw';

//Mousedown
$(canvas).on('mousedown', function(e) {
    last_mousex = mousex = parseInt(e.clientX-canvasx);
 last_mousey = mousey = parseInt(e.clientY-canvasy);
    mousedown = true;
});

//Mouseup
$(canvas).on('mouseup', function(e) {
    mousedown = false;
});

//Mousemove
$(canvas).on('mousemove', function(e) {
    mousex = parseInt(e.clientX-canvasx);
    mousey = parseInt(e.clientY-canvasy);
    if(mousedown) {
        ctx.beginPath();
        if(tooltype=='draw') {
            ctx.globalCompositeOperation = 'source-over';
            ctx.strokeStyle = 'black';
            ctx.lineWidth = 3;
        } else {
            ctx.globalCompositeOperation = 'destination-out';
            ctx.lineWidth = 10;
        }
        ctx.moveTo(last_mousex,last_mousey);
        ctx.lineTo(mousex,mousey);
        ctx.lineJoin = ctx.lineCap = 'round';
        ctx.stroke();
    }
    last_mousex = mousex;
    last_mousey = mousey;
    //Output
    $('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});

//Use draw|erase
use_tool = function(tool) {
    tooltype = tool; //update
}
canvas {
    cursor: crosshair;
    border: 1px solid #000000;
}
<canvas id="canvas" width="800" height="500"></canvas>
<input type="button" value="draw" onclick="use_tool('draw');" />
<input type="button" value="erase" onclick="use_tool('erase');" />
<div id="output"></div>
Richard
  • 922
  • 11
  • 11
6

I had to provide a simple example for this subject so I'll share here:

http://jsfiddle.net/Haelle/v6tfp2e1

class SignTool {
  constructor() {
    this.initVars()
    this.initEvents()
  }

  initVars() {
    this.canvas = $('#canvas')[0]
    this.ctx = this.canvas.getContext("2d")
    this.isMouseClicked = false
    this.isMouseInCanvas = false
    this.prevX = 0
    this.currX = 0
    this.prevY = 0
    this.currY = 0
  }

  initEvents() {
    $('#canvas').on("mousemove", (e) => this.onMouseMove(e))
    $('#canvas').on("mousedown", (e) => this.onMouseDown(e))
    $('#canvas').on("mouseup", () => this.onMouseUp())
    $('#canvas').on("mouseout", () => this.onMouseOut())
    $('#canvas').on("mouseenter", (e) => this.onMouseEnter(e))
  }
  
  onMouseDown(e) {
   this.isMouseClicked = true
    this.updateCurrentPosition(e)
  }
  
  onMouseUp() {
   this.isMouseClicked = false
  }
  
  onMouseEnter(e) {
   this.isMouseInCanvas = true
    this.updateCurrentPosition(e)
  }
  
  onMouseOut() {
   this.isMouseInCanvas = false
  }

  onMouseMove(e) {
    if (this.isMouseClicked && this.isMouseInCanvas) {
     this.updateCurrentPosition(e)
      this.draw()
    }
  }
  
  updateCurrentPosition(e) {
      this.prevX = this.currX
      this.prevY = this.currY
      this.currX = e.clientX - this.canvas.offsetLeft
      this.currY = e.clientY - this.canvas.offsetTop
  }
  
  draw() {
    this.ctx.beginPath()
    this.ctx.moveTo(this.prevX, this.prevY)
    this.ctx.lineTo(this.currX, this.currY)
    this.ctx.strokeStyle = "black"
    this.ctx.lineWidth = 2
    this.ctx.stroke()
    this.ctx.closePath()
  }
}

var canvas = new SignTool()
canvas {
  position: absolute;
  border: 2px solid;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="500" height="300"></canvas>
Haelle
  • 335
  • 1
  • 3
  • 10
5

A super short version, here, without position:absolute in vanilla JavaScript. The main idea is to move the canvas' context to the right coordinates and draw a line. Uncomment click handler and comment mousedown & mousemove handlers below to get a feel for how it is working.

<!DOCTYPE html>
<html>
<body>

<p style="margin: 50px">Just some padding in y direction</p>

<canvas id="myCanvas" width="300" height="300" style="background: #000; margin-left: 100px;">Your browser does not support the HTML5 canvas tag.</canvas>

<script>
    const c = document.getElementById("myCanvas");
    // c.addEventListener("click", penTool); // fires after mouse left btn is released
    c.addEventListener("mousedown", setLastCoords); // fires before mouse left btn is released
    c.addEventListener("mousemove", freeForm);


    const ctx = c.getContext("2d");

    function setLastCoords(e) {
        const {x, y} = c.getBoundingClientRect();
        lastX = e.clientX - x;
        lastY = e.clientY - y;
    }

    function freeForm(e) {
        if (e.buttons !== 1) return; // left button is not pushed yet
        penTool(e);
    }

    function penTool(e) {
        const {x, y} = c.getBoundingClientRect();
        const newX = e.clientX - x;
        const newY = e.clientY - y;

        ctx.beginPath();
        ctx.lineWidth = 5;
        ctx.moveTo(lastX, lastY);
        ctx.lineTo(newX, newY);
        ctx.strokeStyle = 'white';
        ctx.stroke();
        ctx.closePath();

        lastX = newX;
        lastY = newY;
    }

    let lastX = 0;
    let lastY = 0;  
</script>

</body>
</html>
Nitin
  • 7,187
  • 6
  • 31
  • 36
  • 3
    It is important, not to set the width and height in CSS as it only stretches the canvas instead of resizing it - https://stackoverflow.com/a/8693791/1585523 – Nitin Dec 26 '19 at 04:06
3

Also check this one here:
Example:
https://github.com/williammalone/Simple-HTML5-Drawing-App

Documentation:
http://www.williammalone.com/articles/create-html5-canvas-javascript-drawing-app/

This document includes following codes:

HTML:

<canvas id="canvas" width="490" height="220"></canvas>

JS:

context = document.getElementById('canvas').getContext("2d");

$('#canvas').mousedown(function(e){
  var mouseX = e.pageX - this.offsetLeft;
  var mouseY = e.pageY - this.offsetTop;
        
  paint = true;
  addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
  redraw();
});

$('#canvas').mouseup(function(e){
  paint = false;
});

$('#canvas').mouseleave(function(e){
  paint = false;
});

var clickX = new Array();
var clickY = new Array();
var clickDrag = new Array();
var paint;

function addClick(x, y, dragging)
{
  clickX.push(x);
  clickY.push(y);
  clickDrag.push(dragging);
}

//Also redraw
function redraw(){
  context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas
  
  context.strokeStyle = "#df4b26";
  context.lineJoin = "round";
  context.lineWidth = 5;
            
  for(var i=0; i < clickX.length; i++) {        
    context.beginPath();
    if(clickDrag[i] && i){
      context.moveTo(clickX[i-1], clickY[i-1]);
     }else{
       context.moveTo(clickX[i]-1, clickY[i]);
     }
     context.lineTo(clickX[i], clickY[i]);
     context.closePath();
     context.stroke();
  }
}

And another awesome example
http://perfectionkills.com/exploring-canvas-drawing-techniques/

infomasud
  • 2,263
  • 1
  • 18
  • 12
vusan
  • 5,221
  • 4
  • 46
  • 81
3

It's been years since the question was asked and was answered.

For anyone who looks for a simple drawing canvas (eg, for taking the signature from the user/customer), here I am posting a more simplified jquery version of the currently accepted answer

$(document).ready(function() {
  var flag, dot_flag = false,
 prevX, prevY, currX, currY = 0,
 color = 'black', thickness = 2;
  var $canvas = $('#canvas');
  var ctx = $canvas[0].getContext('2d');

  $canvas.on('mousemove mousedown mouseup mouseout', function(e) {
    prevX = currX;
    prevY = currY;
    currX = e.clientX - $canvas.offset().left;
    currY = e.clientY - $canvas.offset().top;
    if (e.type == 'mousedown') {
      flag = true;
    }
    if (e.type == 'mouseup' || e.type == 'mouseout') {
      flag = false;
    }
    if (e.type == 'mousemove') {
      if (flag) {
        ctx.beginPath();
        ctx.moveTo(prevX, prevY);
        ctx.lineTo(currX, currY);
        ctx.strokeStyle = color;
        ctx.lineWidth = thickness;
        ctx.stroke();
        ctx.closePath();
      }
    }
  });

  $('.canvas-clear').on('click', function(e) {
    c_width = $canvas.width();
    c_height = $canvas.height();
    ctx.clearRect(0, 0, c_width, c_height);
    $('#canvasimg').hide();
  });
});
<html>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
  <body>
    <canvas id="canvas" width="400" height="400" style="position:absolute;top:10%;left:10%;border:2px solid;"></canvas>
    <input type="button" value="Clear" class="canvas-clear" />
  </body>
</html>
Munawir
  • 3,346
  • 9
  • 33
  • 51
3

I took the first answer and Changed as per my need. Now

  1. Works on mobile device also.
  2. more responsive,
  3. no issue with scroll

 <html>
  <head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
    <style>
        .selected::before {

           content: 'x';
           height: 30px;
           width: 30px;
           left: 18px;
           top: -8px;
           font-size: 3rem;
           position: absolute;
        }

        .select-colour{
          height: 30px;
          width: 30px;
        }

    </style>
 </head>
    <script type="text/javascript">
    var canvas, ctx, flag = false,
        prevX = 0,
        currX = 0,
        prevY = 0,
        currY = 0,
        dot_flag = false;

    var x = "black",
        y = 2;
    
    function init() {
        canvas = document.getElementById('can');
        ctx = canvas.getContext("2d");
        w = canvas.width;
        h = canvas.height;
    
        canvas.addEventListener("mousemove", function (e) {
            findxy('move', e, '')
        }, false);
        canvas.addEventListener("mousedown", function (e) {
            findxy('down', e, '')
        }, false);
        canvas.addEventListener("mouseup", function (e) {
            findxy('up', e, '')
        }, false);
        canvas.addEventListener("mouseout", function (e) {
            findxy('out', e, '')
        }, false);

        canvas.addEventListener("touchmove", function (e) {
            findxy('move', e, 'touch')
        }, false);
        canvas.addEventListener("touchstart", function (e) {
            findxy('down', e, 'touch')
        }, false);
        canvas.addEventListener("touchend", function (e) {
            findxy('up', e, 'touch')
        }, false);    

    }
    
    function color(obj) {        
        x = obj.id; 
        if (x == "white") y = 14;
        else y = 2;
        var prevSelected = document.getElementsByClassName("selected");
        // If it exists, remove it.
        if(prevSelected.length > 0) { 
          prevSelected[0].classList.remove("selected");
        }
                 
       document.getElementById(obj.id).classList.add("selected");
    }
    
    function draw() {
        ctx.beginPath();
        ctx.moveTo(prevX, prevY);
        ctx.lineTo(currX, currY);
        ctx.strokeStyle = x;
        ctx.lineWidth = y;
        ctx.stroke();
        ctx.closePath();
    }
    
    function erase() {
        var m = confirm("Want to clear");
        if (m) {
            ctx.clearRect(0, 0, w, h);
            document.getElementById("canvasimg").style.display = "none";
        }
    }
    
    function save() {
        document.getElementById("canvasimg").style.border = "2px solid";
        var dataURL = canvas.toDataURL();
        document.getElementById("canvasimg").src = dataURL;
        document.getElementById("canvasimg").style.display = "inline";
    }

    function download() {
        var link = document.createElement('a');
        var dataURL = canvas.toDataURL();
        link.href = dataURL;
        link.download = 'mydrawing.png';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }
    
    function findxy(res, e, source) {
        if(source){
            var clientX = e.changedTouches[0].clientX;
            var clientY = e.changedTouches[0].clientY;
        }else{
            var clientX = e.clientX;
            var clientY = e.clientY;
        }

        if (res == 'down') {
            prevX = currX;
            prevY = currY;
            currX = clientX - canvas.getBoundingClientRect().left;
            currY = clientY - canvas.getBoundingClientRect().top;
    
            flag = true;
            dot_flag = true;
            if (dot_flag) {
                ctx.beginPath();
                ctx.fillStyle = x;
                ctx.fillRect(currX, currY, 2, 2);
                ctx.closePath();
                dot_flag = false;
            }
        }

        if (res == 'up' || res == "out") {
            flag = false;
        }

        if (res == 'move') {
            if (flag) {
                prevX = currX;
                prevY = currY;
                currX = clientX - canvas.getBoundingClientRect().left;
                currY = clientY - canvas.getBoundingClientRect().top;
                draw();
            }
        }
    }
    </script>
  <body onload="init()">
        <div style="">
                <div style="float: left;">
                   <canvas id="can" width="600" height="500" style=" border:5px solid;"></canvas>
                </div>
                <div style="margin-left:20px; float:left;">
                    <div class="row">
                        <div class="col-12">Choose Color</div>
                        <div class="col-2 pt-3"><div class="select-colour" style="background:green;" id="green" onclick="color(this)"></div></div>
                       <div class="col-2 pt-3"><div class="select-colour" style="background:blue;" id="blue" onclick="color(this)"></div></div>
                       <div class="col-2 pt-3"><div class="select-colour" style="background:red;" id="red" onclick="color(this)"></div></div>
                       <div class="col-2 pt-3"><div class="select-colour" style="background:yellow;" id="yellow" onclick="color(this)"></div></div>
                       <div class="col-2 pt-3"><div class="select-colour" style="background:orange;" id="orange" onclick="color(this)"></div></div>
                       <div class="col-2 pt-3"><div class="select-colour" style="background:black;" id="black" onclick="color(this)"></div></div>
                       <div class="col-12 pt-3 pb-3" style="">Eraser</div>
                       <div class="col-2 pt-3"><div class="select-colour" style="background:white;border:2px solid;" id="white" onclick="color(this)"></div></div>
                </div>

                <div class="row">
                    <div class="col-6 col-lg-12 mt-3">
                        <input type="button" value="Download" id="download" size="23" onclick="download()" class="btn btn-primary">
                    </div>
                    <div class="col-6 col-lg-12 mt-3">
                            <input type="button" value="clear" id="clr" size="23" onclick="erase()" class="btn btn-light">
                    </div>          
                </div>
           </div>

        </div>
    </body>
    </html>
infomasud
  • 2,263
  • 1
  • 18
  • 12
2

I used what 1083202 did but removed all the controlles, and implemented the change that KWILLIAMS suggested, that made it non sensitive to scrolling. I also made the canvas big, basically over my whole page 2000x1600 px, excepts for the margins. I did remove all the drawing tools and buttons, and used "blue" as the only color.

I put the JS-code in a separate file named myJS.js, and put that in a folder named "JS" localy:

I then used a Stylus on my Laptop to write on the tuchpad, witch works out a bit better then with the finger or mouse.

The document I use it for is kind of informal, for internal use, but it is nice to be able to put some pen strokes on it before making a pdf-file.

    var canvas, ctx, flag = false,
        prevX = 0,
        currX = 0,
        prevY = 0,
        currY = 0,
        dot_flag = false;

    var x = "blue",
        y = 3;
    
    function init() {
        canvas = document.getElementById('can');
        ctx = canvas.getContext("2d");
        w = canvas.width;
        h = canvas.height;
    
        canvas.addEventListener("mousemove", function (e) {
            findxy('move', e)
        }, false);
        canvas.addEventListener("mousedown", function (e) {
            findxy('down', e)
        }, false);
        canvas.addEventListener("mouseup", function (e) {
            findxy('up', e)
        }, false);
        canvas.addEventListener("mouseout", function (e) {
            findxy('out', e)
        }, false);
    }
 
    function draw() {
        ctx.beginPath();
        ctx.moveTo(prevX, prevY);
        ctx.lineTo(currX, currY);
        ctx.strokeStyle = x;
        ctx.lineWidth = y;
        ctx.stroke();
        ctx.closePath();
    }
   
    function findxy(res, e) {
        if (res == 'down') {
            prevX = currX;
            prevY = currY;
            currX = e.clientX - canvas.getBoundingClientRect().left;
            currY = e.clientY - canvas.getBoundingClientRect().top;
    
            flag = true;
            dot_flag = true;
            if (dot_flag) {
                ctx.beginPath();
                ctx.fillStyle = x;
                ctx.fillRect(currX, currY, 2, 2);
                ctx.closePath();
                dot_flag = false;
            }
        }
        if (res == 'up' || res == "out") {
            flag = false;
        }
        if (res == 'move') {
            if (flag) {
                prevX = currX;
                prevY = currY;
                currX = e.clientX - canvas.getBoundingClientRect().left;
                currY = e.clientY - canvas.getBoundingClientRect().top;
                draw();
            }
        }
    }
<html> 



<body onload="init()">
<p>Below you can draw:</p>
<canvas id="can" width="750" height="1050" style="position:absolute;">Below you can write:</canvas>
</body>
Michael Larsson
  • 187
  • 2
  • 11
1

Let me know if you have trouble implementing this. It uses processing.js and has features for changing colors and making the draw point larger and smaller.

<html>
<head>
<!--script librarires-->
<script type="text/javascript" src="processing.js"></script>
<script type="text/javascript" src="init.js"></script>

<!--styles -->
<style type="text/css" src="stylesheet.css">
</style> 
</head>
<body>
<!--toolbox -->
<div id="draggable toolbox"></div>
<script type="application/processing">
// new script
int prevx, prevy;
int newx, newy;
boolean cliked;
color c1 = #000000;
int largeur=2;
int ps = 20;
int px = 50;
int py = 50;

void setup() {
size(500,500);
frameRate(25);
background(50);

 prevx = mouseX;
 prevy = mouseY;
 cliked = false;
 }

void draw() {
 //couleur
 noStroke(0);
 fill(#FFFFFF);//blanc
rect(px, py, ps, ps);
 fill(#000000);
 rect(px, py+(ps), ps, ps);
 fill(#FF0000);
 rect(px, py+(ps*2), ps, ps);
 fill(#00FF00);
 rect(px, py+(ps*3), ps, ps);
 fill(#FFFF00);
 rect(px, py+(ps*4), ps, ps);
 fill(#0000FF);
 rect(px, py+(ps*5), ps, ps);
 //largeur
 fill(#FFFFFF);
 rect(px, py+(ps*7), ps, ps);
  fill(#FFFFFF);
 rect(px, py+(ps*8), ps, ps);
 stroke(#000000);
 line(px+2, py+(ps*7)+(ps/2), px+(ps-2), py+(ps*7)+(ps/2));
 line(px+(ps/2), py+(ps*7)+1, px+(ps/2), py+(ps*8)-1);
 line(px+2, py+(ps*8)+(ps/2), px+(ps-2), py+(ps*8)+(ps/2));

 if(cliked==false){
  prevx = mouseX;
 prevy = mouseY;  
 }
 if(mousePressed) { 
  cliked = true;
 newx = mouseX;
  newy = mouseY;
  strokeWeight(largeur);
  stroke(c1);
  line(prevx, prevy, newx, newy);
  prevx = newx;
 prevy = newy;
 }else{
  cliked= false;
  }
}
void mouseClicked() {
 if (mouseX>=px && mouseX<=(px+ps)){
  //couleur
  if (mouseY>=py && mouseY<=py+(ps*6)){ 
   c1 = get(mouseX, mouseY);
  }
   //largeur
  if (mouseY>=py+(ps*7) && mouseY<=py+(ps*8)){ 
   largeur += 2;
  }
 if (mouseY>=py+(ps*8) && mouseY<=py+(ps*9)){ 
   if (largeur>2){
    largeur -= 2;
   }
  }
 }
}
</script><canvas></canvas>
</body>
</html>
expiredninja
  • 1,397
  • 4
  • 25
  • 45
1

if you have background image for your canvas, you will have to make some tweaks to have it work properly because white erasing trick will hide the background.

here is a gist with the code.

<html>
    <script type="text/javascript">
    var canvas, canvasimg, backgroundImage, finalImg;
    var mouseClicked = false;
    var prevX = 0;
    var currX = 0;
    var prevY = 0;
    var currY = 0;
    var fillStyle = "black";
    var globalCompositeOperation = "source-over";
    var lineWidth = 2;

    function init() {
      var imageSrc = '/abstract-geometric-pattern_23-2147508597.jpg'
      backgroundImage = new Image();
      backgroundImage.src = imageSrc;
      canvas = document.getElementById('can');
      finalImg = document.getElementById('finalImg');
      canvasimg = document.getElementById('canvasimg');
      canvas.style.backgroundImage = "url('" + imageSrc + "')";
      canvas.addEventListener("mousemove", handleMouseEvent);
      canvas.addEventListener("mousedown", handleMouseEvent);
      canvas.addEventListener("mouseup", handleMouseEvent);
      canvas.addEventListener("mouseout", handleMouseEvent);
    }

    function getColor(btn) {
      globalCompositeOperation = 'source-over';
      lineWidth = 2;
      switch (btn.getAttribute('data-color')) {
        case "green":
        fillStyle = "green";
        break;
        case "blue":
        fillStyle = "blue";
        break;
        case "red":
        fillStyle = "red";
        break;
        case "yellow":
        fillStyle = "yellow";
        break;
        case "orange":
        fillStyle = "orange";
        break;
        case "black":
        fillStyle = "black";
        break;
        case "eraser":
        globalCompositeOperation = 'destination-out';
        fillStyle = "rgba(0,0,0,1)";
        lineWidth = 14;
        break;
      }

    }

    function draw(dot) {
      var ctx = canvas.getContext("2d");
      ctx.beginPath();
      ctx.globalCompositeOperation = globalCompositeOperation;
      if(dot){
        ctx.fillStyle = fillStyle;
        ctx.fillRect(currX, currY, 2, 2);
      } else {
        ctx.beginPath();
        ctx.moveTo(prevX, prevY);
        ctx.lineTo(currX, currY);
        ctx.strokeStyle = fillStyle;
        ctx.lineWidth = lineWidth;
        ctx.stroke();
      }
      ctx.closePath();
    }

    function erase() {
      if (confirm("Want to clear")) {
        var ctx = canvas.getContext("2d");
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        document.getElementById("canvasimg").style.display = "none";
      }
    }

    function save() {
      canvas.style.border = "2px solid";
      canvasimg.width = canvas.width;
      canvasimg.height = canvas.height;
      var ctx2 = canvasimg.getContext("2d");
      // comment next line to save the draw only
      ctx2.drawImage(backgroundImage, 0, 0);
      ctx2.drawImage(canvas, 0, 0);
      finalImg.src = canvasimg.toDataURL();
      finalImg.style.display = "inline";
    }

    function handleMouseEvent(e) {
      if (e.type === 'mousedown') {
        prevX = currX;
        prevY = currY;
        currX = e.offsetX;
        currY = e.offsetY;
        mouseClicked = true;
        draw(true);
      }
      if (e.type === 'mouseup' || e.type === "mouseout") {
        mouseClicked = false;
      }
      if (e.type === 'mousemove') {
        if (mouseClicked) {
          prevX = currX;
          prevY = currY;
          currX = e.offsetX;
          currY = e.offsetY;
          draw();
        }
      }
    }
    </script>
    <body onload="init()">
      <canvas id="can" width="400" height="400" style="position:absolute;top:10%;left:10%;border:2px solid;">
      </canvas>
      <div style="position:absolute;top:12%;left:43%;">Choose Color</div>
      <div style="position:absolute;top:15%;left:45%;width:10px;height:10px;background:green;" data-color="green" onclick="getColor(this)"></div>
      <div style="position:absolute;top:15%;left:46%;width:10px;height:10px;background:blue;" data-color="blue" onclick="getColor(this)"></div>
      <div style="position:absolute;top:15%;left:47%;width:10px;height:10px;background:red;" data-color="red" onclick="getColor(this)"></div>
      <div style="position:absolute;top:17%;left:45%;width:10px;height:10px;background:yellow;" data-color="yellow" onclick="getColor(this)"></div>
      <div style="position:absolute;top:17%;left:46%;width:10px;height:10px;background:orange;" data-color="orange" onclick="getColor(this)"></div>
      <div style="position:absolute;top:17%;left:47%;width:10px;height:10px;background:black;" data-color="black" onclick="getColor(this)"></div>
      <div style="position:absolute;top:20%;left:43%;">Eraser</div>
      <div style="position:absolute;top:22%;left:45%;width:15px;height:15px;background:white;border:2px solid;" data-color="eraser" onclick="getColor(this)"></div>
      <canvas id="canvasimg" style="display:none;" ></canvas>
      <img id="finalImg" style="position:absolute;top:10%;left:52%;display:none;" >
      <input type="button" value="save" id="btn" size="30" onclick="save()" style="position:absolute;top:55%;left:10%;">
      <input type="button" value="clear" id="clr" size="23" onclick="erase()" style="position:absolute;top:55%;left:15%;">
    </body>
    </html>
Mohammed Essehemy
  • 2,006
  • 1
  • 16
  • 20