Chapter 11
Canvas, SVG, and Drag and Drop

The HTML5 Herald is really becoming quite dynamic for an “ol’ timey” newspaper! We’ve added a video with the new video element, made our site available offline, added support to remember the user’s name and email address, and used geolocation to detect the user’s location.

But there’s still more we can do to make it even more fun. First, the video is a little at odds with the rest of the paper, since it’s in color. Second, the geolocation feature, while fairly speedy, could use a progress indicator that lets the user know we haven’t left them stranded. And finally, it would be nice to add just one more dynamic piece to our page. We’ll take care of all three of these using the APIs we’ll discuss in this chapter: Canvas, SVG, and Drag and Drop.

Canvas

With HTML5’s Canvas API, we’re no longer limited to drawing rectangles on our sites. We can draw anything we can imagine, all through JavaScript. This can improve the performance of our websites by avoiding the need to download images off the network. With canvas, we can draw shapes and lines, arcs and text, gradients and patterns. In addition, canvas gives us the power to manipulate pixels in images and even video. We’ll start by introducing some of the basic drawing features of canvas, but then move on to using its power to transform our video—taking our modern-looking color video and converting it into conventional black and white to match the overall look and feel of The HTML5 Herald.

The Canvas 2D Context spec is supported in:

  • Safari 2.0+

  • Chrome 3.0+

  • Firefox 3.0+

  • Internet Explorer 9.0+

  • Opera 10.0+

  • iOS (Mobile Safari) 1.0+

  • Android 1.0+

A Bit of Canvas History

Canvas was first developed by Apple. Since they already had a framework—Quartz 2D—for drawing in two-dimensional space, they went ahead and based many of the concepts of HTML5’s canvas on that framework. It was then adopted by Mozilla and Opera, and then standardized by the WHATWG (and subsequently picked up by the W3C, along with the rest of HTML5).

There’s some good news here. If you aspire to do development for the iPhone or iPad (referred to jointly as iOS), or for the Mac, what you learn in canvas should help you understand some of the basics concepts of Quartz 2D. If you already develop for the Mac or iOS and have worked with Quartz 2D, many canvas concepts will look very familiar to you.

Creating a canvas Element

The first step to using canvas is to add a canvas element to the page:

canvas/demo1.html (excerpt)
<canvas>
Sorry! Your browser doesn’t support Canvas.
</canvas>

The text in between the canvas tags will only be shown if the canvas element is not supported by the visitor’s browser.

Since drawing on the canvas is done using JavaScript, we’ll need a way to grab the element from the DOM. We’ll do so by giving our canvas an id:

canvas/demo1.html (excerpt)
<canvas id="myCanvas">
Sorry! Your browser doesn’t support Canvas.
</canvas>

All drawing on the canvas happens via JavaScript, so let’s make sure we’re calling a JavaScript function when our page is ready. We’ll add our jQuery document ready check to a script element at the bottom of the page:

canvas/demo1.html (excerpt)
<script>
$('document').ready(function(){
  draw();
});
</script>

The canvas element takes both a width and height attribute, which should also be set:

canvas/demo1.html (excerpt)
<canvas id="myCanvas" width="200" height="200">
Sorry! Your browser doesn’t support Canvas.
</canvas>

Finally, let’s add a border to our canvas to visually distinguish it on the page, using some CSS. Canvas has no default styling, so it’s difficult to see where it is on the page unless you give it some kind of border:

css/canvas.css (excerpt)
#myCanvas {
  border: dotted 2px black;
}

Now that we’ve styled it, we can actually view the canvas container on our page —Figure 11.1 shows what it looks like.

canvas API creating elements canvas element An empty canvas with a dotted border

Figure 11.1.  An empty canvas with a dotted border

Drawing on the Canvas

All drawing on the canvas happens via the Canvas JavaScript API. We’ve called a function called draw() when our page is ready, so let’s go ahead and create that function. We’ll add the function to our script element. The first step is to grab hold of the canvas element on our page:

canvas/demo1.html (excerpt)
<script>
…
function draw() {
  var canvas = document.getElementById("myCanvas");
}
</script>

Getting the Context

Once we’ve stored our canvas element in a variable, we need to set up the canvas’s context. The context is the place where your drawing is rendered. Currently, there’s only wide support for drawing to a two-dimensional context. The W3C Canvas spec defines the context in the CanvasRenderingContext2D object. Most methods we’ll be using to draw onto the canvas are methods of this object.

We obtain our drawing context by calling the getContext method and passing it the string "2d", since we’ll be drawing in two dimensions:

canvas/demo1.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");
}

The object that’s returned by getContext is a CanvasRenderingContext2D object. In this chapter, we’ll refer to it as simply “the context object” for brevity.

Note: WebGL

WebGL is a new API for 3D graphics being managed by the Khronos Group, with a WebGL working group that includes Apple, Mozilla, Google, and Opera.

By combining WebGL with HTML5 Canvas, you can draw in three dimensions. WebGL is currently supported in Firefox 4+, Chrome 8+, and Safari 6. To learn more, see http://www.khronos.org/webgl/.

Filling Our Brush with Color

On a regular painting canvas, before you can begin, you must first saturate your brush with paint. In the HTML5 Canvas, you must do the same, and we do so with the strokeStyle or fillStyle properties. Both strokeStyle and fillStyle are set on a context object. And both take one of three values: a string representing a color, a CanvasGradient, or a CanvasPattern.

Let’s start by using a color string to style the stroke. You can think of the stroke as the border of the shape you’re going to draw. To draw a rectangle with a red border, we first define the stroke color:

canvas/demo1.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");     
  var context = canvas.getContext("2d");
  context.strokeStyle = "red";
}

To draw a rectangle with a red border and blue fill, we must also define the fill color:

canvas/demo1.html (excerpt)
function draw() {
  …
  context.fillStyle = "blue"; 
} 

We can use any CSS color value to set the stroke or fill color, as long as we specify it as a string: a hexadecimal value like #00FFFF, a color name like red or blue, or an RGB value like rgb(0, 0, 255). We can even use RGBA to set a semitransparent stroke or fill color. Let’s change our blue fill to blue with a 50% opacity:

canvas/demo1.html (excerpt)
function draw() {
  …
  context.fillStyle = "rgba(0, 0, 255, 0.5)"; 
} 

Drawing a Rectangle to the Canvas

Once we’ve defined the color of the stroke and the fill, we’re ready to actually start drawing! Let’s begin by drawing a rectangle. We can do this by calling the fillRect and strokeRect methods. Both of these methods take the X and Y coordinates where you want to begin drawing the fill or the stroke, and the width and the height of the rectangle. We’ll add the stroke and fill 10 pixels from the top and 10 pixels from the left of the canvas’s top left corner:

canvas/demo1.html (excerpt)
function draw() {
  …
  context.fillRect(10,10,100,100);
  context.strokeRect(10,10,100,100);
}

This will create a semitransparent blue rectangle with a red border, like the one in Figure 11.2.

A simple rectangle—not bad for our first canvas drawing!

Figure 11.2. A simple rectangle—not bad for our first canvas drawing!

The Canvas Coordinate System

As you may have gathered, the coordinate system in the canvas element is different from the Cartesian coordinate system you learned in math class. In the canvas coordinate system, the top-left corner is (0,0). If the canvas is 200 pixels by 200 pixels, then the bottom-right corner is (200, 200), as Figure 11.3 illustrates.

The canvas coordinate system goes top-to-bottom and left-to-right

Figure 11.3. The canvas coordinate system goes top-to-bottom and left-to-right

Variations on fillStyle

Instead of a color as our fillStyle, we could have also used a CanvasGradient or a CanvasPattern.

We create a CanvasPattern by calling the createPattern method. createPattern takes two parameters: the image to create the pattern with, and how that image should be repeated. The repeat value is a string, and the valid values are the same as those in CSS: repeat, repeat-x, repeat-y, and no-repeat.

Instead of using a semitransparent blue fillStyle, let’s create a pattern using our bicycle image. First, we must create an Image object, and set its src property to our image:

canvas/demo2.html (excerpt)
function draw() {
  …
  var img = new Image();
  img.src = "../images/bg-bike.png";
}

Setting the src attribute will tell the browser to start downloading the image—but if we try to use it right away to create our gradient, we’ll run into some problems, because the image will still be loading. So we’ll use the image’s onload property to create our pattern once the image has been fully loaded by the browser:

canvas/demo2.html (excerpt)
function draw() {
  …
  var img = new Image();
  img.src = "../images/bg-bike.png";
  
  img.onload = function() {
  
  };
}

In our onload event handler, we call createPattern, passing it the Image object and the string repeat, so that our image repeats along both the X and Y axes. We store the results of createPattern in the variable pattern, and set the fillStyle to that variable:

canvas/demo2.html (excerpt)
function draw() {
  …
  var img = new Image();
  img.src = "../images/bg-bike.png";
  img.onload = function() {
    pattern = context.createPattern(img, "repeat");
    context.fillStyle = pattern;
    context.fillRect(10,10,100,100);
    context.strokeRect(10,10,100,100);  
  };
}

Note: Anonymous Functions

You may be asking yourself, “what is that function statement that comes right before the call to img.onload?” It’s an anonymous function. Anonymous functions are much like regular functions except, as you might guess, they don’t have names.

When you see an anonymous function inside of an event listener, it means that the anonymous function is being bound to that event. In other words, the code inside that anonymous function will be run when the load event is fired.

Now, our rectangle’s fill is a pattern made up of our bicycle image, as Figure 11.4 shows.

A pattern fill on a canvas

Figure 11.4. A pattern fill on a canvas

We can also create a CanvasGradient to use as our fillStyle. To create a CanvasGradient, we call one of two methods: createLinearGradient(x0, y0, x1, y1) or createRadialGradient(x0, y0, r0, x1, y1, r1); then we add one or more color stops to the gradient.

createLinearGradient’s x0 and y0 represent the starting location of the gradient. x1 and y1 represent the ending location.

To create a gradient that begins at the top of the canvas and blends the color down to the bottom, we’d define our starting point at the origin (0,0), and our ending point 200 pixels down from there (0,200):

canvas/demo3.html (excerpt)
function draw() {
  …
  var gradient = context.createLinearGradient(0, 0, 0, 200);
}

Next, we specify our color stops. The color stop method is simply addColorStop(offset, color).

The offset is a value between 0 and 1. An offset of 0 is at the starting end of the gradient, and an offset of 1 is at the other end. The color is a string value that, as with the fillStyle, can be a color name, a hexadecimal color value, an rgb() value, or an rgba() value.

To make a gradient that starts as blue and begins to blend into white halfway down the gradient, we can specify a blue color stop with an offset of 0 and a purple color stop with an offset of 1:

canvas/demo3.html (excerpt)
function draw() {
  …
  var gradient = context.createLinearGradient(0, 0, 0, 200);
  gradient.addColorStop(0,"blue"); 
  gradient.addColorStop(1,"white");
  context.fillStyle = gradient;
  context.fillRect(10,10,100,100);
  context.strokeRect(10,10,100,100);
}

Figure 11.5 is the result of setting our CanvasGradient to be the fillStyle of our rectangle.

canvas API gradients gradients in canvas Creating a linear gradient with Canvas

Figure 11.5.  Creating a linear gradient with Canvas

Drawing Other Shapes by Creating Paths

We’re not limited to drawing rectangles—we can draw any shape we can imagine! Unlike rectangles and squares, however, there’s no built-in method for drawing circles, or other shapes. To draw more interesting shapes, we must first lay out the path of the shape.

Paths create a blueprint for your lines, arcs, and shapes, but paths are invisible until you give them a stroke! When we drew rectangles, we first set the strokeStyle and then called fillRect. With more complex shapes, we need to take three steps: lay out the path, stroke the path, and fill the path. As with drawing rectangles, we can just stroke the path, or fill the path—or we can do both.

Let’s start with a simple circle:

canvas/demo4.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");

  context.beginPath();
}

Now we need to create an arc. An arc is a segment of a circle; there’s no method for creating a circle, but we can simply draw a 360° arc. We create it using the arc method:

canvas/demo4.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");

  context.beginPath();
  context.arc(50, 50, 30, 0, Math.PI*2, true);
}

The arguments for the arc method are as follows: arc(x, y, radius, startAngle, endAngle, anticlockwise).

x and y represent where on the canvas you want the arc’s path to begin. Imagine this as the center of the circle that you’ll be drawing. radius is the distance from the center to the edge of the circle.

startAngle and endAngle represent the start and end angles along the circle’s circumference that you want to draw. The units for the angles are in radians, and a circle is 2π radians. We want to draw a complete circle, so we will use 2π for the endAngle. In JavaScript, we can get this value by multiplying Math.PI by 2.

anticlockwise is an optional argument. If you wanted the arc to be drawn counterclockwise instead of clockwise, you would set this value to true. Since we are drawing a full circle, it doesn’t matter which direction we draw it in, so we omit this argument.

Our next step is to close the path, since we’ve now finished drawing our circle. We do that with the closePath method:

canvas/demo4.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");

  context.beginPath();
  context.arc(100, 100, 50, 0, Math.PI*2, true);
  context.closePath();
}

Now we have a path! But unless we stroke it or fill it, we’ll be unable to see it. Thus, we must set a strokeStyle if we would like to give it a border, and we must set a fillStyle if we’d like our circle to have a fill color. By default, the width of the stroke is 1 pixel—this is stored in the lineWidth property of the context object. Let’s make our border a bit bigger by setting the lineWidth to 3:

canvas/demo4.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");

  context.beginPath();
  context.arc(50, 50, 30, 0, Math.PI*2, true);
  context.closePath();
  context.strokeStyle = "red";
  context.fillStyle = "blue";
  context.lineWidth = 3;
}

Lastly, we fill and stroke the path. Note that this time, the method names are different than those we used with our rectangle. To fill a path, you simply call fill, and to stroke it you call stroke:

canvas/demo4.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");

  context.beginPath();
  context.arc(100, 100, 50, 0, Math.PI*2, true);
  context.closePath();
  context.strokeStyle = "red";
  context.fillStyle = "blue";
  context.lineWidth = 3;
  context.fill();
  context.stroke();
}

Figure 11.6 shows the finished circle.

canvas API drawing circles Our shiny new circle

Figure 11.6.  Our shiny new circle

To learn more about drawing shapes, the Mozilla Developer Network has an excellent tutorial.

Saving Canvas Drawings

If we create an image programmatically using the Canvas API, but decide we’d like to have a local copy of our drawing, we can use the API’s toDataURL method to save our drawing as a PNG or JPEG file.

To preserve the circle we just drew, we could add a new button to our HTML, and open the canvas drawing as an image in a new window once the button is clicked. To do that, let’s define a new JavaScript function:

canvas/demo5.html (excerpt)
function saveDrawing() {
  var canvas = document.getElementById("myCanvas");
  window.open(canvas.toDataURL("image/png"));
}

Next, we’ll add a button to our HTML and call our function when it’s clicked:

canvas/demo5.html (excerpt)
<canvas id="myCanvas" width="200" height="200">
Sorry! Your browser doesn’t support Canvas.
</canvas>
<form>
  <input type="button" name="saveButton" id="saveButton" 
↵value="Save Drawing">
</form>
…
<script>

$('document').ready(function(){
  draw();
  $('#saveButton').click(saveDrawing);
});
…

When the button is clicked, a new window or tab opens up with a PNG file loaded into it, as shown in Figure 11.7.

Our image loads in a new window

Figure 11.7. Our image loads in a new window

To learn more about saving our canvas drawings as files, see the W3C Canvas spec and the “Saving a canvas image to file” section of Mozilla’s Canvas code snippets.

Drawing an Image to the Canvas

We can also draw images into the canvas element. In this example, we’ll be redrawing into the canvas an image that already exists on the page.

For the sake of illustration, we’ll be using the HTML5 logo as our image for the next few examples. Let’s start by adding it to our page in an img element:

canvas/demo6.html (excerpt)
<canvas id="myCanvas" width="200" height="200">
Your browser does not support canvas.
</canvas>
<img src="../images/html5-logo.png" id="myImageElem">

Next, after grabbing the canvas element and setting up the canvas’s context, we can grab an image from our page via document.getElementById:

canvas/demo6.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");
  var image = document.getElementById("myImageElem");
}

We’ll use the same CSS we used before to make the canvas element visible:

css/canvas.css (excerpt)
#myCanvas {
  border: dotted 2px black;
}

Let’s modify it slightly to space out our canvas and our image:

css/canvas.css (excerpt)
#myCanvas {
  border: dotted 2px black;
  margin: 0px 20px;
}

Figure 11.8 shows our empty canvas next to our image.

canvas API drawing an image An image and a canvas sitting on a page, not doing much

Figure 11.8.  An image and a canvas sitting on a page, not doing much

We can use canvas’s drawImage method to redraw the image from our page into the canvas:

canvas/demo6.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");
  var image = document.getElementById("myImageElem");
  context.drawImage(image, 0, 0);
}

Because we’ve drawn the image to the (0,0) coordinate, the image appears in the top-left of the canvas, as you can see in Figure 11.9.

canvas API drawing an image Redrawing an image inside a canvas

Figure 11.9.  Redrawing an image inside a canvas

We could instead draw the image at the center of the canvas, by changing the X and Y coordinates that we pass to drawImage. Since the image is 64 by 64 pixels, and the canvas is 200 by 200 pixels, if we draw the image to (68, 68),[17] the image will be in the center of the canvas, as in Figure 11.10.

Displaying the image in the center of the canvas

Figure 11.10. Displaying the image in the center of the canvas

Manipulating Images

Redrawing an image element from the page onto a canvas is fairly unexciting. It’s really no different from using an img element! Where it does become interesting is how we can manipulate an image after we’ve drawn it into the canvas.

Once we’ve drawn an image on the canvas, we can use the getImageData method from the Canvas API to manipulate the pixels of that image. For example, if we wanted to convert our logo from color to black and white, we can do so using methods in the Canvas API.

getImageData will return an ImageData object, which contains three properties: width, height, and data. The first two are self-explanatory, but it’s the last one, data, that interests us.

data contains information about the pixels in the ImageData object, in the form of an array. Each pixel on the canvas will have four values in the data array—these correspond to that pixel’s R, G, B, and A values.

The getImageData method allows us to examine a small section of a canvas, so let’s use this feature to become more familiar with the data array. getImageData takes four parameters, corresponding to the four corners of a rectangular piece of the canvas we’d like to inspect. If we call getImageData on a very small section of the canvas, say context.getImageData(0, 0, 1, 1), we’d be examining just one pixel (the rectangle from 0,0 to 1,1). The array that’s returned is four items long, as it contains a red, green, blue, and alpha value for this lone pixel:

function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");
  var image = document.getElementById("myImageElem");
  // draw the image at x=0 and y=0 on the canvas
  context.drawImage(image, 68, 68);
  var imageData = context.getImageData(0, 0, 1, 1);
  var pixelData = imageData.data;
  alert(pixelData.length);
}

The alert prompt confirms that the data array for a one-pixel section of the canvas will have four values, as Figure 11.11 demonstrates.

getImageData method The data array for a single pixel contains four values

Figure 11.11.  The data array for a single pixel contains four values

Converting an Image from Color to Black and White

Let’s look at how we’d go about using getImageData to convert a full color image into black and white on a canvas. Assuming we’ve already placed an image onto the canvas, as we did above, we can use a for loop to iterate through each pixel in the image, and change it to grayscale.

First, we’ll call getImageData(0,0,200,200) to retrieve the entire canvas. Then, we need to grab the red, green, and blue values of each pixel, which appear in the array in that order:

canvas/demo7.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");
  var image = document.getElementById("myImageElem");
  context.drawImage(image, 68, 68);

  var imageData = context.getImageData(0, 0, 200, 200);
  var pixelData = imageData.data;
    
  for (var i = 0; i < pixelData.length; i += 4) {
    var red = pixelData[i];
    var green = pixelData[i + 1];
    var blue = pixelData[i + 2];
  }
}

Notice that our for loop is incrementing i by 4 instead of the usual 1. This is because each pixel takes up four values in the imageData array—one number each for the R, G, B, and A values.

Next, we must determine the grayscale value for the current pixel. It turns out that there’s a mathematical formula for converting RGB to grayscale: you simply need to multiply each of the red, green, and blue values by some specific numbers, seen in the code block below:

canvas/demo7.html (excerpt)
…
for (var i = 0; i < pixelData.length; i += 4) {
  var red = pixelData[i];
  var green = pixelData[i + 1];
  var blue = pixelData[i + 2];
        
  var grayscale = red * 0.3 + green * 0.59 + blue * 0.11;
}
…

Now that we have the proper grayscale value, we’re going to store it back into the red, green, and blue values in the data array:

canvas/demo7.html (excerpt)
…
for (var i = 0; i < pixelData.length; i += 4) {
  var red = pixelData[i];
  var green = pixelData[i + 1];
  var blue = pixelData[i + 2];
        
  var grayscale = red * 0.3 + green * 0.59 + blue * 0.11;
        
  pixelData[i] = grayscale;
  pixelData[i + 1] = grayscale;
  pixelData[i + 2] = grayscale;
}
…

So now we’ve modified our pixel data by individually converting each pixel to grayscale. The final step? Putting the image data we’ve modified back into the canvas via a method called putImageData. This method does exactly what you’d expect: it takes image data and writes it onto the canvas. Here’s the method in action:

canvas/demo7.html (excerpt)
function draw() {
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");
  var image = document.getElementById("myImageElem");
  context.drawImage(image, 60, 60);

  var imageData = context.getImageData(0, 0, 200, 200);
  var pixelData = imageData.data;
    
  for (var i = 0; i < pixelData.length; i += 4) {
    var red = pixelData[i];
    var green = pixelData[i + 1];
    var blue = pixelData[i + 2];
        
    var grayscale = red * 0.3 + green * 0.59 + blue * 0.11;
        
    pixelData[i] = grayscale;
    pixelData[i + 1] = grayscale;
    pixelData[i + 2] = grayscale;
  } 
  context.putImageData(imageData, 0, 0);
}

With that, we’ve drawn a black-and-white version of the validation image into the canvas.

Security Errors with getImageData

If you tried out this code in Chrome or Firefox, you may have noticed that it failed to work—the image on the canvas is in color. That’s because in these two browsers, if you try to convert an image on your desktop in an HTML file that’s also on your desktop, an error will occur in getImageData. The error is a security error, though in our case it’s an unnecessary one.

The true security issue that Chrome and Firefox are attempting to stop is a user on one domain from manipulating images on another domain. For example, stopping me from loading an official logo from http://google.com/ and then manipulating the pixel data.

The W3C Canvas spec describes it this way:

Information leakage can occur if scripts from one origin can access information (e.g. read pixels) from images from another origin (one that isn’t the same). To mitigate this, canvas elements are defined to have a flag indicating whether they are origin-clean.

This origin-clean flag will be set to false if the image you want to manipulate is on a different domain from the JavaScript doing the manipulating. Unfortunately, in Chrome and Firefox, this origin-clean flag is also set to false while you’re testing from files on your hard drive—they’re seen as files living on different domains.

If you want to test pixel manipulation using canvas in Firefox or Chrome, you’ll need to either test it on a web server running on your computer (http://localhost/), or test it online on a real web server.

Manipulating Video with Canvas

We can take the code we’ve already written to convert a color image to black and white, and enhance it to make our color video black and white, to match the old-timey feel of The HTML5 Herald page. We’ll do this in a new, separate JavaScript file called videoToBW.js, so that we can include it on the site’s home page.

The file begins, as always, by setting up the canvas and the context:

js/videoToBW.js (excerpt)
function makeVideoOldTimey ()
{
  var video = document.getElementById("video");
  var canvas = document.getElementById("canvasOverlay");
  var context = canvas.getContext("2d");
}

Next, we’ll add a new event listener to react to the play event firing on the video element.

We want to call a draw function when the video begins playing. To do so, we’ll add an event listener to our video element that responds to the play event:

js/videoToBW.js (excerpt)
function makeVideoOldTimey ()
{
  var video = document.getElementById("video");
  var canvas = document.getElementById("canvasOverlay");
  var context = canvas.getContext("2d");

  video.addEventListener("play", function(){
    draw(video,context,canvas);
  },false);
}

The draw function will be called when the play event fires, and it will be passed the video, context, and canvas objects. We’re using an anonymous function here instead of a normal named function because we can’t actually pass parameters to named functions when declaring them as event handlers.

Since we want to pass several parameters to the draw function—video, context, and canvas—we must call it from inside an anonymous function.

Let’s look at the draw function:

js/videoToBW.js (excerpt)
function draw(video, context, canvas) 
{
  if (video.paused || video.ended) 
  {
    return false;
  }
    
  drawOneFrame(video, context, canvas);
}

Before doing anything else, we check to see if the video is paused or has ended, in which case we’ll just cut the function short by returning false. Otherwise, we continue onto the drawOneFrame function. The drawOneFrame function is nearly identical to the code we had above for converting an image from color to black and white, except that we’re drawing the video element onto the canvas instead of a static image:

js/videoToBW.js (excerpt)
function drawOneFrame(video, context, canvas){
  // draw the video onto the canvas
  context.drawImage(video, 0, 0, canvas.width, canvas.height);

  var imageData = context.getImageData(0, 0, canvas.width, 
↵canvas.height);
  var pixelData = imageData.data;
  // Loop through the red, green and blue pixels, 
  // turning them grayscale
  for (var i = 0; i < pixelData.length; i += 4) {
    var red = pixelData[i];
    var green = pixelData[i + 1];
    var blue = pixelData[i + 2];
    //we'll ignore the alpha value, which is in position i+3
      
    var grayscale = red * 0.3 + green * 0.59 + blue * 0.11;
      
    pixelData[i] = grayscale;
    pixelData[i + 1] = grayscale;
    pixelData[i + 2] = grayscale;
  }
    
  imageData.data = pixelData;
   
  context.putImageData(imageData, 0, 0);
}

After we’ve drawn one frame, what’s the next step? We need to draw another frame! The setTimeout method allows us to keep calling the draw function over and over again, without pause: the final parameter is the value for delay—or how long, in milliseconds, the browser should wait before calling the function. Because it’s set to 0, we are essentially running draw continuously. This goes on until the video has either ended, or been paused:

js/videoToBW.js (excerpt)
function draw(video, context, canvas) {
  if (video.paused || video.ended) 
  {
    return false;
  }
    
  var status = drawOneFrame(video, context, canvas);

  // Start over!
  setTimeout(function(){ draw(video, context, canvas); }, 0);
}

The net result? Our color video of a plane taking off now plays in black and white!

Displaying Text on the Canvas

If we were to view The HTML5 Herald from a file on our computer, we’d encounter security errors in Firefox and Chrome when trying to manipulate an entire video, as we would a simple image.

We can add a bit of error-checking in order to make our video work anyway, even if we view it from our local machine in Chrome or Firefox.

The first step is to add a try/catch block to catch the error:

js/videoToBW.js (excerpt)
function drawOneFrame(video, context, canvas){
  context.drawImage(video, 0, 0, canvas.width, canvas.height);

  try {
    var imageData = context.getImageData(0, 0, canvas.width, 
↵canvas.height);
    var pixelData = imageData.data;
    for (var i = 0; i < pixelData.length; i += 4) {
      var red = pixelData[i];
      var green = pixelData[i + 1];
      var blue = pixelData[i + 2];
      var grayscale = red * 0.3 + green * 0.59 + blue * 0.11;
      pixelData[i] = grayscale;
      pixelData[i + 1] = grayscale;
      pixelData[i + 2] = grayscale;
    }
    
    imageData.data = pixelData;
    context.putImageData(imageData, 0, 0);
  } 
  catch (err) {
    // error handling code will go here
  }
}

When an error occurs in trying to call getImageData, it would be nice to give some sort of message to the user in order to give them a hint about what may be going wrong. We’ll do just that, using the fillText method of the Canvas API.

Before we write any text to the canvas, we should clear what’s already drawn to it. We’ve already drawn the first frame of the video into the canvas using the call to drawImage. How can we clear that out?

It turns out that if we reset the width or height on the canvas, the canvas will be cleared. So, let’s reset the width:

js/videoToBW.js (excerpt)
function drawOneFrame(video, context, canvas){
  context.drawImage(video, 0, 0, canvas.width, canvas.height);

  try {
    …
  } 
  catch (err) {
    canvas.width = canvas.width;
  }
}

Next, let’s change the background color from black to transparent, since the canvas element is positioned on top of the video:

js/videoToBW.js (excerpt)
function drawOneFrame(video, context, canvas){
  context.drawImage(video, 0, 0, canvas.width, canvas.height);

  try {
    …
  } 
  catch (err) {
    canvas.width = canvas.width;
    canvas.style.backgroundColor = "transparent";
  }
}

Before we can draw any text to the now transparent canvas, we first must set up the style of our text—similar to what we did with paths earlier. We do that with the fillStyle and textAlign methods:

js/videoToBW.js (excerpt)
videoToBW.js (excerpt)
function drawOneFrame(video, context, canvas){
  context.drawImage(video, 0, 0, canvas.width, canvas.height);

  try {
    …
  } 
  catch (err) {
    canvas.width = canvas.width;
    canvas.style.backgroundColor = "transparent";
    context.fillStyle = "white";
    context.textAlign = "left";
  }
}

We must also set the font we’d like to use. The font property of the context object works the same way the CSS font property does. We’ll specify a font size of 18px and a comma-separated list of font families:

js/videoToBW.js (excerpt)
function drawOneFrame(video, context, canvas){
  context.drawImage(video, 0, 0, canvas.width, canvas.height);

  try {
    …
  } 
  catch (err) {
    canvas.width = canvas.width;
    canvas.style.backgroundColor = "transparent";
    context.fillStyle = "white";
    context.textAlign = "left";
    context.font = "18px LeagueGothic, Tahoma, Geneva, sans-serif";
  }
}

Notice that we’re using League Gothic; any fonts you’ve included with @font-face are also available for you to use on your canvas. Finally, we draw the text. We use a method of the context object called fillText, which takes the text to be drawn and the (x,y) coordinates where it should be placed. Since we want to write out a fairly long message, we’ll split it up into several sections, placing each one on the canvas separately:

js/videoToBW.js (excerpt)
function drawOneFrame(video, context, canvas){
  context.drawImage(video, 0, 0, canvas.width, canvas.height);

  try {
    …
  } 
  catch (err) {
    canvas.width = canvas.width;
    canvas.style.backgroundColor = "transparent";
    context.fillStyle = "white";
    context.textAlign = "left";
    context.font = "18px LeagueGothic, Tahoma, Geneva, sans-serif";
    context.fillText("There was an error rendering ", 10, 20);
    context.fillText("the video to the canvas.", 10, 40);
    context.fillText("Perhaps you are viewing this page from", 10, 
↵70);
    context.fillText("a file on your computer?", 10, 90);
    context.fillText("Try viewing this page online instead.", 10, 
↵130);

    return false;
  }
}

As a last step, we return false. This lets us check in the draw function whether an exception was thrown. If it was, we want to stop calling drawOneFrame for each video frame, so we exit the draw function:

js/videoToBW.js (excerpt)
function draw(video, context, canvas) {
    if (video.paused || video.ended) 
  {
    return false;
  }
    
  var status = drawOneFrame(video, context, canvas);
  
  if (status == false)
  {
    return false;
  }
  // Start over!
  setTimeout(function(){ draw(video, context, canvas); }, 0);
}

Accessibility Concerns

A major downside of canvas in its current form is its lack of accessibility. The canvas doesn’t create a DOM node, is not a text-based format, and is thus essentially invisible to tools like screen readers. For example, even though we wrote text to the canvas in our last example, that text is essentially no more than a bunch of pixels, and is therefore inaccessible.

The HTML5 community is aware of these failings, and while no solution has been finalized, debates on how canvas could be changed to make it accessible are underway. You can read a compilation of the arguments and currently proposed solutions on the W3C’s wiki page.

Further Reading

To read more about canvas and the Canvas API, here are a couple of good resources:

SVG

We already learned a bit about SVG back in Chapter 7, when we used SVG files as a fallback for gradients in IE9 and older versions of Opera. In this chapter, we’ll dive into SVG in more detail and learn how to use it in other ways.

First, a quick refresher: SVG stands for Scalable Vector Graphics. SVG is a specific file format that allows you to describe vector graphics using XML. A major selling point of vector graphics in general is that, unlike bitmap images (such as GIF, JPEG, PNG, and TIFF), vector images preserve their shape even as you blow them up or shrink them down. We can use SVG to do many of the same tasks we can do with canvas, including drawing paths, shapes, text, gradients, and patterns. There are also some very useful open source tools relevant to SVG, some of which we will leverage in order to add a spinning progress indicator to The HTML5 Herald’s geolocation widget.

Basic SVG, including using SVG in an HTML img element, is supported in:

  • Safari 3.2+

  • Chrome 6.0+

  • Firefox 4.0+

  • Internet Explorer 9.0+

  • Opera 10.5+

There is currently no support for SVG in Android’s browser.

Note: XML

XML stands for eXtensible Markup Language. Like HTML, it’s a markup language, which means it’s a system meant to annotate text. Just as we can use HTML tags to wrap our content and give it meaning, so can XML tags be used to describe the content of files.

Unlike canvas, images created with SVG are available via the DOM. This allows technologies like screen readers to see what’s present in an SVG object through its DOM node—and it also allows you to inspect SVG using your browser’s developer tools. Since SVG is an XML file format, it’s also more accessible to search engines than canvas.

Drawing in SVG

Drawing a circle in SVG is arguably easier than drawing a circle with canvas. Here’s how we do it:

images/circle.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
  <circle cx="50" cy="50" r="25" fill="red"/>
</svg>

The viewBox attribute defines the starting location, width, and height of the SVG image.

The circle element defines a circle, with cx and cy the X and Y coordinates of the center of the circle. The radius is represented by r, while fill is for the fill style.

To view an SVG file, you simply open it via the File menu in any browser that supports SVG. Figure 11.12 shows what our circle looks like.

SVG (Scalable Vector Graphics) drawing shapes A circle drawn using SVG

Figure 11.12.  A circle drawn using SVG

We can also draw rectangles in SVG, and add a stroke to them, as we did with canvas.

This time, let’s take advantage of SVG being an XML—and thus text-based—file format, and utilize the desc tag, which allows us to provide a description for the image we’re going to draw:

images/rectangle.svg (excerpt)
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 400 400">
  <desc>Drawing a rectangle</desc>
</svg>

Next, we populate the rect tag with a number of attributes that describe the rectangle. This includes the X and Y coordinate where the rectangle should be drawn, the width and height of the rectangle, the fill, the stroke, and the width of the stroke:

images/rectangle.svg
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 400 400">
  <desc>Drawing a rectangle</desc>
  <rect x="10" y="10" width="100" height="100"
        fill="blue" stroke="red" stroke-width="3"  />
</svg>

Figure 11.13 shows our what our rectangle looks like.

SVG (Scalable Vector Graphics) drawing shapes A rectangle drawn with SVG

Figure 11.13.  A rectangle drawn with SVG

Unfortunately, it’s not always this easy. If you want to create complex shapes, the code begins to look a little scary. Figure 11.14 shows a fairly simple-looking star image from http://openclipart.org/:

SVG (Scalable Vector Graphics) drawing shapes A line drawing of a star

Figure 11.14.  A line drawing of a star

And here are just the first few lines of SVG for this image:

<svg xmlns="http://www.w3.org/2000/svg"    
  width="122.88545" height="114.88568">
<g
  inkscape:label="Calque 1"
  inkscape:groupmode="layer"
  id="layer1"
  transform="translate(-242.42282,-449.03699)">
  <g
    transform="matrix(0.72428496,0,0,0.72428496,119.87078,183.8127)"
    id="g7153">
    <path
      style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width
↵:2.761343;stroke-linecap:round;stroke-linejoin:round;stroke-miterl
↵imit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
      d="m 249.28667,389.00422 -9.7738,30.15957 -31.91999,7.5995 c -
↵2.74681,1.46591 -5.51239,2.92436 -1.69852,6.99979 l 30.15935,12.57
↵796 -11.80876,32.07362 c -1.56949,4.62283 -0.21957,6.36158 4.24212
↵,3.35419 l 26.59198,-24.55691 30.9576,17.75909 c 3.83318,2.65893 6
↵.12086,0.80055 5.36349,-3.57143 l -12.10702,-34.11764 22.72561,-13
↵.7066 c 2.32805,-1.03398 5.8555,-6.16054 -0.46651,-6.46042 l -33.5
↵0135,-0.66887 -11.69597,-27.26175 c -2.04282,-3.50583 -4.06602,-7.
↵22748 -7.06823,-0.1801 z"
      id="path7155"
      inkscape:connector-curvature="0"
      sodipodi:nodetypes="cccccccccccccccc" />
…

Eek!

Using Inkscape to Create SVG Images

To save ourselves some work (and sanity), instead of creating SVG images by hand, we can use an image editor to help. One open source tool that you can use to make SVG images is Inkscape. Inkscape is an open source vector graphics editor that outputs SVG. Inkscape is available for download at http://inkscape.org/.

For our progress-indicating spinner, instead of starting from scratch, we’ve searched the public domain to find a good image from which to begin. A good resource to know about for public domain images is http://openclipart.org/, where you can find images that are copyright-free and free to use. The images have been donated by the creators for use in the public domain, even for commercial purposes, without the need to ask for permission.

We will be using an image of three arrows as the basis of our progress spinner, shown in Figure 11.15. The original can be found at openclipart.org.

progress meter The image we’ll be using for our progress indicator

Figure 11.15.  The image we’ll be using for our progress indicator

SVG Filters

To make our progress spinner match our page a bit better, we can use a filter in Inkscape to make it black and white. Start by opening the file in Inkscape, then choose Filters > Color > Moonarize.

You may notice if you test out The HTML5 Herald in Safari that our black-and-white spinner is still ... in color. That’s because SVG filters are a specific feature of SVG yet to be implemented in Safari 5, though it will be part of Safari 6. SVG filters are supported in Firefox, Chrome, and Opera. They’re currently unsupported in all versions of Safari, Internet Explorer, the Android browser, and iOS.

A safer approach would be to avoid using filters, and instead simply modify the color of the original image.

We can do this in Inkscape by selecting the three arrows in the spinner.svg image, and then selecting Object > Fill and Stroke. The Fill and Stroke menu will appear on the right-hand side of the screen, as seen in Figure 11.16.

Modifying color using Fill and Stroke

Figure 11.16. Modifying color using Fill and Stroke

From this menu, we can choose to edit the existing linear gradient by clicking the Edit button. We can then change the Red, Green, and Blue values all to 0 to make our image black and white.

We’ve saved the resulting SVG as spinnerBW.svg.

Using the Raphaël Library

Raphaël is an open source JavaScript library that wraps around SVG. It makes drawing and animating with SVG much easier than with SVG alone.

Drawing an Image to Raphaël’s Container

Much as with canvas, you can also draw images into a container you create using Raphaël.

Let’s add a div to our main index file, which we’ll use as the container for the SVG elements we’ll create using Raphaël. We’ve named this div spinner:

css/styles.css (excerpt)
<article id="ad4">
  <div id="mapDiv">        
    <h1 id="geoHeading">Where in the world are you?</h1>
    <form id="geoForm">
      <input type="button" id="geobutton" value="Tell us!">
    </form>    
    <div id="spinner"></div>
  </div>
</article>

We have styled this div to be placed in the center of the parent mapDiv using the following CSS:

css/styles.css (excerpt)
#spinner {
  position:absolute;
  top:8px;
  left:55px;
}

Now, in our geolocation JavaScript, let’s put the spinner in place while we’re fetching the map. The first step is to turn our div into a Raphaël container. This is as simple as calling the Raphael method, and passing in the element we’d like to use, along with a width and height:

js/geolocation.js (excerpt)
function determineLocation(){
  if (navigator.onLine) {
    if (Modernizr.geolocation) {
      navigator.geolocation.getCurrentPosition(displayOnMap);
  
      var container = Raphael(document.getElementById("spinner"), 
↵125, 125);
                  

Next, we draw the spinner SVG image into the newly created container with the Raphaël method image, which is called on a Raphaël container object. This method takes the path to the image, the starting coordinates where the image should be drawn, and the width and height of the image:

js/geolocation.js (excerpt)
var container = Raphael(document.getElementById("spinner"),125,125);
var spinner = container.image("images/spinnerBW.svg",0,0,125,125);
                  

With this, our spinner image will appear when we click on the button in the geolocation widget.

Rotating a Spinner with Raphaël

Now that we have our container and the spinner SVG image drawn into it, we want to animate the image to make it spin. Raphaël has animation features built in with the animate method. Before we can use this method, though, we first need to tell it which attribute to animate. Since we want to rotate our image, we’ll create an object that specifies how many degrees of rotation we want.

We create a new object attrsToAnimate, specifying that we want to animate the rotation, and we want to rotate by 720 degrees (two full turns):

js/geolocation.js (excerpt)
var container = Raphael(document.getElementById("spinner"),125,125);
var spinner = container.image("images/spinnerBW.png",0,0,125,125);
var attrsToAnimate = { rotation: "720" };
                  

The final step is to call the animate method, and specify how long the animation should last. In our case, we will let it run for a maximum of sixty seconds. Since animate takes its values in milliseconds, we’ll pass it 60000:

js/geolocation.js (excerpt)
var container = Raphael(document.getElementById("spinner"),125,125);
var spinner = container.image("images/spinnerBW.png",0,0,125,125);
var attrsToAnimate = { rotation: "720" }; 
spinner.animate(attrsToAnimate, 60000);
                  

That’s great! We now have a spinning progress indicator to keep our visitors in the know while our map is loading. There’s still one problem though: it remains after the map has loaded. We can fix this by adding one line to the beginning of the existing displayOnMap function:

js/geolocation.js (excerpt)
function displayOnMap(position){
  document.getElementById("spinner").style.visibility = "hidden";
                  

This line sets the visibility property of the spinner element to hidden, effectively hiding the spinner div and the SVG image we’ve loaded into it.

Canvas versus SVG

Now that we’ve learned about canvas and SVG, you may be asking yourself, which is the right one to use? The answer is: it depends on what you’re doing.

Both canvas and SVG allow you to draw custom shapes, paths, and fonts. But what’s unique about each?

Canvas allows for pixel manipulation, as we saw when we turned our video from color to black and white. One downside of canvas is that it operates in what’s known as immediate mode. This means that if you ever want to add more to the canvas, you can’t simply add to what’s already there. Everything must be redrawn from scratch each time you want to change what’s on the canvas. There’s also no access to what’s drawn on the canvas via the DOM. However, canvas does allow you to save the images you create to a PNG or JPEG file.

By contrast, what you draw to SVG is accessible via the DOM, because its mode is retained mode—the structure of the image is preserved in the XML document that describes it. SVG also has, at this time, a more complete set of tools to help you work with it, like the Raphaël library and Inkscape. However, since SVG is a file format—rather than a set of methods that allows you to dynamically draw on a surface—you can’t manipulate SVG images the way you can manipulate pixels on canvas. It would have been impossible, for example, to use SVG to convert our color video to black and white as we did with canvas.

In summary, if you need to paint pixels to the screen, and have no concerns about the ability to retrieve and modify your shapes, canvas is probably the better choice. If, on the other hand, you need to be able to access and change specific aspects of your graphics, SVG might be more appropriate.

It’s also worth noting that neither technology is appropriate for static images—at least not while browser support remains a stumbling block. In this chapter, we’ve made use of canvas and SVG for a number of such static examples, which is fine for the purpose of demonstrating what they can do. But in the real world, they’re only really appropriate for cases where user interaction defines what’s going to be drawn.

Drag and Drop

In order to add one final dynamic effect to our site, we’re going to examine the new Drag and Drop API. This API allows us to specify that certain elements are draggable, and then specify what should happen when these draggable elements are dragged over or dropped onto other elements on the page.

Drag and Drop is supported in:

  • Safari 3.2+

  • Chrome 6.0+

  • Firefox 3.5+ (there is an older API that was supported in Firefox 3.0)

  • Internet Explorer 7.0+

  • Android 2.1+

There is currently no support for Drag and Drop in Opera. The API is unsupported by design in iOS, as Apple directs you to use the DOM Touch API instead.

There are two major kinds of functionality you can implement with Drag and Drop: dragging files from your computer into a web page—in combination with the File API—or dragging elements into other elements on the same page. In this chapter, we’ll focus on the latter.

Note: Drag and Drop and the File API

If you’d like to learn more about how to combine Drag and Drop with the File API, in order to let users drag files from their desktop onto your websites, an excellent guide can be found at the Mozilla Developer Network.

The File API is currently only supported in Firefox 3.6+ and Chrome.

There are several steps to adding Drag and Drop to your page:

  1. Set the draggable attribute on any HTML elements you’d like to be draggable.

  2. Add an event listener for the dragstart event on any draggable HTML elements.

  3. Add an event listener for the dragover and drop events on any elements you want to have accept dropped items.

Feeding the WAI-ARIA Cat

In order to add a bit of fun and frivolity to our page, let’s add a few images of mice, so that we can then drag them onto our cat image and watch the cat react and devour them. Before you start worrying (or call the SPCA), rest assured that we mean, of course, computer mice. We’ll use another image from OpenClipArt for our mice.

The first step is to add these new images to our index.html file. We’ll give each mouse image an id as well:

js/dragDrop.js (excerpt)
<article id="ac3">
  <hgroup>
    <h1>Wai-Aria? HAHA!</h1>
    <h2>Form Accessibility</h2>
  </hgroup>
            
  <img src="images/cat.png" alt="WAI-ARIA Cat">
            
  <div class="content">
    <p id=”mouseContainer”>
      <img src="images/computer-mouse-pic.svg" width="30" 
↵alt="mouse treat" id="mouse1">
      <img src="images/computer-mouse-pic.svg" width="30" 
↵alt="mouse treat" id="mouse2">
      <img src="images/computer-mouse-pic.svg" width="30" 
↵alt="mouse treat" id="mouse3">
    </p>
…

Figure 11.17 shows our images in their initial state.

Three little mice, ready to be fed to the WAI-ARIA cat

Figure 11.17. Three little mice, ready to be fed to the WAI-ARIA cat

Making Elements Draggable

The next step is to make our images draggable. In order to do that, we add the draggable attribute to them, and set the value to true:

js/dragDrop.js (excerpt)
<img src="images/computer-mouse-pic.svg" width="30" 
↵alt="mouse treat" id="mouse1" draggable="true">
<img src="images/computer-mouse-pic.svg" width="30" 
↵alt="mouse treat" id="mouse2" draggable="true">
<img src="images/computer-mouse-pic.svg" width="30" 
↵alt="mouse treat" id="mouse3" draggable="true">

Important: draggable Must Be Set

Note that draggable is not a Boolean attribute; you have to explicitly set it to true.

Now that we have set draggable to true, we have to set an event listener for the dragstart event on each image. We’ll use jQuery’s bind method to attach the event listener:

js/dragDrop.js (excerpt)
$('document').ready(function() {
  $('#mouseContainer img').bind('dragstart', function(event) {
    // handle the dragstart event
  });
});

The DataTransfer Object

DataTransfer objects are one of the new objects outlined in the Drag and Drop API. These objects allow us to set and get data about the objects that are being dragged. Specifically, DataTransfer lets us define two pieces of information:

  1. the type of data we’re saving about the draggable element

  2. the value of the data itself

In the case of our draggable mouse images, we want to be able to store the id of these images, so we know which image is being dragged around.

To do this, we first need to tell DataTransfer that we want to save some plain text by passing in the string text/plain. Then we give it the id of our mouse image:

js/dragDrop.js (excerpt)
$('#mouseContainer img').bind('dragstart', function(event) {
  event.originalEvent.dataTransfer.setData("text/plain", 
↵event.target.getAttribute('id'));
});

When an element is dragged, we save the id of the element in the DataTransfer object, to be used again once the element is dropped. The target property of a dragstart event will be the element that’s being dragged.

Important: dataTransfer and jQuery

The jQuery library’s Event object only gives you access to properties it knows about. This causes problems when you’re using new native events like DataTransfer; trying to access the dataTransfer property of a jQuery event will result in an error. However, you can always retrieve the original DOM event by calling the originalEvent method on your jQuery event, as we did above. This will give you access to any properties your browser supports—in this case that includes the new DataTransfer object.

Of course, this isn’t an issue if you’re rolling your own JavaScript from scratch!

Accepting Dropped Elements

Now our mouse images are set up to be dragged. Yet, when we try to drag them around, we’re unable to drop them anywhere, which is no fun.

The reason is that by default, elements on the page aren’t set up to receive dragged items. In order to override the default behavior on a specific element, we must stop it from happening. We can do that by creating two more event listeners.

The two events we need to monitor for are dragover and drop. As you’d expect, dragover fires when you drag something over an element, and drop fires when you drop something on it.

We’ll need to prevent the default behavior for both these events—since the default prohibits you from dropping an element.

Let’s start by adding an id to our cat image so that we can bind event handlers to it:

js/dragDrop.js (excerpt)
<article id="ac3">
  <hgroup>
    <h1>Wai-Aria? HAHA!</h1>
    <h2 id="catHeading">Form Accessibility</h2>
  </hgroup>
            
  <img src="images/cat.png" id="cat" alt="WAI-ARIA Cat">

You may have noticed that we also gave an id to the h2 element. This is so we can change this text once we’ve dropped a mouse onto the cat.

Now, let’s handle the dragover event:

js/dragDrop.js (excerpt)
$('#cat').bind('dragover', function(event) {
  event.preventDefault();
});

That was easy! In this case, we merely ensured that the mouse picture can actually be dragged over the cat picture. We simply need to prevent the default behavior —and jQuery’s preventDefault method serves this purpose exactly.

The code for the drop handler is a bit more complex, so let us review it piece by piece. Our first task is to figure out what the cat should say when a mouse is dropped on it. In order to demonstrate that we can retrieve the id of the dropped mouse from the DataTransfer object, we’ll use a different phrase for each mouse, regardless of the order they’re dropped in. We’ve given three cat-appropriate options: “MEOW!”, “Purr ...”, and “NOMNOMNOM.”

We’ll store these options inside an object called mouseHash. The first step is to declare our object:

js/dragDrop.js (excerpt)
$('#cat').bind('drop', function(event) {
  var mouseHash = {};

Next, we’re going to take advantage of JavaScript’s objects allowing us to store key/value pairs inside them, as well as storing each response in the mouseHash object, associating each response with the id of one of the mouse images:

js/dragDrop.js (excerpt)
$('#cat').bind('drop', function(event) {
  var mouseHash = {};
  mouseHash['mouse1'] = "NOMNOMNOM";
  mouseHash['mouse2'] = "MEOW!";
  mouseHash['mouse3'] = "Purr...";
               

Our next step is to grab the h2 element that we’ll change to reflect the cat’s response:

js/dragDrop.js (excerpt)
var catHeading = document.getElementById('catHeading'),

Remember when we saved the id of the dragged element to the DataTransfer object using setData? Well, now we want to retrieve that id. If you guessed that we will need a method called getData for this, you guessed right:

js/dragDrop.js (excerpt)
var item = event.originalEvent.dataTransfer.getData("text/plain");

Note that we’ve stored the mouse’s id in a variable called item. Now that we know which mouse was dropped, and we have our heading, we just need to change the text to the appropriate response:

js/dragDrop.js (excerpt)
catHeading.innerHTML = mouseHash[item];

We use the information stored in the item variable (the dragged mouse’s id) to retrieve the correct message for the h2 element. For example, if the dragged mouse is mouse1, calling mouseHash[item] will retrieve “NOMNOMNOM” and set that as the h2 element’s text.

Given that the mouse has now been “eaten,” it makes sense to remove it from the page:

js/dragDrop.js (excerpt)
var mousey = document.getElementById(item);
mousey.parentNode.removeChild(mousey);

Last but not least, we must also prevent the default behavior of not allowing elements to be dropped on our cat image, as before:

js/dragDrop.js (excerpt)
event.preventDefault();

Figure 11.18 shows our happy cat, with one mouse to go.

This cat’s already eaten two mice

Figure 11.18. This cat’s already eaten two mice

Further Reading

We’ve only touched on the basics of the Drag and Drop API, to give you a taste of what’s available. We’ve shown you how you can use DataTransfer to pass data from your dragged items to their drop targets. What you do with this power is up to you!

To learn more about the Drag and Drop API, here are a couple of good resources:

That’s All, Folks!

With these final bits of interactivity, our work on The HTML5 Herald has come to an end, and your journey into the world of HTML5 and CSS3 is well on its way! We’ve tried to provide a solid foundation of knowledge about as many of the cool new features available in today’s browsers as possible—but how you build on that is up to you.

We hope we’ve given you a clear picture of how most of these features can be used today on real projects. Many are already well-supported, and browser development is once again progressing at a rapid clip. And when it comes to those elements for which support is still lacking, you have the aid of an online army of ingenious developers. These community-minded individuals are constantly working at coming up with fallbacks and polyfills to help us push forward and build the next generation of websites and applications.

Get to it!



[17] Half of the canvas’s dimensions minus half of the image’s dimensions: (200/2) - (64/2) = 68.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset