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.
<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:
$('#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.
(function($) {
$.fn.extend({pluginName:function(options){
var plugin = this;
var defaultOptions = {
};
if (options)
plugin.Settings = $.extend(defaultOptions, options);
else
plugin.Settings = defaultOptions;
function functionName(values){
}
plugin.functionName = function(values){
}
var variableName;
plugin.variableName = function(v){
if(undefined !== v){
variableName = v;
}
return variableName;
}
return this.each(function(){
});
}});
})(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:
<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.
(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.
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.
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.
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.
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.
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.
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:
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)))) {
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.
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.
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.
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.