Archive for December, 2011

Creating a Loading Spinner Animation in CSS and JavaScript

Update (12/20/2011): Now works in Firefox as well as Chrome and Safari.

Since I’m not a very good designer, I usually try to do as much styling, design, and graphics in code as I can. For instance, when I wrote this mobile compass application in HTML, I did all the graphics programmatically using Canvas and CSS.

I’m now working on a project that requires one of those loading spinner animations that Apple seems to have made famous, so naturally, I started looking into ways to create one purely in code and/or CSS (no external assets). Fortunately, I found a great post on the Signal vs. Noise blog demonstrating exactly what I wanted to do. However, I decided to take it one step further, and write some JavaScript to generate both the required CSS and the HTML. The advantage of generating everything dynamically is that you can configure things like the size, color, and the number of bars in the animation at runtime.

Here’s an example of some randomly generated loading spinner animations which are 100% CSS and HTML generated by JavaScript (Chrome and Safari only for now). If you’re interested in the code, here’s the original CSS from Signal vs. Noise, and below is my port to JavaScript (view the source of the demo to see how to use it). It only works in WebKit-based browsers for the time being, but I’ll update it as browser capabilities get better. It works in WebKit-based browser, and in Firefox.

Check out the example.

var Spinner = {
  SPINNER_ID: '_spinner',
  prefix: (navigator.userAgent.indexOf('WebKit') != -1) ? 'webkit' : 'moz',
  getSpinner: function(size, numberOfBars, color) {
    if (document.getElementById(this.SPINNER_ID) == null) {
      var style = document.createElement('style');
      style.setAttribute('id', this.SPINNER_ID);
      style.innerHTML = '@-'+this.prefix+'-keyframes fade {from {opacity: 1;} to {opacity: 0.25;}}';
      document.getElementsByTagName('head')[0].appendChild(style);
    }
    var spinner = document.createElement('div');
    spinner.style.width = size;
    spinner.style.height = size;
    spinner.style.position = 'relative';
    var rotation = 0;
    var rotateBy = 360 / numberOfBars;
    var animationDelay = 0;
    var frameRate = 1 / numberOfBars;
    for (var i = 0; i < numberOfBars; ++i) {
      var bar = document.createElement('div');
      spinner.appendChild(bar);
      bar.style.width = '12%';
      bar.style.height = '26%';
      bar.style.background = color;
      bar.style.position = 'absolute';
      bar.style.left = '44.5%';
      bar.style.top = '37%';
      bar.style.opacity = '1';
      bar.style.setProperty('-'+this.prefix+'-border-radius', '50px', null);
      bar.style.setProperty('-'+this.prefix+'-box-shadow', '0 0 3px rgba(0,0,0,0.2)', null);
      bar.style.setProperty('-'+this.prefix+'-animation', 'fade 1s linear infinite', null);
      bar.style.setProperty('-'+this.prefix+'-transform', 'rotate('+rotation+'deg) translate(0, -142%)', null);
      bar.style.setProperty('-'+this.prefix+'-animation-delay', animationDelay + 's', null);
      rotation += rotateBy;
      animationDelay -= frameRate;
    }
    return spinner;
  }
}

Loading Data Across Domains with JavaScript

I’m working on a project now that makes heavy use of XHR (XMLHttpRequest) to load data from a server. I’m writing the HTML/JS portion of the application on my local machine, but my server development environment is on a remote server under a different domain. Typically cross-domain XHR request aren’t allowed due to browser security restrictions, however there are two easy work-arounds:

  1. JSONP
  2. Cross-Origin Resource Sharing

JSONP

JSONP gets around the same origin policy by loading JavaScript from another domain into a dynamically generated <script> tag (script tags are not subject to the same origin policy which is what enables things like ads to be served from different domains). In your request (formed as the src attribute of the script tag), you typically specify a callback function that you have already defined in your application. The JavaScript that gets loaded into the dynamically generated script tag will call the specified function, passing in the requested JSON data.

(Note that JSONP requests are probably best handled by robust frameworks like the jQuery.getJSON() function.)

I’ve used JSONP in the past for things like this prototype client-side news reader, but the project I’m working on now uses XML as well as JSON, so I decided to use a different approach.

Cross-Origin Resource Sharing

Cross-origin resource sharing is similar to cross-domain policy files in the Flash word, but they are done through HTTP headers. The specification defines several different headers, however the only one I’ve needed so far is the response header Access-Control-Allow-Origin. Allowing my server to share resources with my local machine was as easy as adding the following line of PHP code before writing to the output buffer:

header('Access-Control-Allow-Origin: http://seeker.home');

(Seeker is the name of my development machine, named after the excellent Jack McDevitt novel.)

Of course, once I move the client-side portion of my application (the HTML, JS, and CSS files) to my server, I can delete or comment this line out since the application will be served from the same domain from which it needs to request data. In the meantime, however, it’s made local development much easier.

Cross-origin resource sharing is part of the XMLHttpRequest Level 2 specification, and is supported in all modern browsers.