Atomic Animation

For simplicity and reduction of verbosity, this article only provides prefixes for WebKit. If you need to support Opera prior to 15 and Firefox prior to 16, you will have to add prefixes yourself. This will not look right or animate below IE8, but IE9 can look okay with a prefixed transform property. The demo page linked at the bottom of this article has all appropriate prefixes to work in the most browsers.

I live in a place somewhat associated with atomic energy, and there's a few places--but not an annoying number--in the area whose names start with "Atomic". I also recently was asked to do a busy indicator for a web app and decided to go with something a bit different, and less boring^Wbog standard: an animated stereotypical atom.

It looks a bit like this: atom Captured while in motion.

The basic structure

The atom uses four elements in the markup: a container and three sub-elements, one for each ellipsoid. The container cannot be positioned statically as this would not contain the absolutely positioned children. Also I am normally quite against putting empty elements in to markup purely to enable more interesting styles but this is usually because there are alternatives which do not require that. In this case, not so much.

Live Example

I won't explain simple math here, so: Blah blah, three ellipsoids things and stuff, 60 degrees apart. Okay, moving on. The widths of the sub-elements are exactly 1/3rd the parent's size. Since I can't make border widths percentage based, and the chosen border radii don't have percentages that appear the same when scaling up the parent size, I decided just to pick a nice parent size that divides evenly by three and used pixels. As will be obvious later, scaling this up isn't quite as simple as changing the parent size anyway, so pixels are okay.

The electrons

Next up those paths should get some particles, just like real atoms. I used pseudo-elements for this, which is why I needed three sub-elements so I could have enough to do all the paths and particles easily.

The border around each electron is purely optional but I feel it makes them appear more distinct against the path they travel, something that seems more obvious when animated or if the colors are inverted. The color chosen should match the background color of the container or most recent parent with one set. If that background is semi-transparent it seems okay to just use the same color without the opacity component as the border here needs to be able to fully cover up the path around the electron. If the background is translucent enough using a solid color shows up too much, just do whatever seems to look the best.

Animation

The following animation is what I use to make the electrons go round-and-round. However before I reveal the needed properties and values on the :after elements to apply these, I think I should explain how I got them as right now they likely seem a bit like magic numbers, which can't really be avoided [in raw CSS].

Deriving the animation transforms

The transforms above describe an elliptical path that approximates the one created by the border-radius directives shown in the electrons section. To avoid calculating them by hand, I determined a seemingly fitting equation for those ellipsoids. Then I used a bit of JavaScript to spit out the appropriate keyframe block that was ready to be pasted directly in to CSS. This also made it much easier to try different numbers of divisions before deciding that eleven divisions (with ten distinct stops) was the lowest I could go and still have everything appear smoothly continuous. For your benefit, I've reproduced that code below with more than enough commentary.

// number of actual animation steps (the code generates a last
//   step same as the first one so the animation doesn't jump)
var n = 10;

// Division of a complete loop by the number of steps.
var step = Math.PI * 2 / n;

// Height (longest dimension) of the ellipsoid sub-elements.
var height = 102;

// Half the width of the ellipsoid path spans used for offsets.
var offset = 7;

// Horizontal "magic number" to achieve an ellipsoid that
//   approximates the one created by the border-radius.
var magic = .31;

var frames = "@keyframes busy {\r\n";
for (var i = 0, t = 0; t < Math.PI * 2 + step; t += step)
{
    // Generate x and y given the distance around the ellipse.
    var x = offset - magic * height / 2 * Math.cos(t);
    var y = height / 2 - offset + height / 2 * Math.sin(t);
    frames += "\t" + i + "% { transform: translate(" +
        Math.round(x) + "px, " + Math.round(y) + "px); }\r\n";
    i += 100 / n;
}

frames += "}";
console.log(frames);

The above produces a compact output to save lines, but extra newlines and spaces can be added to as needed. Instead of printing the output, it could be dynamically inserted in to the DOM as a stylesheet (or appended to the end of an existing one), but it seems preferable to remove the dependency on JavaScript here when the output will always be the same for any configuration based on static numbers.

Animating the properties

Lastly we can pair up those keyframes to the :after elements. In order to make the animation feel more natural yet use the same keyframe block for each electron, the second and third electrons need to not start at the same time (be delayed). The first thought to mind is to just add a simple positive animation-delay value, but this results in an odd startup while each electron waits out their delay before getting going.

What would be best is to have the second and third electrons start at a defined keyframe instead of at the first frame. However I don't care to calculate out what that keyframe is based on the overall animation duration, especially if I change it later, and it can't be done in any case. Interestingly what can be done, though, is to set a negative animation-delay which causes them to start at the keyframe (or tweening value) at that time, as if they had already been running when you actually start the animation. Now that's cool.

Finally, to prevent overlaps and to evenly distribute the particles, the delay for each is one third of the overall animation time. I chose 1.5s because it's not too slow or too fast, and it divides evenly by three. Using that, the particles will never run into (or over) each other.

And there you are! An animated atom of sorts. For a complete all-in-one example of this, please see the demo page. Thank you for reading.