Simple Canvas Drawing with Vanilla JS

Simple Canvas Drawing with Vanilla JS
Project: Canvas Drawing App
Author: Sean Esteva
Edit Online: View on CodePen
License: MIT

This code provides a simple way to draw on a canvas using Vanilla JavaScript. It allows users to draw various shapes such as lines, circles, squares, rectangles, ellipses, and polygons. The code utilizes mouse events to track the user’s interactions with the canvas, including mouse down, move, and up events.

Additionally, it provides functionality for changing line width, fill color, stroke color, and background color. The code is easy to understand and modify, making it helpful for beginners learning about canvas drawing or anyone looking to implement basic drawing features on a web page.

How to Create Simple Canvas Drawing with Vanilla JS

First of all, load the Pure CSS library by adding the following CDN link to the head tag of your webpage.

<link rel='stylesheet' href='https://yui.yahooapis.com/pure/0.6.0/pure-min.css'>

Create the HTML structure for drawing board as follows:

<div class="controls">
    <h3>Shape:</h3>
    <div class='lightBorder' >
    <p><input type="radio" name="shape" value="line" checked="checked"> Line</p>
    <p><input type="radio" name="shape" value="rect"> Rectangle</p>
    <p><input type="radio" name="shape" value="circle"> Circle</p>
    <p><input type="radio" name="shape" value="square"> Square</p>
    <p><input type="radio" name="shape" value="ellipse"> Ellipse</p>
    <p><input type="radio" name="shape" value="polygon"> Polygon</p>
  </div>
    <h3>Line Type:</h3>
    <div class='lightBorder'>
    <p><label>Line Width: <input id='lineWidth' type='range' step='1' min="1" max="200" value="4"></label></p>
    Line End:
    <p><input name="lineCap" value="round" type='radio' checked="checked">round</p>
    <p><input name="lineCap" value="square" type='radio'>square</p>
    <p><input name="lineCap" value="butt" type='radio'>butt</p>

  </div>
  <h3>Colors:</h3>
    <div class='lightBorder'>

    <!-- color -->
    <p><label>Fill Color: <input id='fillColor' type='color' step='1' value="#24B0D5"></label></p>
    <p><label>Stroke Color: <input id='strokeColor' type='color' step='1'> </label></p>
    <p><label>Background Color:<input id='backgroundColor' type='color' step='1' value="#ffffff"></label></p>
    *changing background will delete image!
  </div>
    <div class='lightBorder'>
    <!-- polygon sides -->
    <p><label>Polygon Sides: <input id='polygonSides' type='range' step='1' min="3" max="12"></label></p>

  </div>

  <form>
  <h3>Input Text:</h3> <input type="text" id="textInput"/>
  </form>

    <!-- fill or no fill, you decide -->
    <p><label>Fill: <input id='fillBox' type='checkbox' checked='checked'></label></p>
    <p><label style="font-size:12px;">XOR: <input id='xor' type='checkbox'></label></p>

    <button type="button" class="button-error pure-button" id="clearCanvas" name="button">Clear Canvas</button>
  </div>

  <canvas id="canvas" width="650" height="800"></canvas>

Now, style the drawing board using the following CSS styles:

canvas {
  float: left;
  position: relative;
  border-radius: 10px;
  margin: 10px;
  border: 1px solid gray;

  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

#canvas1 {
  margin-left: 1150px;
  margin-top: 30px;
}

.controls {
  width: 200px;
  padding: 12px;
  border: 1px solid gray;
  border-radius: 10px;
  background: #D9D9D9;
  transition: all .5s ease;
  float: left;
  position: relative;
  margin: 10px;
  height: 800px;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  font-size: 13px;
  font-family: Arial;

}
.controls h3 {
  color: #3D3D3D;
  text-transform: uppercase;
  font-size: 11px;
  line-height: 10px;
}

.lightBorder {
  border: 1px solid #B6B6B6;
  margin-top: 10px;
  margin-bottom: 18px;
  padding: 5px;
  border-radius: 7px;
  background: #EEEEEE;
  box-shadow: inset 1px 1px 10px rgba(0,0,0,0.1);
}

}

h1 {
  margin-left: 300px;
}

p {
  margin: 4px;
}



/*body {
background: #FF4E50;
  background: linear-gradient(to left, #FF4E50 , #F9D423);
  transition: all .5s ease;
}*/

.button-success,
.button-error,
.button-warning,
.button-secondary {
    color: white;
    border-radius: 4px;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}

.button-success {
    background: rgb(28, 184, 65); /* this is a green */
}

.button-error {
    background: rgb(202, 60, 60); /* this is a maroon */
}

.button-warning {
    background: rgb(223, 117, 20); /* this is an orange */
}

.button-secondary {
    background: rgb(66, 184, 221); /* this is a light blue */
}

Finally, add the following JavaScript function to activate the drawing board.

var canvas,
    context,
    dragging = false, //this will be dragging if mouse move is followed by mouse down
    dragStartLocation,
    snapshot;

//getBoundingClientRect
//The returned value is a DOMRect object, which contains read-only left, top, right and
//bottom properties describing the border-box in pixels. top and left are relative to the top-left of the viewport.

// rect is a DOMRect object with six properties: left, top, right, bottom, width, height
//var rect = obj.getBoundingClientRect();

//define mouse coordinates
function getCanvasCoordinates(event) {
    var x = event.clientX - canvas.getBoundingClientRect().left;
    var y = event.clientY - canvas.getBoundingClientRect().top;

    //return object where x is x and y is y
    return {x: x, y: y};
}

//this avoids dragging the image
//The getImageData() method returns an ImageData object that copies the pixel data for the specified rectangle on a canvas.
function takeSnapShot() {
    snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
//These must be added to dragStart()
function restoreSnapShot() {
    context.putImageData(snapshot, 0, 0);
}

function drawLine(position) {
    context.beginPath();
    context.moveTo(dragStartLocation.x, dragStartLocation.y);//this will be the first point and during mouse up the line will be drawn
    context.lineTo(position.x, position.y); //current position of x and y during mouseMove event
    context.stroke(); // The stroke() method actually draws the path you have defined with all those moveTo() and lineTo() methods. So Cool!
}

//The arc() method creates an arc/curve (used to create circles, or parts of circles). To create a circles set start angle to 0 and end angle to 2*Math.PI
function drawCircle(position) { //takes position during mouse up
    var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
    context.beginPath();
    context.arc(dragStartLocation.x, dragStartLocation.y, radius, 0, 2 * Math.PI);
    // context.fill();
}


function drawEllipse(position) {
  var w = position.x - dragStartLocation.x ;
  var h = position.y - dragStartLocation.y  ;
    var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
    context.beginPath();
    //Used the .ellipse method instead of arc to give an extra radius, radius x and radius
    var cw = (dragStartLocation.x > position.x) ? true : false;

    console.log(cw);
    context.ellipse(dragStartLocation.x, dragStartLocation.y, Math.abs(w), Math.abs(h), 0, 2 * Math.PI, false);
}


function drawRect(position) {
  console.log(position.x, dragStartLocation.x);
    var w = position.x - dragStartLocation.x ;
    var h = position.y - dragStartLocation.y  ;
    context.beginPath();
    context.rect(dragStartLocation.x, dragStartLocation.y, w, h);
}


function drawPolygon(position, sides, angle) {
    var coordinates = [],
        radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2)),
        index = 0;

    for (index; index < sides; index++) {
        coordinates.push({
            x: dragStartLocation.x + radius * Math.cos(angle),
            y: dragStartLocation.y - radius * Math.sin(angle)
        })
        angle += (2 * Math.PI) / sides;
    }


    context.beginPath();
    context.moveTo(coordinates[0].x, coordinates[0].y);

    for (index = 0; index < sides; index++) {
        context.lineTo(coordinates[index].x, coordinates[index].y);
    }

    context.closePath();
    // context.fill();

}

function draw(position) {
    var fillBox = document.getElementById("fillBox")
        , shape = document.querySelector('input[type="radio"][name="shape"]:checked').value
        , polygonSides = document.getElementById('polygonSides').value
// ,     polygonAngle = document.getElementById('polygonAngle').value
        , polygonAngle = calculateAngle(dragStartLocation, position)
        , lineCap = document.querySelector('input[type="radio"][name="lineCap"]:checked').value
        , writeCanvas = document.getElementById('textInput').value
        , xor = document.getElementById('xor');

    //global context
    context.lineCap = lineCap;
    
    //we don't need even't handlers because before drawing we are jsut taking a default value

    if (shape === "circle") {
        drawCircle(position);
    }
    if (shape === "square") {
        drawPolygon(position, 4, Math.PI / 4);
    }
    if (shape === "line") {
        drawLine(position);
    }
    if (shape === "ellipse") {
        drawEllipse(position);
    }
    if (shape === "rect") {
        drawRect(position);
    }
    if (shape === "polygon") {
        drawPolygon(position, polygonSides, polygonAngle * (Math.PI / 180));
    }
    if (xor.checked){
      context.globalCompositeOperation = "xor";
    } else {
      context.globalCompositeOperation = "source-over";
    }
    if (fillBox.checked) {
        context.fill();
    } else {
        context.stroke();
    }
}

//define dragstart, drag and dragStop
function dragStart(event) {
    dragging = true;
    dragStartLocation = getCanvasCoordinates(event);
    takeSnapShot();
}
function calculateAngle(start, current) {

    var angle = 360 - Math.atan2(current.y - start.y, current.x - start.x) * 180 / Math.PI;


    return angle;
}
function drag(event) {
    var position;
    if (dragging === true) {
        restoreSnapShot();
        position = getCanvasCoordinates(event);
        //generic
        draw(position);
    }
}

//Drag Stop
function dragStop(event) {
    dragging = false; //dragging stops here
    restoreSnapShot();
    var position = getCanvasCoordinates(event);
    //generic
    draw(position);
}

//Line Width Here
function changeLineWidth() {
    context.lineWidth = this.value;
    //**important**
    //event.stopPropagation() prevents the vent from bubblim up the DOM tree, preventing any parent handlers from being notified of the event.
    event.stopPropagation();
}
//Fill Color
function changeFillStyle() {
    context.fillStyle = this.value;
    event.stopPropagation();
}
//Stroke Color
function changeStrokeStyle() {
    context.strokeStyle = this.value;
    event.stopPropagation();
}

//backgroundColor
function changeBackgroundColor() {
    context.save();
    context.fillStyle = document.getElementById('backgroundColor').value;
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.restore();

    //more info on save() and resote() www.html5.litten.com/understanding-save-and-restore-for-the-canvas-context/
}

function eraseCanvas() {
    context.clearRect(0, 0, canvas.width, canvas.height);
}

//write on canvas
function writeCanvas() {
    context.font = '55px Impact';
    context.fillText(textInput.value, 25, 175);
    console.log(textInput.value);
}

//function invoked when document is fully loaded
function init() {
    canvas = document.getElementById('canvas');
    context = canvas.getContext('2d');


    var lineWidth = document.getElementById('lineWidth'),
        fillColor = document.getElementById('fillColor'),
        strokeColor = document.getElementById('strokeColor'),
        canvasColor = document.getElementById('backgroundColor'),
        clearCanvas = document.getElementById('clearCanvas'),
        textInput = document.getElementById('textInput');
    // context.strokeStyle = 'rebeccapurple';

    // context.font = '25px Impact';
    // context.fillText(writeCanvas, 50, 150);
    // var imgElement = document.getElementById("logo");
    // context.fillStyle = context.createPattern(imgElement, 'repeat');
    context.lineWidth = lineWidth.value;

    // no need for linecap will be defined directly by user
    // context.lineCap = 'square';

    context.fillStyle = fillColor.value;
    //shapes made transparent by overlapping shapes
    // context.globalCompositeOperation = 'xor';


    //event listeners below
    canvas.addEventListener('mousedown', dragStart, false);
    canvas.addEventListener('mousemove', drag, false);
    canvas.addEventListener('mouseup', dragStop, false);
    lineWidth.addEventListener('input', changeLineWidth, false);
    fillColor.addEventListener('input', changeFillStyle, false);
    strokeColor.addEventListener('input', changeStrokeStyle, false);
    canvasColor.addEventListener('input', changeBackgroundColor, false);
    clearCanvas.addEventListener('click', eraseCanvas, false);
    textInput.addEventListener('input', writeCanvas, false);
}

window.addEventListener('load', init, false);

That’s all! hopefully, you have successfully created a simple canvas drawing board using Vanilla JS. If you have any questions or suggestions, feel free to comment below.

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *