Creating a sphere with 3D CSS

With CSS3’s 3D trans­forms I’ve illus­trated how to build a cube and a tetra­he­dron. It is also pos­si­ble to cre­ate a sphere-like object, albeit with many elements.

3D CSS Sphere
Works in the lat­est Safari and iOS (just about runs on an iPhone 4).

Exper­i­ment updated to sup­port –moz now that Fire­fox 10 sup­ports 3D transforms.

Recently I’ve been look­ing at cre­at­ing applic­a­ble 3D carousels. These rely on posi­tion­ing pan­els in a cir­cle around a cen­tral point (ie. rota­tion about the Y-axis), I put these pan­els in an unordered list. A nat­ural exten­sion is to dupli­cate each <ul> and rotate about the X-axis. With ‘A’ rounds (or lists), and ‘B’ pan­els per round (<li>s), I built a script that would dis­trib­ute these in a cir­cu­lar man­ner, pan­els about Y-axis, lists about the X-axis, cre­at­ing a sphere.

The more ele­ments per round and the more rounds, the smoother the sphere. But this soon stacks up and kills Safari. The opti­mum num­bers have been about 9 rounds and 30 pan­els per round for a decent look­ing sphere, but that’s 279 3D ele­ments and Safari starts to choke. Allow­ing the pan­els to over­lap eases this some­what and leads to a tighter sphere but it still appears blocky.

The biggest gains come with bor­der radius. Using a huge radius that made each panel cir­cu­lar the sphere sud­denly gained a lovely cur­va­ture, and the num­ber of rounds and pan­els could be reduced. In the exper­i­ment I use 8 rounds and 24 pan­els (200 ele­ments). This doesn’t start chok­ing until I start aggres­sively animating.

3D sphere using CSS transform

Play­ing with this I’ve built a few dif­fer­ent styles of sphere. In the exper­i­ment I’ve included the blocky “square” ver­sion, along with the smoothed out bor­der radius one (default). Mark­ing pan­els white, and a few black can cre­ate a nice eye-ball effect.

Sphere without border radius Eye

Also included in the exper­i­ment are ver­sions show­ing a sin­gle round and another style named “con­tact”. This takes two lists and ani­mates them like the space trans­porta­tion device in the Jodie Fos­ter movie of the same name.

Rotating rounds Half a sphere Kaleidoscope effect

Ani­mat­ing the bor­der radius on all 192 pan­els (if your machine can cope), gives a neat kalei­do­scope effect, also included in the experiment.

Code

The gen­er­ated HTML is sim­ply a cou­ple of <div>s con­tain­ing lists:

<div id="sphere">
	<div class="container">
		<ul>
			<li></li>
			<li></li>
			…
		</ul>
		…
	</div>
</div>

To trans­form each set of pan­els into a cir­cle the total num­ber is divided by 360 to get the angle of rota­tion. Merely rotat­ing will put all pan­els on top of each other, trans­lat­ing in the Z axis will move them out from the cen­tre point. The cor­rect trans­la­tion dis­tance so the pan­els slightly over­lap (i.e. the cir­cle radius) is worked out with some sim­ple trigonom­e­try. Lists are rotated in the X axis, sim­ply the num­ber of lists divided by 360.

Loop­ing through each panel for each list, the angles of rota­tion are grad­u­ally increased by the cal­cu­lated incre­ments and applied to the elements:

var panels = p || this.panels,
rounds = r || this.rounds,
rotationPerPanel = 360/panels,
rotationPerRound = 360/2/rounds,
yRotation, xRotation,
width = this.panelWidth,
zTranslate = (width/2) / Math.tan(rotationPerPanel * Math.PI/180),
$container = this.el,
$ul, $li, i, j;

this.el.html('');
for(i = 0; i < rounds; i++) {
	$ul = $('<ul>');
	xRotation = rotationPerRound * i;
	$ul[0].style[transformProperty] = "rotateX("+ xRotation + "deg)";
	for(j = 0; j < panels; j++) {
		$li = $('<li>');
		yRotation = rotationPerPanel * j;
		$li[0].style[transformProperty] = "rotateY("+ yRotation +"deg)
                         translateZ("+ zTranslate +"px)";
		$ul.append($li);
	}
	$container.append($ul);
}

To achieve the 3D effect we give #sphere some per­spec­tive, which forms the con­tain­ing block. A sec­ond con­tainer tran­si­tions between dif­fer­ent trans­forms, let­ting us rotate the sphere. The com­pli­cated trans­form parts are applied inline via the JavaScript above, but each list ele­ment needs preserve-3d so its direct descen­dants are trans­formed in the same 3D space (rather than in 2D).

#sphere {
width: 100px;
height: 100px;
margin: 200px auto;
-webkit-perspective: 800;
-moz-persective: 800;
}

.container {
width: 100px;
height: 100px;
-webkit-transition: -webkit-transform 200ms linear;
-webkit-transform-style: preserve-3d;

-moz-transition: -webkit-transform 200ms linear;
-moz-transform-style: preserve-3d;
}

.container > ul {
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
width: 100%;
height: 100%;
position: absolute;
}

.container li {
width: 98px;
height: 98px;
position: absolute;
display: block;
background: #000;
border: 1px solid #fff;
opacity: 0.1;
border-radius: 50px;
}
Paul Hayes

Paul Hayes is a developer at Last.fm. You should follow him on Twitter, where he talks about UX, HTML, CSS and JavaScript, amongst other cool stuff.