Let's Make a Framework: Animations Part 8

22 Jul 2010 | By Alex Young | Tags frameworks tutorials animation lmaf

Welcome to part 22 of Let’s Make a Framework, the ongoing series about building a JavaScript framework. This part continues looking at JavaScript animations.

If you haven’t been following along, these articles are tagged with lmaf. The project we’re creating is called Turing and is available on GitHub: turing.js.

Last week I explained how CSS3 animations work. In this part I’m going to demonstrate how to implement support for them using JavaScript.

CSS3 Feature Detection

The state of CSS3 support is currently in flux because the standards aren’t ready yet. Most browsers still use vendor prefixed tags, which means we need to know what browser we’re dealing with.

Detecting browser support for CSS3 is a little bit tricky, but it’s not impossible. WebKit browsers have an event object called WebKitTransitionEvent, and Opera uses OTransitionEvent. Firefox has a style attribute called MozTransition.

I’ve created an object with a list of properties that can be used to query vendor support:

// CSS3 vendor detection
vendors = {
  // Opera Presto 2.3
  'opera': {
    'prefix': '-o-',
    'detector': function() {
      try {
        document.createEvent('OTransitionEvent');
        return true;
      } catch(e) {
        return false;
      }
    }
  },

  // Chrome 5, Safari 4
  'webkit': {
    'prefix': '-webkit-',
    'detector': function() {
      try {
        document.createEvent('WebKitTransitionEvent');
        return true;
      } catch(e) {
        return false;
      }
    }
  },

  // Firefox 4
  'firefox': {
    'prefix': '-moz-',
    'detector': function() {
      var div = document.createElement('div'),
          supported = false;
      if (typeof div.style.MozTransition !== 'undefined') {
        supported = true;
      }
      div = null;
      return supported;
    }
  }
};

function findCSS3VendorPrefix() {
  for (var detector in vendors) {
    detector = vendors[detector];
    if (detector['detector']()) {
      return detector['prefix'];
    }
  }
}

Move Animation Implementation

To use CSS3 for move animations, we need to do the following:

  • Detect when a CSS property is being used to move an element
  • Get the vendor prefix
  • Set up CSS3 style properties instead of using Turing’s animation engine

Detecting when a CSS Property Means Move

The convention I’ve been using is to manipulate the left or top style properties to move an element. Whenever these properties are animated and a vendor prefix has been found, then we can use CSS transitions.

The best place to do this is in the property loop inside anim.animate:

for (var property in properties) {
  if (properties.hasOwnProperty(property)) {
    properties[property] = parseCSSValue(properties[property], element, property);
    if (property == 'opacity' && opacityType == 'filter') {
      element.style.zoom = 1;
    } else if (CSSTransitions.vendorPrefix && property == 'left' || property == 'top') {
      // Do CSS3 stuff here and return before Turing animates with its own routines
    }
  }
}

I’ve stolen camelize from Prototype to make writing out CSS easier:

element.style[camelize(this.vendorPrefix + 'transition')] = property + ' ' + duration + 'ms ' + (easing || 'linear');
element.style[property] = value;

In the case of Firefox 4, this would translate to:

element.style[MozTransition] = 'left 1000ms linear';
element.style['left'] = '100px';

I’ve put this in a function called start, and I’ve also added an end function to clear the transition afterwards:

CSSTransitions = {
  // ...

  start: function(element, duration, property, value, easing) {
    element.style[camelize(this.vendorPrefix + 'transition')] = property + ' ' + duration + 'ms ' + (easing || 'linear');
    element.style[property] = value;
  },

  end: function(element, property) {
    element.style[camelize(this.vendorPrefix + 'transition')] = null;
  }
};

The core of the property loop now looks like this:

CSSTransitions.start(element, duration, property, properties[property].value + properties[property].units, options.easing);
setTimeout(function() { CSSTransitions.end(element, property); }, duration);
return;

Conclusion

Defining CSS3 transitions in JavaScript isn’t too difficult once the vendor has been detected. A JavaScript API would be nicer, but generating the DOM version of the equivalent CSS isn’t too difficult.

This version misses explicit transition support — the Turing transition names would have to be mapped to CSS3 ones (or we could just switch to the names used by CSS).


blog comments powered by Disqus