1 /** 2 * @fileoverview 3 * @class StyleTransition 4 * @namespace APE.anim 5 *<p> 6 * For morphing an objects styles across a css-time-continuum. 7 *</p> 8 * @author Garrett Smith 9 * 10 * @requires APE.anim.Animation 11 * @requires APE.color 12 * @requires APE.dom.style-f 13 */ 14 15 /** 16 * @param {String} id of the element 17 * @param {Object} styleObject in the form of {color: "#030", fontSize : "12px"} 18 * @param {ufloat} [duration] optional number of seconds. 19 * @param {Function} [transition] optional funtion that takes a float [0-1] and returns a float [0-1] 20 * @extends APE.anim.Animation 21 * @constructor 22 */ 23 APE.anim.StyleTransition = function(id, styleObject, duration, transition) { 24 APE.anim.Animation.call(this, duration); // invoke super constructor. 25 if(id.id) id = id.id; 26 this.id = id; 27 this.adapters = []; 28 this.style = document.getElementById(this.id).style; 29 if(transition) 30 this.transition = transition; 31 this.init(styleObject); 32 }; 33 34 APE.extend(APE.anim.StyleTransition, APE.anim.Animation, { 35 36 inited : false, 37 /** 38 * @method run 39 * @memberOf APE.anim.StyleTransition 40 * @description overrides (implements) <code>run()</code> in Animation. 41 * Runs the animation, getting the correct value 42 * from each ITransitionAdapter. 43 * This run() method gets called by Animation. 44 */ 45 run : function run(rationalValue) { 46 var i = 0, 47 adapters = this.adapters, 48 len = adapters.length, 49 adapter; 50 while(i < len) { 51 adapter = adapters[i++]; 52 this.style[adapter.prop] = adapter.blendTo(rationalValue); 53 } 54 }, 55 56 /**@private */ 57 init : function(styleObject) { 58 if(this.inited) return; 59 60 var el = document.getElementById(this.id), 61 adapters = [], 62 adapter, 63 APE = window.APE, 64 prop, 65 units, 66 fromValue, toValue, 67 dom = APE.dom, 68 TransitionAdapterFactory = APE.anim.TransitionAdapterFactory, 69 ThresholdTransitionAdapter = TransitionAdapterFactory.ThresholdTransitionAdapter, 70 ImmediateThresholdTransitionAdapter = TransitionAdapterFactory.ImmediateThresholdTransitionAdapter 71 72 // Loop through style object to find values. 73 for(prop in styleObject) { 74 toValue = styleObject[prop]; 75 if(!toValue) continue; // CSSStyleRule. 76 77 if(prop == "opacity" && !("opacity"in this.style) 78 && ("filter"in this.style)) { 79 prop = "alpha"; 80 this.style.zoom = "1"; 81 fromValue = dom.getFilterOpacity(el); 82 } 83 else { 84 if(prop == 'clip' && (!fromValue || fromValue.indexOf("auto") != -1)) { 85 fromValue = "rect(0px " + el.offsetWidth + "px " + el.offsetHeight + "px 0px)"; 86 } 87 else { 88 units = dom.getStyleUnit(toValue); 89 fromValue = dom.findInheritedStyle(el, prop, units); 90 } 91 } 92 // Get a ITransitionAdapter from the factory. 93 adapter = TransitionAdapterFactory.fromValues(prop, fromValue, toValue); 94 adapters.push( adapter ); 95 } 96 97 // IE will not properly render visibility when 98 // 1) visibility is initially hidden 99 // 2) alpha filter is applied 100 // 3) and visibility is then set to visible. 101 // after that, the element doesn't appear visible. 102 // Workaround: first transition is visibility. 103 adapters.sort(function(a,b){ 104 return (a instanceof ImmediateThresholdTransitionAdapter ? -1 : 1); 105 }); 106 107 this.adapters = adapters; 108 }, 109 110 /** 111 * @memberOf APE.anim.StyleTransition 112 * Helpful debugging info. */ 113 toString : function() { 114 return "StyleTransitionAdapter : id=#"+this.id+"\n" + 115 APE.anim.Animation.prototype.toString.call(this) + 116 "\nAdapters:\n " + 117 this.adapters.join("\n "); 118 } 119 120 }); 121 122 /** 123 * Factory for APE.anim.TransitionAdapterFactory Interface. 124 * @return {ITransitionAdapter} ITransitionAdapter for a specific type of style setting during run. 125 * The {ITransitionAdapter} implements blendTo(rationaValue). 126 * @class 127 * @private Used internally. 128 */ 129 APE.anim.TransitionAdapterFactory = { 130 // "1px", "1.1px", "-.1px" => ["-.1px", "-.1", "px"] 131 lengthExp : /(^-?\d+|(?:-?\d*\.\d+))(px|em|ex|pt|pc|in|cm|mm|%)/i, 132 colorExp : /color/i, 133 positiveLengthExp : /(?:width|height|padding|fontSize)$/ig, 134 filterExp : /alpha/, 135 opacityExp : /^opacity/, 136 intExp : /^\d+$/, 137 noVisibilityExp : /^(?:hidden|collapse)/, 138 139 fromValues : function(prop, fromValue, toValue) { 140 141 if(this.positiveLengthExp.test( prop )) { 142 return new this.PositiveLengthTransitionAdapter(prop, fromValue, toValue); 143 } 144 if(this.colorExp.test( prop )) 145 return new this.ColorTransitionAdapter(prop, fromValue, toValue); 146 if(prop == 'clip') 147 return new this.ClipTransitionAdapter(prop, fromValue, toValue); 148 if(this.lengthExp.test( fromValue )) { 149 return new this.LengthTransitionAdapter(prop, fromValue, toValue); 150 } 151 if(this.filterExp.test( prop )) 152 return new this.FilterTransitionAdapter(prop, fromValue, toValue); 153 if(this.opacityExp.test( prop )) 154 return new this.OpacityTransitionAdapter(prop, fromValue, toValue); 155 if(prop == "fontWeight" && this.intExp.test( fromValue ) && this.intExp.test( toValue )) { 156 return new this.FontWeightTransitionAdapter(prop, fromValue, toValue); 157 } 158 if(prop == "visibility" && this.noVisibilityExp.test(fromValue) 159 || prop == "display" && fromValue == "none") 160 return new this.ImmediateThresholdTransitionAdapter(prop, fromValue, toValue); 161 // Return an object that sets toValue on completion. 162 return new this.ThresholdTransitionAdapter(prop, fromValue, toValue); 163 } 164 }; 165 166 (function() { 167 168 var APE = window.APE, 169 ColorRGB = APE.color && APE.color.ColorRGB, 170 171 Adapters = { 172 /** @augments APE.anim.TransitionAdapterFactory */ 173 PositiveLengthTransitionAdapter : PositiveLengthTransitionAdapter, 174 ColorTransitionAdapter : ColorTransitionAdapter, 175 LengthTransitionAdapter : LengthTransitionAdapter, 176 FilterTransitionAdapter : FilterTransitionAdapter, 177 OpacityTransitionAdapter : OpacityTransitionAdapter, 178 FontWeightTransitionAdapter : FontWeightTransitionAdapter, 179 ThresholdTransitionAdapter : ThresholdTransitionAdapter, 180 ImmediateThresholdTransitionAdapter : ImmediateThresholdTransitionAdapter, 181 ClipTransitionAdapter : ClipTransitionAdapter 182 }; 183 184 APE.mixin(APE.anim.TransitionAdapterFactory, Adapters); 185 186 function TransitionAdapter(prop, fromValue, toValue, units) { 187 this.prop = prop; 188 this.fromValue = fromValue; 189 this.toValue = toValue; 190 if(units) 191 this.units = units; 192 } 193 TransitionAdapter.prototype.toString = function() { 194 var units = (this.units||''); 195 return APE.getFunctionName(this.constructor) + ": " + this.prop 196 + ", " + this.fromValue.toString() + units 197 + " \u2014 " + this.toValue.toString() + units; 198 }; 199 200 function ColorTransitionAdapter(prop, fromValue, toValue) { 201 if(!ColorRGB) ColorRGB = APE.color.ColorRGB; 202 var f = ColorRGB.fromString(fromValue), 203 t = toValue = ColorRGB.fromString(toValue); 204 205 TransitionAdapter.call(this, prop, f, t); 206 207 // This is where we mix fromValue and toValue, 208 // to avoid the creation of new ColorRGB for each frame. 209 this.currentValue = new ColorRGB(); 210 } 211 212 APE.extend(ColorTransitionAdapter, TransitionAdapter, { 213 214 /** 215 * Adapter/Strategy interface. 216 * @return {String} rgb string of the blended values. 217 */ 218 blendTo : function(rationalValue) { 219 var c = ColorRGB.blend(this.fromValue, this.toValue, rationalValue, this.currentValue); 220 return c.toString(); 221 } 222 }); 223 224 function LengthTransitionAdapter(prop, fromValue, toValue) { 225 var lengthExp = APE.anim.TransitionAdapterFactory.lengthExp, 226 fromValues = lengthExp.exec(fromValue), 227 toValues = lengthExp.exec(toValue); 228 229 TransitionAdapter.call(this, prop, parseFloat(fromValues[0]), 230 parseFloat(toValues[0]), fromValues[2]); 231 } 232 233 APE.extend(LengthTransitionAdapter, TransitionAdapter); 234 235 LengthTransitionAdapter.prototype.blendTo = function(rationalValue) { 236 var inverse = 1-rationalValue; 237 return ((this.fromValue * inverse) + (this.toValue * rationalValue)) + this.units; 238 }; 239 240 function PositiveLengthTransitionAdapter() { 241 LengthTransitionAdapter.apply(this, arguments); 242 } 243 244 /**@ignore 245 * extends LengthTransitionAdapter 246 */ 247 APE.extend(PositiveLengthTransitionAdapter, LengthTransitionAdapter); 248 249 PositiveLengthTransitionAdapter.prototype.blendTo = function(rationalValue) { 250 var inverse = 1-rationalValue, 251 v = Math.max((this.fromValue * inverse) + (this.toValue * rationalValue), 0) + this.units; 252 return v; 253 }; 254 255 var splitClipExp = /,?\s/, 256 clipExp = /rect\(([^\)]+)\)/, 257 zeroToPxExp = /0(\s|\))/g; 258 259 function ClipTransitionAdapter(prop, fromValue, toValue) { 260 this.prop = "clip"; 261 262 var fromString = clipExp.exec(fromValue.replace(zeroToPxExp, "0px$1"))[1], 263 toString = clipExp.exec(toValue.replace(zeroToPxExp, "0px$1"))[1]; 264 265 this.fromValues = fromString.split(splitClipExp); 266 this.toValues = toString.split(splitClipExp); 267 this.clips = []; 268 this.values = []; 269 this.init(); 270 } 271 272 ClipTransitionAdapter.prototype = { 273 init : function() { 274 for(var i = 0, f, t; i < 4; i++) { 275 f = this.fromValues[i], 276 t = this.toValues[i]; 277 if(f == "0") f = "0px"; 278 if(t == "0") t = "0px"; 279 this.clips[i] = new LengthTransitionAdapter(this.prop, f, t); 280 } 281 }, 282 283 blendTo : function(rationalValue) { 284 for(var i = 0; i < 4; i++) 285 this.values[i] = this.clips[i].blendTo(rationalValue); 286 return "rect("+this.values.join(" ")+")"; 287 }, 288 289 toString : function() { 290 return "ClipTransitionAdapter: \n" + this.clips.join(" \n"); 291 } 292 }; 293 294 /**@ignore*/ 295 function OpacityTransitionAdapter(prop, fromValue, toValue) { 296 TransitionAdapter.call(this, prop, parseFloat(fromValue), parseFloat(toValue)); 297 } 298 299 APE.extend(OpacityTransitionAdapter, TransitionAdapter); 300 OpacityTransitionAdapter.prototype.blendTo = function(rationalValue) { 301 var inverse = 1-rationalValue, 302 v = Math.max((this.fromValue * inverse) + (this.toValue * rationalValue), 0); 303 return v; 304 }; 305 306 /**@ignore*/ 307 function FilterTransitionAdapter(prop, fromValue, toValue) { 308 TransitionAdapter.call(this, "filter", fromValue, toValue); 309 } 310 311 /**@ignore 312 * constructor FilterTransitionAdapter 313 * performs adapter service using IE filters crap. 314 */ 315 APE.extend(FilterTransitionAdapter, TransitionAdapter); 316 FilterTransitionAdapter.prototype.blendTo = function(rationalValue) { 317 var inverse = 1-rationalValue, 318 v = Math.abs((this.fromValue * inverse) + (this.toValue * rationalValue), 0); 319 return"alpha(opacity=" + Math.abs(v * 100) + ")"; 320 }; 321 322 /** Useful for z-index, font-weight */ 323 function FontWeightTransitionAdapter(prop, fromValue, toValue) { 324 TransitionAdapter.call(this, prop, parseInt(fromValue), parseInt(toValue)); 325 } 326 327 APE.extend(FontWeightTransitionAdapter, TransitionAdapter); 328 FontWeightTransitionAdapter.prototype.blendTo = function(rationalValue) { 329 var inverse = 1-rationalValue, 330 v = (((this.fromValue * inverse) + (this.toValue * rationalValue))/100 << 0) * 100; 331 if(v < 100) return 100; 332 if(v > 900) return 900; 333 return v; 334 }; 335 336 /**@ignore 337 * constructor ThresholdTransitionAdapter 338 * This is sort of a Null Object. It does implement blendTo, 339 * setting the toValue at the very end. 340 */ 341 function ThresholdTransitionAdapter(prop, fromValue, toValue) { 342 TransitionAdapter.call(this, prop, fromValue, toValue); 343 } 344 345 APE.extend(ThresholdTransitionAdapter, TransitionAdapter); 346 ThresholdTransitionAdapter.prototype.blendTo = function(rationalValue) { 347 if(rationalValue == 1) 348 return this.toValue; 349 return this.fromValue; 350 }; 351 352 function ImmediateThresholdTransitionAdapter(prop, fromValue, toValue) { 353 TransitionAdapter.call(this, prop, fromValue, toValue); 354 } 355 356 APE.extend(ImmediateThresholdTransitionAdapter, TransitionAdapter); 357 ImmediateThresholdTransitionAdapter.prototype.blendTo = function(rationalValue) { 358 if(rationalValue == 0) { 359 return this.fromValue; 360 } 361 return this.toValue; 362 }; 363 })();