Monday, August 30th, 2010

View Source Tutorial: Content Site Using HTML5 Canvas + CSS3

Category: Canvas, Tutorial, View Source

Via Phil Franks comes an interesting HTML5/CSS3 site for There Studio, which is a kind of coworking space in London:

The site itself has a number of circles with information bouncing on the screen that respond to mouse clicks and moves.

Let’s crack the site open using View Source and see how they are doing things. First, they have a repeated background with a little plus symbol with the following style rule on the <body> tag:

  1. background: #ddd url('../images/bg.gif') 50% 0 repeat fixed;

The textual content in each of the circles is clean semantic HTML that is search engine friendly:

  1. <div class="section who first">
  2.   <h3>Who</h3>
  3.   <p>Creatives, makers, thinkers <span class="ampersand">&amp;</span> doers</p>
  4. </div>

To turn that into this:

The <h3> is first transformed into having an underline with the padding and margin being on the bottom:

  1. h3 {
  2.     border-bottom: 1px solid #ccc;
  3.     display: block;
  4.     font-size: 25px;
  5.     font-weight: normal;
  6.     padding: 0 0 10px;
  7.     margin: 0 0 8px;
  8. }

JavaScript creates the circle. The script tags themselves are at the end of the HTML page at the bottom of the <body> tag, a good performance practice in general.

The heart of drawing each circle is in the createBall and createContentBall methods. If a ball will have HTML content, then the createContentBall method is used, otherwise the createBall method is used. Let’s look at the createContentBall method; we’ll break it down:

javascript
< view plain text >
  1. function createContentBall(className,size,color,html) {
  2.   var element = document.createElement( 'div' );
  3.   element.className = className;
  4.   element.width = element.height = size;
  5.   element.style.position = 'absolute';
  6.   element.style.left = -size + 'px';
  7.   element.style.top = -size + 'px';
  8.   element.style.cursor = "default";
  9.   canvas.appendChild(element);
  10.   elements.push( element );
  11.   var circle = document.createElement( 'canvas' );
  12.   circle.width = size;
  13.   circle.height = size;
  14.   if (className !=='image' && className !=='image first') {
  15.     var graphics = circle.getContext( '2d' );
  16.     graphics.fillStyle = color;
  17.     graphics.beginPath();
  18.     graphics.arc( size * .5, size * .5, size * .5, 0, PI2, true );
  19.     graphics.closePath();
  20.     graphics.fill();
  21.   }
  22.   element.appendChild( circle );
  23.   content = document.createElement( 'div' );
  24.   content.className = "content";
  25.   content.onSelectStart = null;
  26.   content.innerHTML = html;
  27.   element.appendChild(content);
  28.   if (className !=='image' && className !=='image first' ) {
  29.     content.style.width = (size - contentPadding*2) + 'px';
  30.     content.style.left = (((size - content.clientWidth) / 2)) +'px';
  31.     content.style.top = ((size - content.clientHeight) / 2) +'px';
  32.   }
  33.   var b2body = new b2BodyDef();
  34.   var circle = new b2CircleDef();
  35.   circle.radius = size / 2;
  36.   circle.density = ballDensity;
  37.   circle.friction = ballFriction;
  38.   circle.restitution = ballRestitution;
  39.   b2body.AddShape(circle);
  40.   b2body.userData = {element: element};
  41.   b2body.position.Set( Math.random() * stage[2], Math.random() * (stage[3]-size) + size/2);
  42.   b2body.linearVelocity.Set( Math.random() * 200, Math.random() * 200 );
  43.   bodies.push( world.CreateBody(b2body) );
  44. }

First, we create an absolutely positioned DIV that will house our Canvas tag:

javascript
< view plain text >
  1. var element = document.createElement( 'div' );
  2. element.className = className;
  3. element.width = element.height = size;
  4. element.style.position = 'absolute';
  5. element.style.left = -size + 'px';
  6. element.style.top = -size + 'px';
  7. element.style.cursor = "default";
  8. canvas.appendChild(element);
  9. elements.push( element );

Then we draw the actual circle itself using the Canvas tag and append it to our container DIV (Note that an SVG circle created programmatically could have also been used here):

javascript
< view plain text >
  1. var circle = document.createElement( 'canvas' );
  2. circle.width = size;
  3. circle.height = size;
  4. if (className !=='image' && className !=='image first') {
  5.    var graphics = circle.getContext( '2d' );
  6.    graphics.fillStyle = color;
  7.    graphics.beginPath();
  8.    graphics.arc( size * .5, size * .5, size * .5, 0, PI2, true );
  9.    graphics.closePath();
  10.    graphics.fill();
  11. }
  12. element.appendChild( circle );

Then we create another DIV to house the HTML content itself:

javascript
< view plain text >
  1. content = document.createElement( 'div' );
  2. content.className = "content";
  3. content.onSelectStart = null;
  4. content.innerHTML = html;
  5. element.appendChild(content);
  6. if (className !=='image' && className !=='image first' ) {
  7.    content.style.width = (size - contentPadding*2) + 'px';
  8.    content.style.left = (((size - content.clientWidth) / 2)) +'px';
  9.    content.style.top = ((size - content.clientHeight) / 2) +'px';
  10. }

Notice that we are setting content.onSelectStart to null above; this is so that when you run the mouse button over the text it doesn’t select (An alternative way to do this is to use the HTML pointer-events CSS property).

Next comes code to deal with the physics of the circles, which uses Box2D.js, a JavaScript physics engine ported from Flash:

javascript
< view plain text >
  1. var b2body = new b2BodyDef();
  2. var circle = new b2CircleDef();
  3. circle.radius = size / 2;
  4. circle.density = ballDensity;
  5. circle.friction = ballFriction;
  6. circle.restitution = ballRestitution;
  7. b2body.AddShape(circle);
  8. b2body.userData = {element: element};
  9. b2body.position.Set( Math.random() * stage[2], Math.random() * (stage[3]-size) + size/2);
  10. b2body.linearVelocity.Set( Math.random() * 200, Math.random() * 200 );
  11. bodies.push( world.CreateBody(b2body) );

Basically, we define a circle, give it a radius, density, friction, and restitution, and then add it to our collection of shapes with a position and linear velocity. Box2D will then handle the physics and we can just take the values back out to draw things on the screen with a setInterval, which happens in the loop method:

javascript
< view plain text >
  1. function loop() {
  2.   delta[0] += (0 - delta[0]) * .5;
  3.   delta[1] += (0 - delta[1]) * .5;
  4.   world.m_gravity.x = 0 // -(0 + delta[0]);
  5.   world.m_gravity.y = -(100 + delta[1]);
  6.   mouseDrag();
  7.   world.Step(timeStep, iterations);
  8.   for (i = 0; i < bodies.length; i++) {
  9.     var body = bodies[i];
  10.     var element = elements[i];
  11.     element.style.left = (body.m_position0.x - (element.width >> 1)) + 'px';
  12.     element.style.top = (body.m_position0.y - (element.height >> 1)) + 'px';
  13.     if (ballRotation && element.tagName == 'DIV') {
  14.       var rotationStyle = 'rotate(' + (body.m_rotation0 * 57.2957795) + 'deg)';
  15.       element.style.WebkitTransform = rotationStyle;
  16.       element.style.MozTransform = rotationStyle;
  17.       element.style.OTransform = rotationStyle;
  18.     }
  19.   }
  20. }

This method gets called with a setInterval periodically. Basically, we apply a delta and a gravity at each time step; see if the mouse is being pressed down (with hooks for touch devices like the iPhone to see if a finger is being pressed down); tell the Box2D World about our gravity and delta and to make an iteration step; and finally use the computed physics values from Box2D to apply CSS3 rotation transforms on our parent DIV and move the element’s CSS top and left values around the screen.

Related Content:

  • Video, HTML5 canvas and the codec cavalcade
    Browser support is again a new frontier. Video codec are a central issue. A variety of codecs are being offered for HTML5. Some codecs may be the...
  • Get ready for HTML5 and canvas elements
    The HTML5 canvas element is a new way to display active graphics and manipulate images with JavaScript. Find out what the new element will mean for...
  • Understanding the HTML5 standard
    Vendors are implementing HTML5 to take advantage of improved compatibility despite the fact that the standard won't be final until late...
  • The impact of HTML5 on Web applications
    HTML, the HyperText Markup Language, is an essential part of the Internet experience. HTML5 is one part of what WHATWG sees as essential to the...
  • HTML5 guide
    HTML5 guide: The advent of HTML5 signals a new wave of Web programming methods, and a new slate of standards for enterprise application...

Posted by Brad Neuberg at 5:00 am
5 Comments

++---
2.9 rating from 14 votes

5 Comments »

Comments feed TrackBack URI

Thank you for this. More articles like this, please.

Comment by cyberdyne — August 30, 2010

WordPress has a ‘read more’ tag for a good reason…

Comment by sixtyseconds — August 30, 2010

“View Source Tutorials” – would be a great reoccurring feature!

Comment by khs4473 — August 30, 2010

This is a copy of Mr.doob’s Ball Pool http://mrdoob.com/projects/chromeexperiments/ball_pool/

Comment by Liburn — August 30, 2010

can I apply a css class to a canvas Class, for example:

// Circle Class
function Circle(x, y, width, height){
this.x = x;
this.y = y;
}

//css
.circlebg {
background: #8f8;
}

and do something like this:
circle.className = “circlebg”;

thanks

Comment by juankuq — October 29, 2010

Leave a comment

You must be logged in to post a comment.