Animating CSS3 Transforms with MooTools Fx

By  on  
MooTools CSS3 Fx

I recently posted an awesome (if I may say so myself) CSS3 / MooTools tutorials called Create a Photo Stack Effect with Pure CSS Animations or MooTools.  The post presented two ways, a pure CSS method or MooTools-powered class, to duplicate Google+'s elegant photo stack animation.  The MooTools method took a bit of Fx exploration since MooTools doesn't animate CSS transform out of the box, and this exploration reminded me of just how amazing MooTools' Fx classes are.  We all know MooTools features the smoothest effects of any JavaScript toolkit, and now I'm confident in saying the Fx class is as flexible or more flexible than any toolkit available.  Let me show you how simple it is to override a Fx instance's set method  to animate any CSS property you'd like!

Quick Fx Primer

JavaScript animations work by animating an element's style properties over a defined duration.  Throughout that duration, however, the styles are updated at varying time increments (not as simples as division of style points over duration) based on the transition (Fx.Transitions) used.

Fx Out of the Box

MooTools' Fx class is the base for many classes in MooTools, including Fx.Tween and Fx.Morph.  Fx's role is to provide a solid base for all types of animations, as well as managing style values over time.  Fx defines several base methods for animating as well, including start, cancel, set, pause, and resume methods.  The Element.Styles and Element.ShortStyles objects aid in parsing styles.  These objects look like:

Element.Styles = {
	left: '@px', top: '@px', bottom: '@px', right: '@px',
	width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
	backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
	fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
	margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
	borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
	zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
};

Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};

['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
	var Short = Element.ShortStyles;
	var All = Element.Styles;
	['margin', 'padding'].each(function(style){
		var sd = style + direction;
		Short[style][sd] = All[sd] = '@px';
	});
	var bd = 'border' + direction;
	Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
	var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
	Short[bd] = {};
	Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
	Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
});

With this format, static styles can be processed and translated from one value to another regardless of whether shorthand or longhand syntax is used. Note that these styles all have strictly defined value formats.

A Problem

One problem with the format provided in Element.Styles (and the problem isn't MooTools-specific) is that the transform property has no standard value signature.  You can incorporate translate, rotate, scale, and so on, thus no set format can be reliably nailed down, much less parsed efficiently.  So how can we utilize MooTools to to animate transforms and other properties which have no absolute value format?  Damn easily.

A Solution

The key to managing transition value settings is customizing your Fx instance's set method.  The set method is called several times over the course of an Fx animation to update the style to the proper value at that point in the animation. The set method with signature is:

set: function(value) {
	// MooTools works with that value here
}

The focal point of what I've illustrated is the value argument which is provided by the Fx instance.  The value argument represents the state within the animation, between 0 and 1, that the style should be set to.  Zero represents the animation lower value, the one value represents the larger value possibility of the animation.  So what value does value have?  Loads.  As long as you know the origin and destination value for the current animation, you can multiply the value by the destination state and get the desired result.   Take the Fx piece of my PhotoStack class for example:

myFx.set = function(value) {
	// Calculate image settings specific to this instance
	var index = image.retrieve("photostack-index"),
	targetRotation = (this.rotationStart + (index * this.rotationIncrement)), // deg
	targetTranslation = (this.translationStart + (index * this.translationIncrement)), // px
	targetTranslationPx = this.translationPx; //px

	// Create the style string for this spot in the animation
	var style = "rotate(" + (targetRotation * value) + "deg) translate(" + (targetTranslation * value) + "px, " + (targetTranslationPx * value) + "px) scale(" + (1 + (value * (this.options.scaleMax - 1))) + ")";

	// Update those styles accordingly
	image.setStyles({
		"-webkit-transform": style,
		"-moz-transform": style,
		"-o-transform": style,
		"-ms-transform": style,
		transform: style
	});
}.bind(this);

The style variable animates the transform of rotate, translate, and scale.  Note that the state of each translation property is calculated by multiplying the destination value by the value provided to the set function, and thus the style is changed to its appropriate fractional value!  If we were to log the value and resulting style calculation to the console, it would look like:

// Subsequent text is from: console.warn(value," / ", style);
// Remember that the style values are (value * destinationValue), calculating the fractional value or state of animation

0 / rotate(0deg) translate(0px, 0px) scale(1)

0.003942649342761062 / rotate(0.023655896056566372deg) translate(0px, -0.011827948028283186px) scale(1.000394264934276)

0.06490812266523716 / rotate(0.38944873599142293deg) translate(0px, -0.19472436799571147px) scale(1.0064908122665237)

0.16465721173163989 / rotate(0.9879432703898393deg) translate(0px, -0.49397163519491966px) scale(1.016465721173164)

0.35448191658586403 / rotate(2.1268914995151844deg) translate(0px, -1.0634457497575922px) scale(1.0354481916585865)

0.3726146371583089 / rotate(2.2356878229498536deg) translate(0px, -1.1178439114749268px) scale(1.0372614637158308)

0.39399644503897263 / rotate(2.363978670233836deg) translate(0px, -1.181989335116918px) scale(1.0393996445038973)

0.6364759677586626 / rotate(3.8188558065519755deg) translate(0px, -1.9094279032759878px) scale(1.0636475967758663)

0.6545084971874737 / rotate(3.9270509831248424deg) translate(0px, -1.9635254915624212px) scale(1.0654508497187474)

0.8235279807847222 / rotate(4.941167884708333deg) translate(0px, -2.4705839423541667px) scale(1.0823527980784724)

0.9238389680425416 / rotate(5.54303380825525deg) translate(0px, -2.771516904127625px) scale(1.0923838968042543)

0.9858158664573369 / rotate(5.914895198744022deg) translate(0px, -2.957447599372011px) scale(1.0985815866457338)

0.990545258721667 / rotate(5.9432715523300015deg) translate(0px, -2.9716357761650007px) scale(1.099054525872167)

1 / rotate(6deg) translate(0px, -3px) scale(1.1)

Tip: the longer the duration, the more the set method will be called.

The frequent set calls over the course of the animation create the animation.  In our example, we've updated the transform style; of course, you could animate any style within your custom set function.  What if you want to animate multiple CSS properties with an animation, including both properties supported  and not supported by MooTools?  Simple:

// Assuming myFx is an instance of Fx.Morph or Fx.Tween....

var oldSet = myFx.set;
myFx.set = function(value) {
	// Run the "old"
	oldSet.apply(this, arguments);
	
	// Do your custom animation stuff here
};

You "save" the instance's  initial set method and call it within your custom set method.

Hopefully the examples and explanation about MooTools' Fx presented above help you to understand some of the underlying code that allows you to animate element styles with MooTools.  The amount of customization to animations you can create by customizing the set method is unreal.  Happy animating!

Recent Features

  • By
    Regular Expressions for the Rest of Us

    Sooner or later you'll run across a regular expression. With their cryptic syntax, confusing documentation and massive learning curve, most developers settle for copying and pasting them from StackOverflow and hoping they work. But what if you could decode regular expressions and harness their power? In...

  • By
    Welcome to My New Office

    My first professional web development was at a small print shop where I sat in a windowless cubical all day. I suffered that boxed in environment for almost five years before I was able to find a remote job where I worked from home. The first...

Incredible Demos

  • By
    Hot Effect: MooTools Drag Opacity

    As you should already know, the best visual features of a website are usually held within the most subtle of details. One simple trick that usually makes a big different is the use of opacity and fading. Another awesome MooTools functionality is...

  • By
    HTML5’s window.postMessage API

    One of the little known HTML5 APIs is the window.postMessage API.  window.postMessage allows for sending data messages between two windows/frames across domains.  Essentially window.postMessage acts as cross-domain AJAX without the server shims. Let's take a look at how window.postMessage works and how you...

Discussion

  1. hamburger

    excellent trick David.
    does it mean that morph supports scale now?

  2. nice, will mootools 2 have these by default?

  3. piotr

    You should check out Fx.Tween.CSS3 (http://mootools.net/forge/p/fx_tween_css3)
    There is one difference, it uses CSS3 animations instead of periodical value sets.
    Last time I’ve been using this, there was problem with firing ‘complete’ event instantly but there’s easy workaround.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!