Tutorial WWW 2012

De $1

Version de 22:06, 19 Avr 2024

cette version.

Revenir à liste des archives.

Voir la version actuelle

_

Introduction

In this tutorial, you will learn how to write a multi-participant Paint program in pure HTML5.

Required tools

The recommend tools are :

  • A recent web browser that supports the <canvas> tag, most do,
  • For live web cam capture, you will need a special Opera build (Windows, Linux, Mac) or Chrome Canary build (Windows, better than Opera for Mac)
  • Check that you have a JavaScript debugger in your browser (Firefox users will need to install Firebug.
  • Editor of your choice for HTML/Javascript (recommended : WebStorm from JetBrains, free licence for researchers/education, 30 days free trial for others).
  • For steps 7 and superior that involves WebSockets, you will need the NodeJS web server and some modules. See NodeJS installation guide for Windows, Linux, Mac.

Download lab exercices

All the source files you will need are included in this archive (4Mo). Unzip, go in the "Paint_HTML5_Multi_Tutorial". You will find subdirectories step1, step2, etc... corresponding to each step described in this document.

Step 1 : a very simple paint !

Go into the step 2 directory. You may  try the example by opening paint.html in your browser.

Notice the first lines of this file :

<!DOCTYPE HTML>
<!-- Note : in HTML5 the DOCTYPE can be memorized by a normal human being -->
<!-- Common mistake : if we put a comment before the doctype element, Internet Explorer will not
     consider the document as a HTML5 document and the canvas tag for example,
     will not be displayed ! -->

The DOCTYPE is the brand new ultra-simple HTML5 DOCTYPE, yeah !

Notice that if you write ANYTHING before this DOCTYPE line, IE9 will not consider the file as HTML5, and you will no be able to run the example ! The <canvas> tag in the page will be ignored. Other browsers will however do nothing nasty if you write some comment at the top of the document.

Then, we declare a CSS and some JavaScript scripts. Yep, we will use jQuery for most DOM manipulation and event handling. HTML5 proposes the new document.querySelector(...) that behaves closely like jQuery selectors but is so much longer than $(...),  and jQuery uses querySelector internally...

<link rel="stylesheet" href="css/paint.css"/>

<script src="js/jquery-1.7.2.min.js"></script>
<script src="js/utils.js"></script>
<script src="js/paint.js"></script>

paint.js holds most of the code for a small "draw when mouse moves" paint program, while utils.js contains a utility function for getting correct mouse cursor position.

When the document if fully loaded and the DOM ready, we create a Paint object, that will hold most of our JavaScript code.

<script type="text/javascript">
// Run when the DOM is ready
$(document).ready(function () {
  // Create the pseudo object which will handle the main canvas
  paint = new PaintObject("canvasMain");
});
</script>

The parameter ("canvasMain") is the Id of the <canvas> tag you will find in the HTML part of the page :

<canvas id="canvasMain">
    <p>Canvas tag not supported by your browser</p>
</canvas>

Let' have a look at the PaintObject code in the paint.js file... First we get a reference on the canvas element and secondly we get a context object in order to draw in the canvas :

function PaintObject(canvas) {
    // get handle of the main canvas, as a DOM object, not as a jQuery Object.
    var mainCanvas = $("#"+canvas).get(0);
    var mainContext = mainCanvas.getContext('2d');
    ...

We also bind a mouse move listener to the canvas :

// Bind event on the canvas
$("#canvasMain").mousemove(this.mouseMoveListener);

The mouse listener is a method of the PaintObject that draw a line at each mouse move, from the previous mouse position to the current one :

// Mouse motion listener for drawing lines
    this.mouseMoveListener = function (event) {
        // we delegate the computation of the mouse position
        // to a utility function (in utils.js) as this is not so trivial
        // we must take into account insets, etc.
        var mousePos = getMousePos(mainCanvas, event);

        // Let's draw some lines that follow the mouse pos
        if (!started) {
            previousMousePos = mousePos;
            started = true;
        } else {
            mainContext.beginPath();
            mainContext.moveTo(previousMousePos.x, previousMousePos.y);
            mainContext.lineTo(mousePos.x, mousePos.y);
            mainContext.closePath();
            // draws as wireframe the drawing orders between beginPath() and closePath()
            mainContext.stroke();

            previousMousePos = mousePos;
        }
    };

Notice that in order to draw a line we declare a path, we move to a position, we draw a line to another position, we close the path. The path is drawn at the mainContext.stroke() call. It will be drawn with the current mainContext.strokeStyle value, which is defaulted to Black.

Be careful : just forget to close the path and at every mouse move you will draw the whole path from the beginning ! Indeed : mainContext.stroke() draws every draw primitive declared since the context.beginPath() call... if a beginPath() is called while a path is not closed, it is ... ignored.

Notice the trick we use in order to set the size of the canvas depending on the size of the browser. Instead of using CSS percentages on canvas width and height, that produce ugly scaling of canvas content, we adjust the size from a JavaScript piece of code :

// Canvas doesnt scale well with '%' in CSS so we use a little trick.
// We give them the size of one of their parent node which can be scalable.
mainCanvas.height = $("#content")[0].clientHeight;
mainCanvas.width = $("#content")[0].clientWidth;

Step 2 : manage mouseUp, mouseMove, mouseDown, define a pencil drawing object

Go into the step 2 directory. You may  try the example by opening paint.html in your browser.

This time we draw only when the mouse moves while a mouse button is pressed !

We also separated the code that draws from the initialisation of the PaintObject. the file drawingtools.js will hold "drawing objects" like pencil, line, rectangle, circle and so on... while paint.js will just do the job for creating contexts, binding events, etc.

We used jQuery to bind different mouse events to the canvas (paint.js)

// Create the drawing tool
var drawingTool = new pencilDrawingTool(); 

// Bind events on the canvas
$("#canvasMain").mousedown(drawingTool.mouseDownListener);
$("#canvasMain").mousemove(drawingTool.mouseMoveListener);
$("#canvasMain").mouseup(drawingTool.mouseUpListener);

 and here is the drawingtool.js content with only a tool that draws like a pencil (notice the 3 listener methods for mouseDown, mouseMove and mouseUp events) :

// Previous position of the mouse
var previousMousePos;

// Drawing tool object
function pencilDrawingTool() {
    this.mouseDownListener = function (event) {
        paint.started = true;
        previousMousePos = getMousePos(paint.getFrontCanvas(), event);
    };

    this.mouseMoveListener = function (event) {
        // we delegate the computation of the mouse position
        // to a utility function as this is not so trivial
        var mousePos = getMousePos(paint.getMainCanvas(), event);

        // Let's draw some lines that follow the mouse pos
        if (paint.started) {
            paint.getMainContext().beginPath();
            paint.getMainContext().moveTo(previousMousePos.x, previousMousePos.y);
            paint.getMainContext().lineTo(mousePos.x, mousePos.y);
            paint.getMainContext().closePath();
            paint.getMainContext().stroke();
        }
        previousMousePos = mousePos;
    };

    this.mouseUpListener = function (event) {
        paint.started = false;
    }
};

Step 3 : add a second, transparent canvas on top of the main canvas, for drawing elastic lines

Go into the step 3 directory. You may  try the example by opening paint.html in your browser.

This example shows an interesting use of canvases : they can be layered one on top of another. Using relative position and z-index with different values, they can act as layers in photoshop. By default they are transparent, and if you use the context.clearRect(x, y, width, height) function you will make a transparent rectangle and be able to "see through" it.

We will use this feature to draw an elastic line (lines that follow the mouse position) on a transparent front canvas, without modifying the main canvas.

In order to animate the line when the mouse moves, we clear the front canvas before drawing a line in its new position.

Adding a new canvas on top of the previous one

This is done in paint.js :

// Prepare a second canvas on top of the previous one, kind of second "layer" 
 var frontCanvas = document.createElement('canvas');
 frontCanvas.id = 'canvasFront';

 // Add the temporary canvas as a second child of the mainCanvas parent.
 mainCanvas.parentNode.appendChild(frontCanvas);

// Get the context for drawing in the canvas
var frontContext = frontCanvas.getContext('2d');

this.getFrontCanvas = function () {
        return frontCanvas;
}

this.getFrontContext = function () {
        return frontContext;
}

We added a line drawing tool in drawingtools.js

We declared an array that will hold the different drawing tools (pencil, line, etc) :

var DEFAULT_TOOL = 'pencil';
...
// Create the drawing tool
var drawingTool = new setOfDrawingTools[DEFAULT_TOOL]();

And the event binding has been modified in order to call the current drawing tool mouse listener method :

// bind events. We use a function multiplexEvent that will call the proper listeners
// methods of the currentTool.
this.bindMultiplexEvents = function () {
    $("#canvasFront").mousedown(this.multiplexEvents);
    $("#canvasFront").mousemove(this.multiplexEvents);
    $("#canvasFront").mouseup(this.multiplexEvents);
}

// if currentTool is pencil, and event.type is mousemove, will
// call pencil.mousemouve(event), if currentTool is line and
// event.type is mouseup, will call line.mouseup(event) etc.
this.multiplexEvents = function (event) {
    drawingTool[event.type](event);
}

...notice that in the line drawing tool (drawingtools.js) we draw only in the front canvas, and before we clear the front canvas :

// the Line Drawing Tool Object
setOfDrawingTools.line = function () {
    this.mousedown = function (event) {
        paint.started = true;
        previousMousePos = getMousePos(paint.getFrontCanvas(), event);
    };

    this.mousemove = function (event) {
        var mousePos = getMousePos(paint.getFrontCanvas(), event);
        if (paint.started) {
            // Clear front canvas before drawing the elastic line in a new position
            paint.getFrontContext().clearRect(0, 0, paint.getFrontCanvas().width, 
                                                    paint.getFrontCanvas().height);

            paint.getFrontContext().beginPath();
            paint.getFrontContext().moveTo(previousMousePos.x, previousMousePos.y);
            paint.getFrontContext().lineTo(mousePos.x, mousePos.y);
            paint.getFrontContext().stroke();
            paint.getFrontContext().closePath();
        }
    };

    this.mouseup = function (event) {
        paint.started = false;
    }
};

 In the paint.html file, we added some spans in order to select the current tool (l

<div id="drawCommands">
    <h6>Pick a tool</h6>
    <span id="line">line</span>
    <span id="pencil">pencil</span>
</div>

And in paint.js we detect clicks on them :

// Handle the drawing tools menu. The selected entry value can be 'Pencil',
// 'Line' etc.
this.changeDrawingTool = function () {
    // this.id is the id of the selected menu item
    drawingTool = new setOfDrawingTools[this.id]();
}

// Bind the changeDrawingTool function onClick to every menu items.
$("#drawCommands").find("span").click(this.changeDrawingTool);