Prototyping with the canvas element

Since the beginning of 2009, HTML5 has become the hot topic in the web design community. Everyone is excited to try out the new elements and join in the fun. While I too am interested in playing with sections and asides, I'm a bit disappointed that the new canvas element hasn't been more prominent in all of the HTML5 hullabaloo.

In brief, the canvas element enables designers to create images using programming and geometry. I realize this might not be a big deal to a good amount of designers out there. If you live in a world of Photoshop, Illustrator, and all around GUI, you might think its completely backwards to create something visual through pure code. For example, take Cedric Dugas:

The canvas tag was created in a response to those who wanted pixel drawing in html. It feels like Flash 5 without any graphic UI. Seriously I gave it a try, Unless you are really good in mathematic formulas, don't expect to do great things on it. Most front-end developers are not well versed in mathematic programmings, which means we'll probably never use this thing as it's too complex to work with, or will only use a plug-in.

I can sympathize with Cedric here. When you are solely a web designer who doesn't have to dabble in development, you are conditioned to rely on GUI. Most of your day is spent pointing and clicking, selecting options, arranging layers, and navigating through multiple levels of dialog boxes. So I understand the repulsion to a tool that can only be manipulated via code and scripting.

But I doubt that most designers are only 'visual' creators, shackled to GUI. More often, a web designer's skill-set extends through a decent amount of front-end development. Chances are, if you ever wrote a couple lines of JavaScript, then you can get your head around using the canvas. If not, the canvas element might be an ideal place to get your feet wet with programming. The results are purely visual, so you have immediate feedback on whether or not your code is working.

Currently, I think most designers view the canvas element as either for weird, experimental designs, or boring pie charts and bar graphs. But I've been using the canvas in a much more practical manner — to generate simple UI graphics.

As I see it, using the canvas element in this manner has several clear advantages over traditional GUI applications like Illustrator or Photoshop:

1 Pixel-perfect rendering: When I'm creating a simple small icon, I have a real tough time making sure there is no anti-aliasing. Aligning shapes and lines to the pixel is like picking up grains of sand with chopsticks. It feels very clumsy to be placing shapes and anchor points specific to the pixel. And there's also the whole issue of dealing with cropping. But relying on the script for the canvas, there's some confidence gained in the code.

2 Image output: To extract a PNG from the canvas element, just right click on the canvas and view the image. Skip over exporting and file management.

3 One less app: Opening up Photoshop just get a new gradient is such a drag. Using the canvas element means you only have worry about another text file. And if you're using the Quickie Canvas, then you only have to worry about another browser tab.

4 Text accessibility: GUI is great because all the attributes you see are represented within some other panel or dialog box. But navigating through all these boxes can be a chore. Especially if you're making slight changes. Using scripts for a canvas element eliminates all that chrome, so you can easily identify and change specific attributes and values.

5 Data URI: This is a game changer. The canvas element outputs data URI. So when you view just the image, you'll see the raw data in the address URL field (Firefox 3 only, Thx Shwetank). The next step is to take that URI and plug it right into CSS. You just skipped right over any sort of file management. The downside is that the data URI could be extremely long, and it looks really messy sitting inside the stylesheet. But this practice makes for very quick prototyping. For the past year there has been plenty a-clamoring about skipping comps in Photoshop and jumping in with HTML and CSS. Using Data URI images fits right into this methodology.

On top of those reasons, you now have at your disposal all the advantageous of generative art. Move over Joshua Davis.

With all these advantages in mind, I developed the Quickie Canvas. It's a simple web app that uses jQuery to generate canvas elements, basically skipping over all the HTML muck to get one to render on the page. Any time I need a gradient, drop shadow, button, or shape, I just load up another tab in Firefox and get coding. As proof of my method, I'm providing the code to all the images used on this site.

Examples

I am still working out all the kinks with some of these examples. All of them were originally rendered in Firefox 3. I've discovered several discrepancies between it and Safari/Opera. If you're using IE, I'm sorry to say I couldn't get excanvas working properly.

Drop shadow

Maybe this is just my fault as a feeble designer, but trying to get Illustrator to export a drop shadow like the one shown here is especially difficult.

var grad = ctx.createLinearGradient(0,0,0,h);
grad.addColorStop(0, 'rgba(0,0,0,.15)');
grad.addColorStop(.3, 'rgba(0,0,0,.05)');
grad.addColorStop(1, 'rgba(0,0,0,.0)');
ctx.fillStyle = grad;
ctx.fillRect( 0, 0, w, h);

Navigation Gradients

Shown at 940 x 196, but was actually rendered at 1440 x 300.

var s = w/6;

var colors = new Array("#F8A","#3D8","#EB2",
                    "#8AF", "#C66", "#C4C" );

for ( i=0; i<=5; i++) {
  grad(i*s, colors[i]);
}

function grad( x, color) {
  ctx.fillStyle = color;
  ctx.fillRect( x, 0, s, h);
}

var lingrad = ctx.createLinearGradient(0,0,0,h);
  lingrad.addColorStop( 0/h, 'rgba(255,255,255,1)');
  lingrad.addColorStop( .73, 'rgba(255,255,255,0)');
  lingrad.addColorStop( .87, 'rgba(0,0,0,0)' );
  lingrad.addColorStop( 1, 'rgba(0,0,0,.1)' );

ctx.fillStyle = lingrad;
ctx.fillRect( 0, 0, w, h);

D Favicon

var grad = ctx.createLinearGradient(0,0,0,h);
grad.addColorStop(0, '#E79');
grad.addColorStop(1, '#B46');
ctx.fillStyle = grad;

ctx.beginPath();
  ctx.lineTo(1,1);
  ctx.lineTo(8,1);
  ctx.arc(8, 8, 7, -Math.PI/2, Math.PI/2, false);
  ctx.lineTo(1,15);
  ctx.lineTo(1,11);
  ctx.lineTo(3,11);
  ctx.lineTo(3,4);
  ctx.lineTo(7,4);
  ctx.lineTo(7,11);
  ctx.arc(8, 8, 3, Math.PI/2, -Math.PI/2, true);
  ctx.lineTo(1,5);
  ctx.fill();
ctx.closePath();

Diagonal Stripes

For the repeating background image seen here. This examples was rendered at 20 x 20, but the height and width values can be manipulated to produce different angles and sizes.

var color1 = 'rgba(255,0,0,1)';
var color2 = 'rgba(255,0,0,.2)';

ctx.fillStyle = color2;
ctx.fillRect( 0, 0, w, h);

ctx.fillStyle = color1;
  ctx.lineTo( w/2, 0);
  ctx.lineTo( 0, h/2);
  ctx.lineTo( 0, 0);
  ctx.moveTo( w, 0);
  ctx.lineTo( w, h/2);
  ctx.lineTo( w/2, h);
  ctx.lineTo( 0, h);
  ctx.fill();
ctx.closePath();

Grain

Be careful with this one. The formula runs for every pixel, so it can get crash your browser at resolutions greater than 300 x 300

for ( ih=0; ih < h; ih++) {
  for ( iw=0; iw<w; iw++ ) {
    var alpha = Math.random()*.07+.04;
    ctx.fillStyle = 'rgba(0,0,0,'+ alpha + ')';
    ctx.fillRect( iw, ih, 1, 1);
  }
}

RSS icon

// orange gradient
var grad = ctx.createLinearGradient(0,0,w,h);
grad.addColorStop(0, '#D80');
grad.addColorStop(.5, '#FA3');
grad.addColorStop(1, '#D80');

// orange background w/ gradient
var r = 2.5; // corner radius
ctx.fillStyle = grad;
ctx.beginPath();
  ctx.arc( r,     r, r, Math.PI, Math.PI*(3/2),false);
  ctx.arc( w-r,   r, r, Math.PI*(3/2), 0,false);
  ctx.arc( w-r, h-r, r, 0, Math.PI*(1/2),false);
  ctx.arc( r,   h-r, r, Math.PI*(1/2), Math.PI,false);
  ctx.fill();
ctx.closePath();

// circle bottom left
ctx.fillStyle = "#FFF";
ctx.beginPath();
  ctx.arc( 3.5, 10.5, 1.5, 0, Math.PI*2, true);
  ctx.fill();
ctx.closePath();

// quarter circle lines
ctx.strokeStyle = "#FFF";
ctx.lineWidth = 2;
ctx.beginPath();
  ctx.arc( 2, 12, 6, 0, -(Math.PI/2), true);
  ctx.stroke();
ctx.closePath();

ctx.beginPath();
  ctx.arc( 2, 12, 9.5, 0, -(Math.PI/2), true); 
  ctx.stroke();
ctx.closePath();

Previous/Next links

I was also able to swivel the arrows around for the up/down links for my portfolio.

function arrow(s, x, y, angle, color) {
  ctx.save();
  ctx.translate(x,y);
  ctx.translate(s*1,s*2);
  ctx.rotate(angle);
  ctx.translate(s*-1,s*-2);
  ctx.beginPath();
  ctx.fillStyle = color;
  ctx.lineTo(s*1.8,s*0);
  ctx.lineTo(s*.8,s*2);
  ctx.lineTo(s*1.8,s*4);
  ctx.lineTo(s*1,s*4);
  ctx.lineTo(s*0,s*2);
  ctx.lineTo(s*1,s*0);
  ctx.fill();
  ctx.closePath();
  ctx.restore();
} 	

var r = 5;
arrow(r, r*0, 0, Math.PI*0, "rgba(0,0,0,.2)");
arrow(r, r*1.2, 0, Math.PI*0, "rgba(0,0,0,.2)");
arrow(r, r*0, 40, Math.PI*0, "rgba(0,0,0,.6)");
arrow(r, r*1.2, 40, Math.PI*0, "rgba(0,0,0,.6)");

arrow(r, r*0, 80, Math.PI*1, "rgba(0,0,0,.2)");
arrow(r, r*1.2, 80, Math.PI*1, "rgba(0,0,0,.2)");
arrow(r, r*0, 120, Math.PI*1, "rgba(0,0,0,.6)");
arrow(r, r*1.2, 120, Math.PI*1, "rgba(0,0,0,.6)");

This article's background

In this example I did 800 iterations, but the image I used has 4800 iterations.

var color1 = 'hsla(330,30%,92%,1)';
var color2 = 'hsla(0,0%,100%,1)';
ctx.fillStyle = color1;
ctx.lineWidth = 1;
ctx.strokeStyle = color2;

for ( i=0; i<=800; i++ ) {
  //randomize values for placement and size
  var x = Math.random()*w;
  var y = Math.random()*(Math.random()*.75+.25)*h*.9;
  var s = 5+Math.random()*Math.random()*Math.random()*100;
  var theta = Math.random()*3.14;

  // render boxes on the left
  ctx.save();
    var x1 = x-w/2;
    ctx.translate( x1, y);
    ctx.rotate(theta);
    ctx.translate(-s/2, -s/2);
    ctx.fillRect(0,0,s,s);
    ctx.strokeRect(0,0,s,s);
  ctx.restore();

  // render the same boxes on the right, so it repeats
  ctx.save();
    var x2 = x+w/2;
    ctx.translate( x2, y);
    ctx.rotate(theta);
    ctx.translate(-s/2, -s/2);
    ctx.fillRect(0,0,s,s);
    ctx.strokeRect(0,0,s,s);
  ctx.restore();
}