A CSS Experiment

The orrery at the top of this site is an experiment in CSS 3 animation. (You might need a more modern browser to see it in action.) It uses data from nasa.gov to accurately represent how planets rotate around our sun.

For performance reasons, I’m only showing the first 6 moons (Luna, Phaebos & Daemos of Mars, and Io, Ganymede, & Europa of Jupiter). They are revolving accurately, but have been all been significantly slowed by an arbitrary amount in order to render them visible in the animation.


Orreries are mechanical models of the solar system, intended to model how fast planets revolve around the sun. Typically they are not to scale in terms of size or distance from the sun, but rather are an attempt to achieve a highly accurate model of planetary orbit relative to each other with clockwork gearing.

The orrery at the top of this page is visually stylized in terms of planetary placement, orbital size, and shape — however, the planets really do rotate at those speeds relative to each other.

An old 3D Studio Max experiment, rendered with Vray.

Orreries have been a long-held fascination of mine. I find the natural revolutions of the planets to be exquisitely beautiful. This clip is from a test render I did many years ago which shows gearing that results in a extremely accurate rotations and revolutions of the Earth, in a (virtual) mechanical model.

For my CSS orrery at the top of the page, I chose to include both Pluto and Eris to show how slowly they revolve relative to celestial bodies closer to the sun. (Tip: if you can’t see some planets, try shrinking your browser width.)

The code

The code for the orrery is posted on github if you want to take a detailed look. Here’s a brief walkthrough of the relevant bits.

The markup to generate the orbital rings and planets themselves is relatively simple. Here’s all it takes for Earth — CSS takes care of the color, shape, positioning, and rotation animation:

1 <div class="earth" id="earth">
2   <div class="label"><span class="symbol"></span>Earth</div>
3   <div class="orbit">
4     <div class="planet">
5       <div class="satellite s_1"></div>
6     </div>
7   </div>
8 </div>
Full code available on github.

SCSS function calls

The adjustments to the planets were made by tweaking the following mixin calls until the planets were sized and laid out how I wanted. I mostly eyeballed the orbit size, position, and size of the planets by beginning with accurate numbers and making small adjustments.

 1 // Build the planets
 2 // buildPlanet($orbitSize, $orbitPos, $color, $planetSize)
 3 .mercury { @include buildPlanet(10%,  2%, $color-2, $mercury-size / 1); }
 4 .venus   { @include buildPlanet(16%,  3%, $color-3, $venus-size / 1); }
 5 .earth   { @include buildPlanet(23%,  4%, $color-4, $earth-size / 1); }
 6 .mars    { @include buildPlanet(31%,  5%, $color-5, $mars-size / 1); }
 7 .jupiter { @include buildPlanet(40%,  6%, $color-6, $jupiter-size / 5); }
 8 .saturn  { @include buildPlanet(50%,  7%, $color-7, $saturn-size / 5); }
 9 .uranus  { @include buildPlanet(61%,  8%, $color-8, $uranus-size / 5); }
10 .neptune { @include buildPlanet(73%,  9%, $color-9, $neptune-size / 5); }
11 .pluto   { @include buildPlanet(86%, 10%, $color-10, $pluto-size / 0.5); }
12 .eris    { @include buildPlanet(100%, 11%, $color-11, $eris-size / 0.5); }
Full code available on github.

Mixin to build the planets

The mixin itself is fairly crude, but it essentially turns a <div class="orbit"></div> into a round object using border-radius. A similarly round ::before pseudo-element positions the planet right on the edge of the orbit.

The reason I used nesting pseudo objects is because it simplifies the animation process by just requiring the .orbit element to rotate. Everything else is a child of that, so rotates freely while also following the rotation of its parent.

 1 @mixin buildPlanet($orbitSize, $orbitPos, $color, $planetSize) {
 2   $planetSize: $planetSize * 3;
 3   $opacity: 0.55 - ($orbitPos/100%)*2;
 5   width: $orbitSize;
 6   height: $orbitSize;
 7   position: absolute;
 8   left: $orbitPos + 26%;
 9   top: ($orbitPos * 3) + 48%;
11   .orbit {
12     width: 100%;
13     padding-top: 100%;
14     border-radius: 50%;
15     position: relative;
16     left: -50%;
17     top: -50%;
18     z-index: 10;
19     background-color: rgba($color, $opacity);
20     -webkit-animation-play-state: running;
21     animation-play-state: running;
22     border: 4px solid transparent;
24     &:hover {
25       -webkit-animation-play-state: paused;
26       animation-play-state: paused;
27       border: 4px solid lighten($color, 1%);
28       cursor: pointer;
30       .planet:before {
31         border: 4px solid lighten($color, 5%);
32       }
33     }
34   }
36   .planet {
37     display: block;
38     width: $planetSize;
39     height: $planetSize;
40     position: absolute;
41     left: -3px;
42     top: 50%;
43     z-index: 20;
44     box-sizing: border-box;
46     &:before {
47       content: '';
48       display: block;
49       width: $planetSize;
50       height: $planetSize;
51       background-color: lighten($color, 1%);
52       border-radius: 50%;
53       position: absolute;
54       border: 4px solid transparent;
55       top: -50%;
56       left: -50%;
57       box-sizing: border-box;
58     }
59   }
60 }
Full code available on github.

The CSS animation process

As an example, here’s how we get the Earth to rotate. This block of CSS is the main engine that will rotate an object indefinitely. We’ll later use the name of this keyframe animation (‘year’) for the CSS 3 animation property.

 1 // CW
 2 @-webkit-keyframes year {
 3   from {
 4     -webkit-transform: rotate3d(0, 0, 1, 180deg);
 5             transform: rotate3d(0, 0, 1, 180deg);
 6   }
 7   to {
 8     -webkit-transform: rotate3d(0, 0, 1, -180deg);
 9             transform: rotate3d(0, 0, 1, -180deg);
10   }
11 }
Full code available on github.

This sets up how fast the planets should rotate relative to each other, modified by the $speed variable. The speed value was used to fine-tune the speed that the planets rotated altogether so I could adjust it easily. I included Earth as an example below.

 1 $speed:   8; // Increase to speed up planetary rotation
 3 // Planetary orbital periods, modified by the $speed
 4 // http://nssdc.gsfc.nasa.gov/planetary/factsheet/
 5 $earth-deg:    365.26 / $speed;
 7 // Convert planetary orbital periods to animation timing in seconds
 8 $earth:        #{earth-deg}s; // outputs '26.09s'; Put another way, it will take 26.09 seconds to make one revolution around the sun
10 // Animate planetary orbits
11 .earth .orbit  { -webkit-animation: year #{$earth} linear infinite;
12                          animation: year #{$earth} linear infinite; }
Full code available on github.

Why not use SVG animation?

The main reason for not using SVG animation is that I was trying to learn more about CSS animation performance for rotate transforms. If I do a v2 of the orrery at any point in the future, I will probably try to just create vector graphics of the planets and use an svg animation library like Snap.svg.

In general, the animation is very CPU intensive for rotating this many objects. I included the pause button to limit the resources the page takes up, but it’s something I’d like to fix in the future.

Hi, I'm Zac Halbert. I'm a digital product designer and lifelong learner living in San Francisco, California with my wife, son, and sheepdog. I currently run the Product Design & UX track at Tradecraft, where we train smart people to succeed in traction roles at high-growth startups.

I also own independent product design consultancy Scout Hawk Product Design Studio, where I help entrepreneurs turn hazy ideas into concrete digital products, and Foliotwist, a portfolio and marketing SaaS company for visual artists. I also advise a number of companies on the intersection of user experience design, product design and management, and rapid prototyping and idea validation that draws heavily from the Lean Startup philosophy.

My background is a mixture between formal graphic design and fine arts education, mixed with a long history of self-taught frontend programming skills. I began to learn HTML, CSS, and UI design at age 12 (I was a huge nerd), and got my first job as a web designer at age 15 — and have been doing it and loving it ever since. I eventually got my degree in graphic design, but have diverse interest in nearly every field that blends technical and creative work.

While I'm not very good at keeping my portfolio or blog up to date, I always love connecting with fellow designers, entrepreneurs, and developers. Here are just a few of the companies I've had the pleasure to work with.