CSS 3D Clouds Retake

This post is going to cover my experience following through the tutorial on making CSS 3D clouds posted here: http://www.clicktorelease.com/tutorials/css3dclouds/. I didn’t make the original code, but I did run into several issues while I went through and I wanted to share my experience, work-arounds, and pieces of code that were missing from the original tutorial.

All the questions that came up and fixes changes here were done on the Chrome Beta (v20.0.1132.41 beta-m to be exact)

1. Creating the world and a camera

CSS 3D Clouds Step 1

In this step, you create two div’s in the body of your HTML 5 page, the outer <div> gets an id of viewport and the inner <div> gets an id of world. From there you setup some structural styling (viewport spans entire screen via absolute positioning, world is centered in the middle)

Initial Page Setup

Some initial styling needs to be done to make it look like the demo, here’s what I have:

* {
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}

body {
	overflow: hidden;
}

#viewport {
	background-image: linear-gradient(bottom, rgb(69, 132, 180) 28%, rgb( 31, 71, 120 ) 64%);
	background-image: -o-linear-gradient(bottom, rgb(69, 132, 180) 28%, rgb( 31, 71, 120 ) 64%);
	background-image: -moz-linear-gradient(bottom, rgb(69, 132, 180) 28%, rgb( 31, 71, 120 ) 64%);
	background-image: -webkit-linear-gradient(bottom, rgb(69, 132, 180) 28%, rgb( 31, 71, 120 ) 64%);
	background-image: -ms-linear-gradient(bottom, rgb(69, 132, 180) 28%, rgb( 31, 71, 120 ) 64%);
}

#world {                    
	background-color: rgba(255, 0, 0, .2);
}

Vendor prefixes

I’m so used to Chrome being the “latest and greatest” that I honestly expected to be able to use non-prefixed CSS properties and have the code “just work”. That’s NOT the case. As of this writing you will need to use prefixed properties, so add the following prefixes into the appropriate rules:

#viewport {
	perspective: 400;
	-webkit-perspective: 400;
	-moz-perspective: 400;
	-o-perspective: 400;
}

#world {                    
	transform-style: preserve-3d;
	-webkit-transform-style: preserve-3d;
	-moz-transform-style: preserve-3d;
	-o-transform-style: preserve-3d;
}

Help, my javascript code doesn’t work!

You probably put your javascript in the <head> tag, which means that document.getElementById( 'world' ) will not work because the elements don’t exist yet. Put the script at the end right before the </body> tag and it should work if everything else is correct.

Besides, it’s just good practice to put your javascript last.

Help, my javascript code doesn’t work! (pt 2)

This just shows my ignorance of javascript, but if something still isn’t working, you might have this problem:

Javascript uses the \ character in your strings to tell the parser to treat the next line as if the string continued:

'translateZ( ' + d + 'px ) \
rotateX( ' + worldXAngle + 'deg) \
rotateY( ' + worldYAngle + 'deg)';

Is the same as:

'translateZ( ' + d + 'px ) rotateX( ' + worldXAngle + 'deg) rotateY( ' + worldYAngle + 'deg)';

Zooming javascript

The code samples in the original tutorial omit the code to zoom in and out with the mouse wheel. Here it is in all it’s javascripty wonderfulness:

window.addEventListener( 'mousewheel', onContainerMouseWheel );
window.addEventListener( 'DOMMouseScroll', onContainerMouseWheel );

function onContainerMouseWheel( event ) {
	event = event ? event : window.event;
	d = d - (event.detail ? event.detail * -5 : event.wheelDelta / 8);
	updateView();
}

2. Adding objects to our world

CSS 3D Clouds Step 2
E.g. .cloudBase.

Create cloud base code is incorrect

Instead of:

for( var j = 0; j <<; 5; j++ ) {

the correct line is:

for( var j = 0; j < 5; j++ ) {

Actual random numbers and prefixed transforms

The random numbers for createCloud():

var random_x = 256 - ( Math.random() * 512 );
var random_y = 256 - ( Math.random() * 512 );
var random_z = 256 - ( Math.random() * 512 );

The transform styles for createCloud()

div.style.transform = t;
div.style.webkitTransform = t;
div.style.MozTransform = t;
div.style.oTransform = t;

3. Adding layers to our objects

CSS 3D Clouds Step 3
There were a couple of things in this section that cause me to scratch my head and go whyyyyy?

Code for random generation and transforms.

Random variables:

var random_x = 256 - ( Math.random() * 512 );
var random_y = 256 - ( Math.random() * 512 );
var random_z = 100 - ( Math.random() * 200 );
var random_a = Math.random() * 360;
var random_s = .25 + Math.random();
random_x *= .2; random_y *= .2;

Vendor transforms:

cloud.style.transform = t;
cloud.style.webkitTransform = t;
cloud.style.MozTransform = t;
cloud.style.oTransform = t;

Why don’t I see the new squares?

You have to add in the style for .cloudLayer into your CSS:

.cloudLayer {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 256px;
	height: 256px;
	margin-left: -128px;
	margin-top: -128px;
	background-color: rgba( 0, 255, 255, .1 );
	-webkit-transition: opacity .5s ease-out;
	-moz-transition: opacity .5s ease-out;
	-o-transition: opacity .5s ease-out;
}

I see the cloud layers, but why are they are all flat?

Yeah, this got me too, the parent div’s need to have preserve-3d, so add this into your CSS:

#world div {
	transform-style: preserve-3d;
	-webkit-transform-style: preserve-3d;
	-moz-transform-style: preserve-3d;    
	-o-transform-style: preserve-3d;    
}

4. Making the 3D effect work

CSS 3D Clouds Step 4

This section is essentially “make the layers point at the camera”. You still want them projected into the same locations, but you want them to always face the camera, giving you that sense of “volume” effect.

Vendor Transforms and Update()

First, here’s all the vendor transforms:

layer.style.transform = t;
layer.style.webkitTransform = t;
layer.style.MozTransform = t;
layer.style.oTransform = t;

Now, you also need to call this update manually once right before the end of your script. So right before the closing script tag, make sure you call this:

update();

Render Loop

Finally, even if you do this, you’ll notice that your layers still don’t point at the camera. You need to add in a function that loops and updates the layers in the #viewport at regular intervals. You could add a call to the update function inside your mouse move event, but we’ll need the loop to get the rotation to work in the next step, so it’s better if you just do this now.

(function() {
	var lastTime = 0;
	var vendors = ['ms', 'moz', 'webkit', 'o'];
	for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
		window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
		window.cancelRequestAnimationFrame = window[vendors[x]+
			'CancelRequestAnimationFrame'];
	}
	
	if (!window.requestAnimationFrame)
		window.requestAnimationFrame = function(callback, element) {
			var currTime = new Date().getTime();
			var timeToCall = Math.max(0, 16 - (currTime - lastTime));
			var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
				timeToCall);
			lastTime = currTime + timeToCall;
			return id;
		};
	
	if (!window.cancelAnimationFrame)
		window.cancelAnimationFrame = function(id) {
			clearTimeout(id);
		};
}())

Lets break this down. I discovered that this function is a polyfill for the browser animation spec here one Paul Irish’s awesome blog http://paulirish.com/2011/requestanimationframe-for-smart-animating/. Previously, when you would animate something, you would set a timer, and every few milliseconds go and update move something from point A to point B on the screen with a small increment. In order to get smoother animations, the browsers are starting to support this requestAnimationFrame function that allows several changes to be made, and update everything with a single reflow / redraw of the screen. This becomes especially handy when you are updating multiple elements on the screen and you want a clean responsive display. It also means that the browser can stop animating when you switch tabs, which means that you don’t eat up battery when someone isn’t looking at your page :)

All you really need to know is that this creates a function on the javascript window object that tells the browser to “please render an animation frame, and call back to this function when you are done and ready to render the next frame”.

5. Final words

CSS 3D Clouds Step 5

Subtle Rotating

Not mentioned, but if you want the clouds to slowly rotate like the demo, you need to add in the rotate z component into the transform update of your layers like so:

'rotateZ( ' + layer.data.a + 'deg ) /

And, you need to add in a speed property into your cloud layers when you create them:

speed: .2 * Math.random() - .1

Adding Cloud Images

Instead of this:

var cloud = document.createElement( 'div' );

Use this (or find your own cloud .png, it should be transparent to work properly)

var cloud = document.createElement( 'img' );
cloud.style.opacity = 0;
var src = 'http://www.clicktorelease.com/tutorials/css3dclouds/cloud.png';
( function( img ) { 
	img.addEventListener( 'load', function() {
		img.style.opacity = .8;
	} )
 } )( cloud );
cloud.setAttribute( 'src', src );

And finally to remove all the debug styles remove the following lines out of your CSS:

From #world remove:

background-color: rgba(255, 0, 0, .2);

From .cloudBase remove:

background-color: rgba( 255, 0, 255, .5 );

From .cloudLayer remove:

background-color: rgba( 0, 255, 255, .1 );

That should cover it! Now go make some happy clouds.

Credits

Everything here was completely taken from the Click To Release CSS 3D Clouds tutorial and expanded to include the missing parts.