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.

What I do at Interknowlogy

I get asked this question a lot:

“So, what exactly do you do at your job?

The most common answer is usually me fumbling through what a surface is, multi-touch, and ending with something about consulting, cool projects, and NDA agreements. They say a picture is worth a thousand words.┬áSo, since a video is between 30-60 frames per second, that’s gotta be about a book right there, so I figured I’d give you a taste of what I do at my main full time job.

http://www.interknowlogy.com

A bit of background on the demo; You may remember this post about building a really simple P2P network, and is the foundation of what we were eventually building out here. The framework itself is built out into three distinct layers, each independent of each other and interchangeable. For instance, the demo here is showing a P2P network layer, which is very nice for small demos because there is very little configuration required. However, because this layer is independent of the others it would be trivial to write an enterprise level sever based implementation that could be used to conduct sessions anywhere in the world.

The second layer is the framework that we’ve built up to handle the logic of managing a remote session, adding / removing remotable objects, synchronization, passing through commands, and routing commands from the network up to the individual remotable objects. The final layer is the remoting piece individual controls. It’s a layer of logic that is attached onto existing controls. The cool factor of the way this is built is it allows for a huge degree of flexibility at a control level if you need it, but also the power of being able to drop in a few lines of code and have objects magically start controlling and presenting.

Hope you enjoyed it, and let me know what you think!

- Paul Rohde

Fracture > Retrospect

As I’ve continued working on this game due to the large amount of support from friends and professors here at Neumont, I’ve decided to rename the game due to another game being released with the exact same name by Lucas Arts (http://www.lucasarts.com/games/fracture/) I’m still unsure if I’m going to end up releasing a full game or trying to sell it at all via steam or xbox live or not, there is still a huge amount of work that still needs to be done on the game to make it production worthy.

As far as progress on the game goes, there are now power-ups, better game logic, a simple menu system that was thrown in last minute, several difficulty levels, level regeneration, two new space backdrops, new weapons, and the list goes on… needless to say there has been a good chunk of additional work put into this game since the last time you saw it.

In addition, I was given the opportunity to demo this game to the new prospective students this last Friday after they went through a XNA demo and had a chance to try out what you could do with XNA. A lot of the students were very impressed, though I couldn’t decide if it was a ‘oooo this is a cool game, or oooo this cool that someone here did something like this’ kind of reaction. Guess we’ll see when I get the chance to create and release a demo version :)

However, my blog is not complete without giving you some sort of media to look at, so, here is a desktop background made from one of the space backdrops in the game at 1920×1200 for your viewing / using pleasure.

Download at 1920 x 1200