I thought it would be fun to play around with three.js so I created a simple spinning iPhone. Rendering is handled by WebGL so it’s only viewable in browsers that have support (Chrome, Firefox 4+).
Three.js is a javascript 3d engine. According to their README:
The aim of the project is to create a lightweight 3D engine with a very low level of complexity — in other words, for dummies. The engine can render using <canvas>, <svg> and WebGL.
The code is pretty simple. First you’ll need some basic layout markup.
<!DOCTYPE html>
<html>
<head>
<title>iScreenShot</title>
</head>
<body>
<header id="page-header">
<h1>iScreenShot</h1>
</header>
<!-- Content -->
<section id="page-content">
<div id="iphone"></div>
</section>
<script type="text/javascript" src="/js/three/Three.js"></script>
<script type="text/javascript" src="/js/three/RequestAnimationFrame.js"></script>
<script type="text/javascript" src="/js/three/Detector.js"></script>
<script type="text/javascript" src="/js/main.js"></script>
</body>
<html>
You can find the three.js library javascript files in the three.js repository. The markup should require little explanation. The only thing of real note is the empty #iphone div that will be targeted by the code in main.js.
Now we’ll need the javascript to set up the scene and mouse interaction. The contents of main.js follows:
(function($) {
var camera, scene, renderer,
geometry, material, mesh;
// Set up some variables and add a mousemove handler to the page
var mouseX = 0,
mouseY = 0,
windowCentreX = window.innerWidth / 2,
windowCentreY = window.innerHeight / 2;
document.addEventListener( 'mousemove', function( event ) {
// Update mouseX and mouseY based on the new mouse X and Y positions
mouseX = ( event.clientX - windowCentreX );
mouseY = ( event.clientY - windowCentreY );
}, false );
init();
animate();
function init() {
var width = $('#iphone').width(),
height = 400;
camera = new THREE.Camera( 75, width / height, 1, 10000 );
camera.position.z = 550;
scene = new THREE.Scene();
var materials = [
new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
new THREE.MeshLambertMaterial ( { color: 0xffffff, map: THREE.ImageUtils.loadTexture( '/images/iphone-front.png' ) } ),
new THREE.MeshLambertMaterial ( { color: 0xffffff, map: THREE.ImageUtils.loadTexture( '/images/iphone-rear.png' ) } )
];
cube = new THREE.CubeGeometry( 254, 493, 30, 4, 4, 2, materials );
mesh = new THREE.Mesh( cube, new THREE.MeshFaceMaterial() );
scene.addObject( mesh );
renderer = new THREE.WebGLRenderer();
renderer.setSize( width, height );
$('#iphone').append( renderer.domElement );
}
function animate() {
mesh.rotation.y = mouseX * 0.005;
mesh.rotation.x = mouseY * 0.005;
requestAnimationFrame( animate );
render();
}
function render() {
renderer.render( scene, camera );
}
})(jQuery);
Let’s break this down to see how it works.
First, we setup up some scoped variables and logic to handle the mouse behavior.
(function($) {
var camera, scene, renderer,
geometry, material, mesh;
// Set up some variables and add a mousemove handler to the page
var mouseX = 0,
mouseY = 0,
windowCentreX = window.innerWidth / 2,
windowCentreY = window.innerHeight / 2;
document.addEventListener( 'mousemove', function( event ) {
// Update mouseX and mouseY based on the new mouse X and Y positions
mouseX = ( event.clientX - windowCentreX );
mouseY = ( event.clientY - windowCentreY );
}, false );
...
Next, we’ll execute the two main functions for rendering and animating the graphics.
init(); animate();
Now for the good stuff. First we need to set up our three.js scene and camera.
var width = $('#iphone').width(),
height = 400;
camera = new THREE.Camera( 75, width / height, 1, 10000 );
camera.position.z = 550;
scene = new THREE.Scene();
Here’s where we set up the materials for the cube. See, we’re not actually rendering a fully realized iPhone 3D model. A reasonably cool visual trick can be achieved by just rendering a stretched cube with an iPhone skin slapped on. The materials below define how each side of the cube will look.
var materials = [
new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
new THREE.MeshLambertMaterial ( { color: 0xdddde4 } ),
new THREE.MeshLambertMaterial ( {
color: 0xffffff,
map: THREE.ImageUtils.loadTexture( '/images/iphone-front.png' )
} ),
new THREE.MeshLambertMaterial ( {
color: 0xffffff,
map: THREE.ImageUtils.loadTexture( '/images/iphone-rear.png' )
} )
];
Finally, we create the actual cube, add it to the scene, initialize the renderer and append it to our empty div.
cube = new THREE.CubeGeometry( 254, 493, 30, 4, 4, 2, materials );
mesh = new THREE.Mesh( cube, new THREE.MeshFaceMaterial() );
scene.addObject( mesh );
renderer = new THREE.WebGLRenderer();
renderer.setSize( width, height );
$('#iphone').append( renderer.domElement );
For a finishing touch we allow the 3D model to be rotated by defining some animation. It’s a simple matter of translating the cube mesh in the x and y directions.
function animate() {
mesh.rotation.y = mouseX * 0.005;
mesh.rotation.x = mouseY * 0.005;
requestAnimationFrame( animate );
render();
}
function render() {
renderer.render( scene, camera );
}
I’m just barely diving into the library, but working with 3D graphics is definitely a welcome change of pace from the static content I’m working with most of the time.
