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

DooScrib - A jQuery plugin for creating a simple drawing canvas

4.11/5 (2 votes)
11 Mar 2013CPOL13 min read 25.5K   261  
The first article in a series where the final outcome is a shared canvas that people can use on a web page.

Introduction

Just as some of mankind's earliest form of communication include cave drawings of migration patterns, in today's world we continue to use drawings as a way of communicating amongst. I still remember from my days in college a professor drawing a hurricane as he was describing infinite loops and the destruction that they can bring one another. Each form communicates an idea or a picture in easier ways as time progresses.

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 series of articles I plan to develop a working solution for a shared digital canvas where users can collaborate in real time. As each article is written I will expand on dooScrib.com to eventually create a full working example.

Background

I can still remember as a very young boy in the 70's going to a GTE switchroom with my father and hearing all the switches clank. With my eyes all big and round in amazement my father would explain in simplest form the process of connecting one customer to another via the telephone. At home my father and I would use my 150 in 1 electronic kit to build complex things inside of a simple platform, to help further the ideas he showed me from his work.

Making complex things seem simple was always the message I gained from my father. Even in today's world I am amazed at the development and delivery of technology in a manner that makes things simple.

So it shouldn't be amazing that one night while I was watching my wife play Draw Something that I thought to myself what a simple idea.

Hey I can build something like that and in the process show it's simplicity for others to expand upon it.

Requirements

The basic requirement for this solution is that our end user is using a browser which supports HTML 5 and the canvas element. Lucky for us developers most 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.

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

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. Since I am huge fan and supporter of jQuery I am going to fashion as much of this as possible around a custom plug-in. I am also going to add in the requirement of working on mobile devices as well.

Step one - jQuery plug-in

I like simplicity and my preference is to shield complexity away as much as possible. So, from a use perspective I am thinking that I want something simple enough that a developer can add the following code:

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

A few questions needs to be answered; what element on the page do I want to attach this to, any special styling elements I want to apply, how thick should the line be and finally what size should the canvas be? I know some of you are saying that width and height are styling elements and normally I would agree with you, but in this case they simply aren't.

Basics of a plug-in

There are a lot of articles on writing jQuery plug-ins, and I don't want to spend too much time on the basics of writing one. What I have below is the basic framework for starting a jQuery plug-in with some comments to give a basic understanding.

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);

The details of a plug-in really occur within the creativity of your solution. If you have never written one before, but have always been interested then my recommendation would be to start with something like the above as your starting framework.

My only advice is to be creative but at the same time keep it simple.

Step two - Create a drawing surface

To create a drawing surface we will have to insert something like the following:

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

I know that some of you are wondering why the height and width are attributes of the element. Why not define these in the CSS like all others are 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 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. Since the canvas element came from Apple and was first introduced in WebKit I sided on this being a requirement.

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

Ok so lets start coding up the plug-in now that we have some basics in place.

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);
        }
    }
})(jQuery);

The basics are now done. You could actually run the above and attach it to one or more elements on the page, the end result would, be that it would add a canvas element to whatever element(s) you attached it to.

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

With the Canvas added, this still isn't going to be enough. If we want to do any drawing on it we will need to create a context that we can use to draw with.

The following bit of code can be added right after the Canvas is added and it will work for creating the context object that we need. There are some other methods and variables (penSize, cap) referenced that I will be discussing later in the article.

JavaScript
dooScrib.penSize(defaultOptions.penSize);

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

Now we have a canvas created, as well as we have the drawing context needed to actually draw lines and and other graphics. With this out of the way we can now begin adding some of that javascript needed in order to bring canvas to life.

Step three - Handle user input

Since we are creating a drawing surface, we will need to know when the user is drawing versus when they are just moving the cursor. To handle the user input, lets consume the events for when the mouse is moving, as well as when its clicked and released. Before we get into the code for consuming mouse events, we should talk about mobile devices.

Mobile devices

Turns out that with mobile devices, we 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 ways to detect mobile or touch based devices from your web page. 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 the browser discovery taken care of, we can now begin to add some code that will take care of subscribing to the appropriate events needed for dealing with user input. The following code is added, just below the code that we added for getting the context needed for drawing on the canvas that we added.

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. It fits in perfect for some of the later development, when I plan to start sharing the drawing canvas with multiple users.

The following code includes all of the options that can 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) {}
};

Step four - Let's start drawing

So if you lack patience, as I do at times, you are probably beginning to wonder when do we start 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 are 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 basically preventing the default behavior of the event using the preventDefault() function that is attached to the event object. 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. We take the difference of the coordinates received in relation to the offset of where the canvas is located in the browser.

Draw some lines

I had originally integrated the process of drawing lines directly into the mouse events, however, as my design progressed I realized that was not going to work to reach the ultimate goal of creating a shared digital canvas. Plus, I wanted to give users of the plugin the ability to recreate or draw lines without touch or mouse events.

So I moved the actual working of drawing lines into the following bt 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, lets instead discuss the properties of the pen options, as well as, the code that actually draws the line.

Pen properties

  • lineCap
  • Set the style of how the line will end. This can either be butt, rounded or square. I have a private variable that stores the value which can be modified by a public function that I will cover later.

  • strokeStyle
  • Set the color of the line that will be drawn. Again I have created a private variable that stores the value which can be modified by a public function that I will cover later.

  • lineWidth
  • Set the width of the line that will be drawn. Like the previous two properties I have created a private variable that stores the value which can be modified by a public function that I will cover later.

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.

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

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

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

  • stroke
  • Draw the actual line.

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

Expand on user input

Now that we have our line drawing function in place, lets plug it into the user events and start to do something.

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 moved 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, making 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 its 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;
};

Step five - Let's add some fluff

Thin black lines get boring after awhile so I added the following to give the control more usability and creativity for the user.

  • penSize(value)
  • Allows you to either get or set the width of the pen to use while drawing.

  • lineCap(value)
  • Allows you to either get or set the shape of how a line will end. Valid values include butt, round or square.

  • lineColor(value)
  • Allows you to set the color of the pen to use while drawing a line. Values are CSS verified which means you can use #------ values or English readable values like Red, Green, Blue, etc.

Step six - Use it

This part is really dependent on you. Download the code, play with the example I have included, or maybe integrate it into one of your own projects.

The next article

In the next article I plan to integrate the plugin into a project using Node.js, Express and socket.io to begin to create a shared Canvas across the internet.

History

  • 10 March, 2013: Initial version.

License

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