Create WebGL site headers with browser fallback to Canvas

Make 3D headers that shine on desktop browsers and hardware but don’t leave mobile devices stranded


Support for WebGL is fairly widespread but there are some exceptions to the rule. The chief two exceptions come from mobile and Internet Explorer. With mobile usage neither iOS nor Android support WebGL and this is largely down to the security threats that could potentially come from WebGL given the access to low level rendering of graphics.

Surprisingly the Blackberry Playbook does allow it (although there is a hefty warning screen), but it runs quite fast. IE can support WebGL through Chrome Frame ( even though there is no native support. These drawbacks cause problems for us as we prepare our content. ThreeJS gives the option of rendering using Canvas or WebGL, so we’ll use WebGL if it’s supported and if not a cut-down version will be provided and rendered through Canvas. The Canvas renderer is not as fast as WebGL so we’ll lose the particles when rendering to Canvas. Canvas also requires the particles to be programmed slightly differently so it makes sense on two levels to cut this out.


Starting out

Downoad the tutorial files and drag the ‘start’ folder onto your desktop and open up the file ‘index.html’ in Dreamweaver or a similar code editor of your choice. Take a look at the body content, and you’ll see that there is a div tag named ‘container’ – the WebGL will go here. Scroll down to the third script tag and add the following variables before the ‘createParticles’ function.

001    var renderer, scene, camera;
 002    var sphere, uniforms, attributes, group;
 003    var WIDTH = window.innerWidth;
 004    var HEIGHT = window.innerHeight/2;

Remaining variables

You will have noticed two previous script tags – these are special scripts that create the shaders for the particles in the project, hence why we need to add the variables in the third script tag. We’ve already added the code that makes the particles; we will add the rest of the project in this tutorial, though.

001    var mouseX = 0, mouseY = 0;
 002    var lastUpdate = new Date().getTime();
 003    var windowHalfX = window.innerWidth / 2;
 004    var windowHalfY = window.innerHeight / 2;

Call the functions

The variables are holders that will hold values within our code that we need to access at various times. Now add the code as shown here, which calls two functions. The first will initialize our 3D scene, while the second function will animate that scene on the web page.

001    init();
 002    animate();

Create the init function

The init function sets the scene up, so we’ll go ahead and create that function right under the last code. Here we define a new camera to view the scene. This takes the arguments for field of view, aspect ratio, how near and how far we can see in the scene. We position this camera to look down the Z axis and set up a new Scene.

001    function init() {
 002    camera = new THREE.PerspectiveCamera( 40,   WIDTH / HEIGHT, 1, 10000 );
 003    camera.position.z = 300;
 004    scene = new THREE.Scene();

Add a light

We will not be able to see anything in our scene unless we have a light shining so the next block of code sets up a new light with white as its colour. We position the light to be shining from the Z axis so it shines from the point of view of the camera. We then add the light to the scene.

001    var light = new THREE.DirectionalLight(     0xffffff );
 002    light.position.set( 0, 0, 1 );
 003    scene.add( light );

Group objects together

We are setting up a group that we can add many elements into. This is useful if you want to move many elements in one go, you can just move the group and everything will move with it. Once the group is set up we add the group into the scene as we did with the light in the last step.

001    group = new THREE.Object3D();
 002    scene.add( group );

Check the renderer

We are now going to check the type of renderer we have available to us. If we can render through WebGL then we are going to call the createParticles function code. Particles can be rendered through the Canvas renderer but with different code. Canvas is a much slower renderer so we are keeping the page content lower.

 001    if(Detector.webgl) {
 002        createParticles();
 003    }

Creating the logo

The next line of code calls a function that we have yet to write named ‘createLogo’. This does exactly what it says. We are going to create the logo from a Canvas-drawn shape, extruding this and texturing in our code. This is because the canvas renderer isn’t great at bringing in loaded models, so we’ll create one with our code!

001    createLogo();

Choosing how to display

Use the detector code we used earlier to check to see if the browser has WebGL available. If it has then we use this much faster renderer and set the anti-aliasing (edge smoothing) to true. If there is no WebGL renderer then the Canvas version is used. You could easily add an else statement here that uses an image if Canvas isn’t supported.

001    if(Detector.webgl) {
 002    renderer = new THREE.WebGLRenderer( {      antialias: true } );
 003    } else if(Detector.canvas) {
 004        renderer = new THREE.CanvasRenderer();
 005    }

Display the 3D content

The next block of code sets the size of the renderer to the width and half the height of the browser, as that’s the size we want to allow for the view. We store the div tag with the id ‘container’ in the variable of the same name, then finally we add in the renderer to the container so it is in the DOM.

001    renderer.setSize( WIDTH, HEIGHT );
 002    var container = document.getElementById(     ‘container’ );
 003    container.appendChild( renderer.domElement 

Listening in

The code here sets up three listeners. The first is for the mouse moving, the second is if the content is viewed on a touch screen device and a finger is moved anywhere. The last line is the window resize which will redraw the WebGL/Canvas display accordingly to display for the new size.

001    document.addEventListener(‘mousemove’,     onDocumentMouseMove, false);
 002    document.addEventListener( ‘touchmove’,     onDocumentTouchMove, false );
 003    window.addEventListener( ‘resize’,     onWindowResize, false );
 004    }

Dynamic 3D shape

When we’ve drawn the path for our 3D shape we will call the function started in this step. This will extrude the path into 3D and add a material to the mesh. We are passing into the function the shape of the path, the extrude settings, color, position, rotation, scaling and opacity of the new model.

001    function addShape( shape, extrudeSettings, color, x, y, z, rx, ry, rz, s, o ) {
 002    var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
 003    var mesh = THREE.SceneUtils.createMultiMaterialObject( geometry, [ new 
 THREE.MeshLambertMaterial( { color: color, 
 opacity: o } ) ] );

Position the mesh

In the next block of code we use the settings that have been passed into the function to set the position, rotation and scaling of the mesh. Once this is done we add the mesh to the group that we set up in step 6 of the code.

001    mesh.position.set( x, y, z );
 002    mesh.rotation.set( rx, ry, rz );
 003    mesh.scale.set( s, s, s );
 004    group.add( mesh );
 005    }

Creating the logo

We set up our function that will create the logo. At present this just has a new variable that is holding a shape, so it is expecting some points of a shape to be put in here. In the start folder, open the text file step14.txt in a text editor. Copy and paste the shape data just after the code shown here. We explain how we created this in the In Detail box.

001    function createLogo(){
 002    var logo = new THREE.Shape();

Extrusion settings

Now we set the extrude settings of the shape, which define how much to extrude the shape by and the size of the bevel with the thickness as well. We’re adding a very subtle bevelled edge to the shape. Now we have the shape and the amount to extrude it, we call the addShape function from steps 12 and 13.

001    var extrudeSettings = { amount: 8,
 002    size: 10, height: 4, curveSegments: 6,
 003    bevelThickness: 0.4, bevelSize: 0.4,        bevelEnabled: true };
 004    addShape( logo, extrudeSettings, 0xFFAA33, 
 -80, 100, 0, 3.14, 0, 0, 2, 0.9 );
 005    }

Update the view

If the window is resized the camera is updated to redraw the display to the new width and height of the document. The camera settings have changed and so the updateProjectionMatrix is called. This will make the display redraw. The renderer is also updated to reflect the new resized browser window.

001    function onWindowResize() {
 002    camera.aspect = window.innerWidth /     (window.innerHeight / 2);
 003    camera.updateProjectionMatrix();
 004    renderer.setSize( window.innerWidth,        window.innerHeight/2 );
 005    }

Responding to the mouse

If the mouse moves around, we want the logo to spin in 3D and follow it, so we need to store the position of the mouse from the halfway point of the document on both axes. This will allow the model to spin around the centre position, so when the mouse is above half the document we can make the logo face upwards.

001    function onDocumentMouseMove( event ) {    
 002    mouseX = ( event.clientX - windowHalfX );  
 003    mouseY = ( event.clientY - windowHalfY )   
 004    }

Responding to touch

Obviously, in this day and age your website visitor is quite likely to be looking at your page on a device with a touch screen (such as a smartphone or tablet), so here we need to register the touch movement and take the position of this exactly as we did with the mouse and store the position so that we can rotate it later.

001    function onDocumentTouchMove( event ) {
 002    if ( event.touches.length == 1 ) {
 003    mouseX = event.touches[ 0 ].pageX -         windowHalfX;
 004    mouseY = event.touches[ 0 ].pageY -      windowHalfY;
 005        }
 006    }

Animating the scene

The requestAnimationFrame is a W3C  specification for allowing browser vendors to take the strain of running a loop. This used to be done with setInterval but now can be done by calling this function. All this does is call itself, so we update the display by calling the render function.

001    function animate() {
 002        requestAnimationFrame( animate );
 003        render();
 004    }

Checking before we render

Now we set up the render function as mentioned in the last step. The first thing we need to do here is check whether we have WebGL enabled. If so, then we are going to animate the particles; if not, we’ll just move on to animating the 3D model’s position in the scene.

001    function render() {
 002    if(Detector.webgl) {

Animate the particles

We want the particles to be continually animating so here we rotate all of the particles on
the X axis which gives a swirling up and down motion, then we check the size of the particle and make them pulse up and down in size, which are then updated in the display.

001    var time = * 0.005;
 002    sphere.rotation.x = -0.01 * time;
 003    for( var i = 0; i < attributes.size.value. 
 length; i++ )
 004    {
 005    attributes.size.value[ i ] = 24 + 13 *     Math.sin( 0.1 * i + time );
 006    }
 007    attributes.size.needsUpdate = true;
 008    }

Rotate the model

Our final step of code takes the mouse X and Mouse Y position and then uses it to rotate the model. We then divide the mouse position by such a high number because we rotate in radians, 3.14 (pi) is 180 degrees. Any high numbers would cause the model to spin erratically. We then tell the renderer to render the scene and the camera. Test this in a browser to finally see it in action.

001    group.rotation.y += ( (mouseX/250) - group.rotation.y ) * .05;
 002    group.rotation.x += ( (mouseY/250) - group.rotation.x ) * .05;
 003    camera.lookAt( scene.position );
 004    renderer.render( scene, camera );
 005    }