Monday, August 30th, 2010
View Source Tutorial: Content Site Using HTML5 Canvas + CSS3
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:
-
-
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:
To turn that into this:

The <h3> is first transformed into having an underline with the padding and margin being on the bottom:
-
-
h3 {
-
border-bottom: 1px solid #ccc;
-
display: block;
-
font-size: 25px;
-
font-weight: normal;
-
padding: 0 0 10px;
-
margin: 0 0 8px;
-
}
-
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:
-
-
function createContentBall(className,size,color,html) {
-
var element = document.createElement( 'div' );
-
element.className = className;
-
element.width = element.height = size;
-
element.style.position = 'absolute';
-
element.style.left = -size + 'px';
-
element.style.top = -size + 'px';
-
element.style.cursor = "default";
-
canvas.appendChild(element);
-
elements.push( element );
-
var circle = document.createElement( 'canvas' );
-
circle.width = size;
-
circle.height = size;
-
if (className !=='image' && className !=='image first') {
-
var graphics = circle.getContext( '2d' );
-
graphics.fillStyle = color;
-
graphics.beginPath();
-
graphics.arc( size * .5, size * .5, size * .5, 0, PI2, true );
-
graphics.closePath();
-
graphics.fill();
-
}
-
element.appendChild( circle );
-
content = document.createElement( 'div' );
-
content.className = "content";
-
content.onSelectStart = null;
-
content.innerHTML = html;
-
element.appendChild(content);
-
if (className !=='image' && className !=='image first' ) {
-
content.style.width = (size - contentPadding*2) + 'px';
-
content.style.left = (((size - content.clientWidth) / 2)) +'px';
-
content.style.top = ((size - content.clientHeight) / 2) +'px';
-
}
-
var b2body = new b2BodyDef();
-
var circle = new b2CircleDef();
-
circle.radius = size / 2;
-
circle.density = ballDensity;
-
circle.friction = ballFriction;
-
circle.restitution = ballRestitution;
-
b2body.AddShape(circle);
-
b2body.userData = {element: element};
-
b2body.position.Set( Math.random() * stage[2], Math.random() * (stage[3]-size) + size/2);
-
b2body.linearVelocity.Set( Math.random() * 200, Math.random() * 200 );
-
bodies.push( world.CreateBody(b2body) );
-
}
-
First, we create an absolutely positioned DIV that will house our Canvas tag:
-
-
var element = document.createElement( 'div' );
-
element.className = className;
-
element.width = element.height = size;
-
element.style.position = 'absolute';
-
element.style.left = -size + 'px';
-
element.style.top = -size + 'px';
-
element.style.cursor = "default";
-
canvas.appendChild(element);
-
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):
-
-
var circle = document.createElement( 'canvas' );
-
circle.width = size;
-
circle.height = size;
-
if (className !=='image' && className !=='image first') {
-
var graphics = circle.getContext( '2d' );
-
graphics.fillStyle = color;
-
graphics.beginPath();
-
graphics.arc( size * .5, size * .5, size * .5, 0, PI2, true );
-
graphics.closePath();
-
graphics.fill();
-
}
-
element.appendChild( circle );
-
Then we create another DIV to house the HTML content itself:
-
-
content = document.createElement( 'div' );
-
content.className = "content";
-
content.onSelectStart = null;
-
content.innerHTML = html;
-
element.appendChild(content);
-
if (className !=='image' && className !=='image first' ) {
-
content.style.width = (size - contentPadding*2) + 'px';
-
content.style.left = (((size - content.clientWidth) / 2)) +'px';
-
content.style.top = ((size - content.clientHeight) / 2) +'px';
-
}
-
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:
-
-
var b2body = new b2BodyDef();
-
var circle = new b2CircleDef();
-
circle.radius = size / 2;
-
circle.density = ballDensity;
-
circle.friction = ballFriction;
-
circle.restitution = ballRestitution;
-
b2body.AddShape(circle);
-
b2body.userData = {element: element};
-
b2body.position.Set( Math.random() * stage[2], Math.random() * (stage[3]-size) + size/2);
-
b2body.linearVelocity.Set( Math.random() * 200, Math.random() * 200 );
-
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:
-
-
function loop() {
-
delta[0] += (0 - delta[0]) * .5;
-
delta[1] += (0 - delta[1]) * .5;
-
world.m_gravity.x = 0 // -(0 + delta[0]);
-
world.m_gravity.y = -(100 + delta[1]);
-
mouseDrag();
-
world.Step(timeStep, iterations);
-
for (i = 0; i <bodies.length; i++) {
-
var body = bodies[i];
-
var element = elements[i];
-
element.style.left = (body.m_position0.x - (element.width>> 1)) + 'px';
-
element.style.top = (body.m_position0.y - (element.height>> 1)) + 'px';
-
if (ballRotation && element.tagName == 'DIV') {
-
var rotationStyle = 'rotate(' + (body.m_rotation0 * 57.2957795) + 'deg)';
-
element.style.WebkitTransform = rotationStyle;
-
element.style.MozTransform = rotationStyle;
-
element.style.OTransform = rotationStyle;
-
}
-
}
-
}
-
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.








Thank you for this. More articles like this, please.
WordPress has a ‘read more’ tag for a good reason…
“View Source Tutorials” – would be a great reoccurring feature!
This is a copy of Mr.doob’s Ball Pool http://mrdoob.com/projects/chromeexperiments/ball_pool/
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