Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Drawing on an HTML Canvas

5.00/5 (3 votes)
27 Apr 2014CPOL10 min read 17.4K  
Development of a jQuery plugin to be used to create and draw on a canvas element

Introduction

With the expanding use and availability of HTML5 and the Canvas element, the medium of communication on the web becomes easier for developers to implement.

In this article, I plan to go over the development of a jQuery plugin that you can use to create and draw on an HTML5 canvas element. A working example on dooScrib.com exists for you to check out what is possible from a very basic idea.

Background

The basic requirement for this solution is that our end user is using a browser which supports HTML 5 and of course the canvas element. Lucky for us, modern browsers come with this support and even better mobile browsers like those found on iOS and Android devices come with this support as well.

Simple detection of the <canvas> element can be handled on the page and information presented to our user in the event their browser does not support it.

XML
<canvas id="drawingSurface" width="100" height="100"> no canvas support </canvas>

If it's not obvious already, let me say that the <canvas> element is in my opinion one of the greatest additions to HTML. Unlike so many of the other HTML elements, this one requires some JavaScript in making it really come alive.

So from a development perspective, HTML5 and JavaScript are needed.

Also since I am a huge fan and supporter of jQuery, it's probably even better that I fashion this development into a plug-in.

Using the Code

So with the plugin complete, my objective is that any developer can use it by simply adding something like the following to their page:

JavaScript
$('#surface').dooScribPlugin({
    width:300,
    height:400,
    cssClass:'pad',
    penSize:4
});

With those few lines of code, I am answering some important questions.

  • What element should the canvas be created as a child of?
  • How big does the canvas element need to be when it is created on the page?
  • Are there any styling parameters (CSS) that should be added to the element?
  • Also what the initial thickness should the pen be when the user begins to draw?

So with the basics out of the way, let's begin to create the plugin.

Basics of a Plug-in

There are a lot of articles on developing jQuery plug-ins, so I don't want to spend too much time on the basics of writing one. What I have below is the basic framework that I have put together for when I want to write a plugin. Entirely possible that there are better ways but over the years this is what I have fashioned as working for me.

JavaScript
(function($) {
    // using $.fn.extend allows us to expand on jquery
    $.fn.extend({pluginName:function(options){
        // save a link to your instance
        var plugin = this;
        
        var defaultOptions = {
            // add what you know are default values for your options
        };
        
        // connect your default values to what the user has added
        // I connect everything into the current instance so that it 
        // can be referenced later if needed.
        if (options)
            plugin.Settings = $.extend(defaultOptions, options);
        else
            plugin.Settings = defaultOptions;
            
        // private functions
        function functionName(values){
            // code here
        }
        
        // public functions
        plugin.functionName = function(values){
            // code here
        }
        
        // implement get/set for your object properties
        var variableName;
        plugin.variableName = function(v){
            // validate data sent in
            if(undefined !== v){
                variableName = v;
            }
            
            return variableName;
        }
    
        return this.each(function(){
            // initialization code goes here
        });
    }});
})(jQuery);

So now that we have most of the basics done and out of the way, let's begin with the work of developing the plugin to do what we want.

Create the Drawing Canvas

To create the drawing canvas, we will have to insert something that looks like the following:

HTML
<canvas id="canvasid" class="className" width="100" height="100"></canvas>

Some of you are probably wondering why are the height and width being added as attributes of the element. Why not just define these in CSS like all others are (should be) handled?

Well it turns out that the canvas element doesn't "like" the height and width being defined as a style element. I spent several hours debugging issues with the mouse coordinates not coming across properly until I discovered this. What I noticed but ignored initially was that all the Safari canvas documentation spells out setting the height and width this way.

So since the canvas element came from Apple and was first introduced in WebKit, I sided on this being a requirement.

I have also added the ID so that the element can be selected later if needed and then the class is added so that it can be styled.

Let's code up the plug-in now to include the creation of canvas element on the page.

JavaScript
(function($) {
    $.fn.extend({dooScribPlugin:function(options){
        var dooScrib = this;

        var defaultOptions = {
            penSize:2,
            width:100,
            height:100,
            cssClass:''
        };

        if (options)
            dooScrib.Settings = $.extend(defaultOptions, options);
        else
            dooScrib.Settings = defaultOptions;     
            
        if(true === isNaN(dooScrib.Settings.height)){
            dooScrib.Settings.height = 100;
        }
        
        if(true === isNaN(dooScrib.Settings.width)){
            dooScrib.Settings.width = 100;
        }

        var ID = this.attr('ID');
        if ((undefined === ID) || ('' === ID)){
            ID = 'dooScribCanvas'+Math.round($.now()*Math.random());
        }
        else {
            ID = 'dooScribCanvas'+Math.round($.now()*Math.random())+ID;
        }

        return this.each(function(){
            $("<canvas id='"+ID+"' class='"+defaultOptions.cssClass+"' 
            height='"+defaultOptions.height+"' width='"+defaultOptions.width+"'></canvas<").appendTo(dooScrib);

            dooScrib.penSize(defaultOptions.penSize);

            drawingSurface = document.getElementById(ID).getContext('2d');
            drawingSurface.lineWidth = dooScrib.penSize();
            drawingSurface.lineCap = cap;
        }
    }
})(jQuery);

So we still have the basic plugin discussed earlier but now we have added some code to detect whether any values were entered for height and width of canvas that will be created. If there are no values supplied, I decided to continue with random value of 100 for both the height and width.

Next, we proceeded to create a value that will be used later as the ID for the canvas element that will be created on the page.

Then finally comes the work of creating a canvas element.

As you can see, the ID is automatically generated and associated with a random number. I chose to go this route in the event that the plug-in is associated with a collection of elements, causing multiple canvas elements to be created.

Handling User Input

Since we are creating a drawing surface, we will need to know whenever the user is drawing versus when they are just moving the cursor. To handle this user input, let's consume the events for when the mouse is moving, as well as when the mouse button has been clicked and released. Before we get into the code for consuming mouse events, I want to talk about mobile devices.

Mobile Devices

Turns out that with mobile devices, we generally don't have events like mousedown, mouseup, or mousemove. Instead on mobile or touch based devices, we have "touch" events.

Amazing no?

There are several different ways to detect mobile or touch based devices from your web page. However, since I am focused on the consumption of specific touch events I am going to query the window to see if it supports touch events.

I added the following code as a public method to the plugin, so that whoever is using the plugin can also be aware of the browsers ability to support touch.

JavaScript
dooScrib.hasTouch = function() {
    return 'ontouchstart' in window;
};

I also added the following code as private method of normalizing touch events, so that they will contain the X and Y coordinate information in the same format as mouse events.

JavaScript
function normalizeTouch(e) {
    if (true === dooScrib.hasTouch()) {
        if (['touchstart', 'touchmove', 'touchend'].indexOf(e.type) > -1) {
            e.clientX = event.targetTouches[0].pageX;
            e.clientY = event.targetTouches[0].pageY;
        }
    }

    return e;
}

Consuming Events

With browser discovery taken care of, we can now begin to add some code that will take care of subscribing to the appropriate events that are needed for dealing with user input. The following code is added, just below the code that we added earlier for getting the context needed to draw on the canvas that we created.

JavaScript
if (false === dooScrib.hasTouch()) {
    document.getElementById(ID).addEventListener('mousedown', clickDown, true);
    document.getElementById(ID).addEventListener('mousemove', moved, true);
    document.getElementById(ID).addEventListener('mouseup', clickUp, true);
}
else {
    document.getElementById(ID).addEventListener('touchstart', clickDown, true);
    document.getElementById(ID).addEventListener('touchmove', moved, true);
    document.getElementById(ID).addEventListener('touchend', clickUp, true);
}

Before I go over the code for dealing with the events, I want to update the settings for the plugin as well. It would probably be a good idea to inform the user of the plugin about the different events as they happen.

The following code includes all of the options that can now be passed into the plugin by the user.

JavaScript
var defaultOptions = {
    penSize:2,
    width:100,
    height:100,
    cssClass:'',
    onClick: function(e) {},
    onMove: function(e) {},
    onPaint: function(e) {},
    onRelease: function(e) {}
};

Is it Time to Start Drawing??

So if you are like me and lack patience, you are probably beginning to wonder when do we start actually drawing some lines? Well, in those famous words I hated to hear from my parents on long road trips. "We are almost there."

Before I get deep into the details of drawing lines, let me go over the following bit of code.

JavaScript
var moved = function(e) {
if (!e) {
    e = window.event;
}

if (true === dooScrib.hasTouch()) {
    e.preventDefault();
    e = normalizeTouch(e);
}

var offset = $(dooScrib).offset();
var pt = new Point(e.clientX - offset.left, e.clientY - offset.top);

In all of the mouse/touch events that will be received, the first thing that needs to be done is to validate whether or not an event was passed in. In the event that nothing was passed into the function and we are just being notified that an event exists; we will need to pull it from the window.

Does this ever happen with mouse or touch events? I think in all my years of working with events, I have only been confronted with this type of scenario maybe one or two times. However, the time I spent in debugging has scarred me enough that I handle them this way now.

So the next thing to do, is to handle the case where it's a touch based event so that the browser doesn't think that the user is beginning a hold or drag event. We can do this by preventing the default behavior of the event using the preventDefault() function that is attached to the event object. After that then we normalize the touch event so that it contains X and Y coordinates in a format that is consistent with mouse events.

The final piece of code to review is properly defining the X and Y coordinates in relation to the canvas and its actual position on the screen. We take the difference of the coordinates received in relation to the offset of where the canvas is located in the browser.

Time to Draw Some Lines

I had originally integrated the process of drawing lines directly into the mouse events, however, as time progressed I thought it best to give users of the plugin the ability to recreate or draw lines without touch or mouse events.

So I moved the actual work of drawing lines into the following bit of code:

JavaScript
dooScrib.drawLine = function(fromX, fromY, toX, toY) {
    if ((undefined !== fromX) && (undefined !== fromY) && 
            (undefined !== toX) && (undefined !== toY)) {
        if((false === isNaN(Number(fromX))) && (false === isNaN(Number(fromY))) && 
                 (false === isNaN(Number(toX))) && (false === isNaN(Number(toY)))) {
            // set all the pen options
            drawingSurface.lineCap = cap;
            drawingSurface.strokeStyle = color;
            drawingSurface.lineWidth = penWidth;

            drawingSurface.beginPath();

            drawingSurface.moveTo(fromX, fromY);

            drawingSurface.lineTo(toX, toY);
            drawingSurface.stroke();
        }
    }
}

I won't go into the data validation code because my hopes are that it is already fairly self documenting. So instead let's discuss the properties of the pen options, as well as, the code that actually draws the line.

Drawing a Line

Once you have the properties set, drawing a line on a canvas is a very simple process that takes four steps to complete. Think of it like a Bob Ross painting tutorial.

First, you pick up the brush that you want to paint with.

Set the beginning point for where the line will be drawn from.

Set the ending point for where the line will drawn to.

Draw the actual line.

  • beginPath
  • moveTo
  • lineTo
  • stroke

OK, so maybe my Bob Ross impersonation needs some work but I am hoping you get the point.

Expand on User Input

Now that we have our line drawing function in place, let's plug it into the user events that can reported to the user of the plugin and start to draw.

In the following code example, I am handling the event where the user clicks the mouse button or touches the screen on their mobile device. Since we have already discussed basic event handling, normalizing the touch events and defining the coordinates in relation to the position of the canvas I have removed that code from the examples that will follow.

Handling mouse clicks or a touch event as you can see in the following code, is basically recording the location and setting a flag that will indicate that drawing has begun. After all the work is taken care of, it notifies the user of the plug-in, if they have supplied an event handler. It is probably worth pointing out, that in each of these functions the code also returns false to prevent the event from being handled further.

JavaScript
var clickDown = function(e) {
    prevPoint = pt;

    dooScrib.drawing = true;
    dooScrib.Settings.onClick(pt);

    return false;
};

When the user has released the mouse button or lifted their finger from the screen, we are notified via the clickUp function. At this point, we only need to save those coordinates, clear the drawing flag and then notify subscribers via the onRelease callback.

JavaScript
var clickUp = function(e) {
    dooScrib.Settings.onRelease(pt);
    dooScrib.drawing = false;

    return false;            
};

The plugin will get move events regardless of whether the user has clicked the mouse or not. So when these events are received, we will test whether the drawing flag has been set or not.

If the plugin is currently in drawing mode, then we will take the previous coordinates and with the new coordinates we are now able to draw a line. Finally, make sure to save the current coordinates for any subsequent calls where a previous point would be needed to complete a new line.

The moved event will notify users of the plugin via the OnPaint event or the onMove event.

One thing to point out is that in the case of mobile devices, the plugin will only receive move events while it's in drawing mode.

JavaScript
var moved = function(e) {
    if (true === dooScrib.isDrawing()) {
        dooScrib.drawLine(prevPoint.X, prevPoint.Y, pt.X, pt.Y);
        prevPoint = pt;

        dooScrib.Settings.onPaint(pt);
    }
    else {
        dooScrib.Settings.onMove(pt);
    }

    return false;
};

Points of Interest

Over the past year, I have been focusing my development efforts towards Objective-C but my longer term developments for this plugin include the use of Node.js, Express and socket.io to create a digitally shared canvas on the site dooScrib.

History

  • 10 March, 2013: Original dooscrib article written here
  • 27 April, 2014: This article written as cleanup to previous

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)