1 /** 2 * jx.nfx.js 3 * do animation of htmlElements 4 * CSS3 animation & normal are compatible 5 * 6 */ 7 8 /* FX */ 9 Jx().$package(function(J){ 10 J.fx = J.fx || {}; 11 12 !function(context, doc, win) { 13 14 var html = doc.documentElement, 15 rgbOhex = /^rgb\(|#/, 16 relVal = /^([+\-])=([\d\.]+)/, 17 numUnit = /^(?:[\+\-]=)?\d+(?:\.\d+)?(%|in|cm|mm|em|ex|pt|pc|px)$/, 18 rotate = /rotate\(((?:[+\-]=)?([\-\d\.]+))deg\)/, 19 scale = /scale\(((?:[+\-]=)?([\d\.]+))\)/, 20 skew = /skew\(((?:[+\-]=)?([\-\d\.]+))deg, ?((?:[+\-]=)?([\-\d\.]+))deg\)/, 21 translate = /translate\(((?:[+\-]=)?([\-\d\.]+))px, ?((?:[+\-]=)?([\-\d\.]+))px\)/, 22 // these elements do not require 'px' 23 unitless = { 24 lineHeight: 1, 25 zoom: 1, 26 zIndex: 1, 27 opacity: 1, 28 transform: 1 29 }, 30 // which property name does this browser use for transform 31 /** 32 * @ignore 33 */ 34 transform = function() { 35 var styles = doc.createElement('a').style, 36 props = ['webkitTransform', 'MozTransform', 'OTransform', 'msTransform', 'Transform'], 37 i 38 for (i = 0; i < props.length; i++) { 39 if (props[i] in styles) return props[i] 40 } 41 } (), 42 43 // does this browser support the opacity property? 44 /** 45 * @ignore 46 */ 47 opasity = function() { 48 return typeof doc.createElement('a').style.opacity !== 'undefined' 49 } (), 50 51 // initial style is determined by the elements themselves 52 getStyle = doc.defaultView && doc.defaultView.getComputedStyle ? 53 function(el, property) { 54 property = property == 'transform' ? transform: property 55 var value = null, 56 computed = doc.defaultView.getComputedStyle(el, '') 57 computed && (value = computed[camelize(property)]) 58 return el.style[property] || value 59 }: html.currentStyle ? 60 61 function(el, property) { 62 property = camelize(property) 63 64 if (property == 'opacity') { 65 var val = 100 66 try { 67 val = el.filters['DXImageTransform.Microsoft.Alpha'].opacity 68 } catch(e1) { 69 try { 70 val = el.filters('alpha').opacity 71 } catch(e2) {} 72 } 73 return val / 100 74 } 75 var value = el.currentStyle ? el.currentStyle[property] : null 76 return el.style[property] || value 77 }: function(el, property) { 78 return el.style[camelize(property)] 79 }, 80 /** 81 * @ignore 82 */ 83 frame = function() { 84 // native animation frames 85 // http://webstuff.nfshost.com/anim-timing/Overview.html 86 // http://dev.chromium.org/developers/design-documents/requestanimationframe-implementation 87 return win.requestAnimationFrame || win.webkitRequestAnimationFrame || win.mozRequestAnimationFrame || win.oRequestAnimationFrame || win.msRequestAnimationFrame || 88 function(callback) { 89 win.setTimeout(function() { 90 callback( + new Date()) 91 }, 92 10) 93 } 94 } () 95 96 function parseTransform(style, base) { 97 var values = {}, m; 98 if (m = style.match(rotate)) values.rotate = by(m[1], base ? base.rotate: null); 99 if (m = style.match(scale)) values.scale = by(m[1], base ? base.scale: null); 100 if (m = style.match(skew)) { 101 values.skewx = by(m[1], base ? base.skewx: null); 102 values.skewy = by(m[3], base ? base.skewy: null); 103 } 104 if (m = style.match(translate)) { 105 values.translatex = by(m[1], base ? base.translatex: null); 106 values.translatey = by(m[3], base ? base.translatey: null); 107 } 108 return values; 109 } 110 111 function formatTransform(v) { 112 var s = '' 113 if ('rotate' in v) s += 'rotate(' + v.rotate + 'deg) ' 114 if ('scale' in v) s += 'scale(' + v.scale + ') ' 115 if ('translatex' in v) s += 'translate(' + v.translatex + 'px,' + v.translatey + 'px) ' 116 if ('skewx' in v) s += 'skew(' + v.skewx + 'deg,' + v.skewy + 'deg)' 117 return s 118 } 119 120 function rgb(r, g, b) { 121 return '#' + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1) 122 } 123 124 // convert rgb and short hex to long hex 125 function toHex(c) { 126 var m = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(c) 127 return (m ? rgb(m[1], m[2], m[3]) : c).replace(/#(\w)(\w)(\w)$/, '#$1$1$2$2$3$3') // short to long 128 } 129 130 // change font-size => fontSize etc. 131 function camelize(s) { 132 return s.replace(/-(.)/g, 133 function(m, m1) { 134 return m1.toUpperCase() 135 }) 136 } 137 138 function fun(f) { 139 return typeof f == 'function' 140 } 141 142 /** 143 * Core tween method that requests each frame 144 * @param duration: time in milliseconds. defaults to 1000 145 * @param fn: tween frame callback function receiving 'position' 146 * @param done {optional}: complete callback function 147 * @param ease {optional}: easing method. defaults to easeOut 148 * @param from {optional}: integer to start from 149 * @param to {optional}: integer to end at 150 * @returns method to stop the animation 151 */ 152 function tween(duration, fn, done, ease, from, to) { 153 ease = ease || 154 function(t) { 155 // default to a pleasant-to-the-eye easeOut (like native animations) 156 return Math.sin(t * Math.PI / 2) 157 } 158 var time = duration || 1000, 159 diff = to - from, 160 start = +new Date(), 161 stop = 0, 162 end = 0 163 frame(run) 164 165 function run(t) { 166 var delta = t - start 167 if (delta > time || stop) { 168 to = isFinite(to) ? to: 1 169 stop ? end && fn(to) : fn(to) 170 return done && done() 171 } 172 // if you don't specify a 'to' you can use tween as a generic delta tweener 173 // cool, eh? 174 isFinite(to) ? fn((diff * ease(delta / time)) + from) : fn(ease(delta / time)) 175 frame(run) 176 } 177 return { 178 stop: function(jump) { 179 stop = 1 180 end = jump // jump to end of animation? 181 } 182 } 183 } 184 185 /** 186 * generic bezier method for animating x|y coordinates 187 * minimum of 2 points required (start and end). 188 * first point start, last point end 189 * additional control points are optional (but why else would you use this anyway ;) 190 * @param points: array containing control points 191 [[0, 0], [100, 200], [200, 100]] 192 * @param pos: current be(tween) position represented as float 0 - 1 193 * @return [x, y] 194 */ 195 function bezier(points, pos) { 196 var n = points.length, 197 r = [], 198 i, 199 j 200 for (i = 0; i < n; ++i) { 201 r[i] = [points[i][0], points[i][1]] 202 } 203 for (j = 1; j < n; ++j) { 204 for (i = 0; i < n - j; ++i) { 205 r[i][0] = (1 - pos) * r[i][0] + pos * r[parseInt(i + 1, 10)][0] 206 r[i][1] = (1 - pos) * r[i][1] + pos * r[parseInt(i + 1, 10)][1] 207 } 208 } 209 return [r[0][0], r[0][1]] 210 } 211 212 // this gets you the next hex in line according to a 'position' 213 function nextColor(pos, start, finish) { 214 var r = [], 215 i, 216 e, 217 from, 218 to; 219 for (i = 0; i < 6; i++) { 220 from = Math.min(15, parseInt(start.charAt(i), 16)) 221 to = Math.min(15, parseInt(finish.charAt(i), 16)) 222 e = Math.floor((to - from) * pos + from) 223 e = e > 15 ? 15 : e < 0 ? 0 : e 224 r[i] = e.toString(16) 225 } 226 return '#' + r.join('') 227 } 228 229 // this retreives the frame value within a sequence 230 function getTweenVal(pos, units, begin, end, k, i, v) { 231 if (k == 'transform') { 232 v = {} 233 for (var t in begin[i][k]) { 234 v[t] = (t in end[i][k]) ? Math.round(((end[i][k][t] - begin[i][k][t]) * pos + begin[i][k][t]) * 1000) / 1000 : begin[i][k][t] 235 } 236 return v 237 } else if (typeof begin[i][k] == 'string') { 238 return nextColor(pos, begin[i][k], end[i][k]) 239 } else { 240 // round so we don't get crazy long floats 241 v = Math.round(((end[i][k] - begin[i][k]) * pos + begin[i][k]) * 1000) / 1000 242 // some css properties don't require a unit (like zIndex, lineHeight, opacity) 243 if (! (k in unitless)) v += units[i][k] || 'px' 244 return v 245 } 246 } 247 248 // support for relative movement via '+=n' or '-=n' 249 function by(val, start, m, r, i) { 250 return (m = relVal.exec(val)) ? (i = parseFloat(m[2])) && (start + (m[1] == '+' ? 1 : -1) * i) : parseFloat(val) 251 } 252 253 /** 254 * fx: 255 * @param element(s): HTMLElement(s) 256 * @param options: mixed bag between CSS Style properties & animation options 257 * - {n} CSS properties|values 258 * - value can be strings, integers, 259 * - or callback function that receives element to be animated. method must return value to be tweened 260 * - relative animations start with += or -= followed by integer 261 * - duration: time in ms - defaults to 1000(ms) 262 * - easing: a transition method - defaults to an 'easeOut' algorithm 263 * - complete: a callback method for when all elements have finished 264 * - bezier: array of arrays containing x|y coordinates that define the bezier points. defaults to none 265 * - this may also be a function that receives element to be animated. it must return a value 266 */ 267 function animate(elements, options) { 268 var els = elements ? (els = isFinite(elements.length) ? elements: [elements]) : [], 269 i, 270 complete = options.complete, 271 duration = options.duration, 272 ease = options.easing, 273 points = options.bezier, 274 begin = [], 275 end = [], 276 units = [], 277 bez = [], 278 originalLeft, 279 originalTop 280 281 delete options.complete; 282 delete options.duration; 283 delete options.easing; 284 delete options.bezier; 285 286 if (points) { 287 // remember the original values for top|left 288 originalLeft = options.left; 289 originalTop = options.top; 290 delete options.right; 291 delete options.bottom; 292 delete options.left; 293 delete options.top; 294 } 295 296 for (i = els.length; i--;) { 297 298 // record beginning and end states to calculate positions 299 begin[i] = {} 300 end[i] = {} 301 units[i] = {} 302 303 // are we 'moving'? 304 if (points) { 305 306 var left = getStyle(els[i], 'left'), 307 top = getStyle(els[i], 'top'), 308 xy = [by(fun(originalLeft) ? originalLeft(els[i]) : originalLeft || 0, parseFloat(left)), by(fun(originalTop) ? originalTop(els[i]) : originalTop || 0, parseFloat(top))] 309 310 bez[i] = fun(points) ? points(els[i], xy) : points 311 bez[i].push(xy) 312 bez[i].unshift([ 313 parseInt(left, 10), parseInt(top, 10)]) 314 } 315 316 for (var k in options) { 317 var v = getStyle(els[i], k), 318 unit, 319 tmp = fun(options[k]) ? options[k](els[i]) : options[k] 320 if (typeof tmp == 'string' && rgbOhex.test(tmp) && !rgbOhex.test(v)) { 321 delete options[k]; // remove key :( 322 continue; // cannot animate colors like 'orange' or 'transparent' 323 // only #xxx, #xxxxxx, rgb(n,n,n) 324 } 325 326 begin[i][k] = k == 'transform' ? parseTransform(v) : typeof tmp == 'string' && rgbOhex.test(tmp) ? toHex(v).slice(1) : parseFloat(v) 327 end[i][k] = k == 'transform' ? parseTransform(tmp, begin[i][k]) : typeof tmp == 'string' && tmp.charAt(0) == '#' ? toHex(tmp).slice(1) : by(tmp, parseFloat(v)); 328 // record original unit 329 (typeof tmp == 'string') && (unit = tmp.match(numUnit)) && (units[i][k] = unit[1]) 330 } 331 } 332 // ONE TWEEN TO RULE THEM ALL 333 return tween(duration, 334 function(pos, v, xy) { 335 // normally not a fan of optimizing for() loops, but we want something 336 // fast for animating 337 for (i = els.length; i--;) { 338 if (points) { 339 xy = bezier(bez[i], pos) 340 els[i].style.left = xy[0] + 'px' 341 els[i].style.top = xy[1] + 'px' 342 } 343 for (var k in options) { 344 v = getTweenVal(pos, units, begin, end, k, i) 345 k == 'transform' ? els[i].style[transform] = formatTransform(v) : k == 'opacity' && !opasity ? (els[i].style.filter = 'alpha(opacity=' + (v * 100) + ')') : (els[i].style[camelize(k)] = v) 346 } 347 } 348 }, 349 complete, ease) 350 } 351 352 // expose static methods 353 animate.tween = tween; 354 animate.getStyle = getStyle; 355 animate.bezier = bezier; 356 animate.transform = transform; 357 animate.parseTransform = parseTransform; 358 animate.formatTransform = formatTransform; 359 360 if (typeof module !== 'undefined') module.exports = animate; 361 else context['animate'] = animate; 362 363 } (J.fx || this, document, window) 364 365 }); 366 /* easings */ 367 Jx().$package(function(J){ 368 J.fx.easings = J.fx.easings || {}; 369 370 var easings = { 371 easeOut: function (t) { 372 return Math.sin(t * Math.PI / 2); 373 } 374 375 , easeOutStrong: function (t) { 376 return (t == 1) ? 1 : 1 - Math.pow(2, -10 * t); 377 } 378 379 , easeIn: function (t) { 380 return t * t; 381 } 382 383 , easeInStrong: function (t) { 384 return (t == 0) ? 0 : Math.pow(2, 10 * (t - 1)); 385 } 386 387 , easeOutBounce: function(pos) { 388 if ((pos) < (1/2.75)) { 389 return (7.5625*pos*pos); 390 } else if (pos < (2/2.75)) { 391 return (7.5625*(pos-=(1.5/2.75))*pos + .75); 392 } else if (pos < (2.5/2.75)) { 393 return (7.5625*(pos-=(2.25/2.75))*pos + .9375); 394 } else { 395 return (7.5625*(pos-=(2.625/2.75))*pos + .984375); 396 } 397 } 398 399 , easeInBack: function(pos){ 400 var s = 1.70158; 401 return (pos)*pos*((s+1)*pos - s); 402 } 403 404 , easeOutBack: function(pos){ 405 var s = 1.70158; 406 return (pos=pos-1)*pos*((s+1)*pos + s) + 1; 407 } 408 409 , bounce: function (t) { 410 if (t < (1 / 2.75)) { 411 return 7.5625 * t * t; 412 } 413 if (t < (2 / 2.75)) { 414 return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; 415 } 416 if (t < (2.5 / 2.75)) { 417 return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; 418 } 419 return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; 420 } 421 422 , bouncePast: function (pos) { 423 if (pos < (1 / 2.75)) { 424 return (7.5625 * pos * pos); 425 } else if (pos < (2 / 2.75)) { 426 return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75); 427 } else if (pos < (2.5 / 2.75)) { 428 return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375); 429 } else { 430 return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375); 431 } 432 } 433 434 , swingTo: function(pos) { 435 var s = 1.70158; 436 return (pos -= 1) * pos * ((s + 1) * pos + s) + 1; 437 } 438 439 , swingFrom: function (pos) { 440 var s = 1.70158; 441 return pos * pos * ((s + 1) * pos - s); 442 } 443 444 , elastic: function(pos) { 445 return -1 * Math.pow(4, -8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1; 446 } 447 448 , spring: function(pos) { 449 return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 450 } 451 452 , blink: function(pos, blinks) { 453 return Math.round(pos*(blinks||5)) % 2; 454 } 455 456 , pulse: function(pos, pulses) { 457 return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; 458 } 459 460 , wobble: function(pos) { 461 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; 462 } 463 464 , sinusoidal: function(pos) { 465 return (-Math.cos(pos*Math.PI)/2) + 0.5; 466 } 467 468 , flicker: function(pos) { 469 var pos = pos + (Math.random()-0.5)/5; 470 return easings.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos); 471 } 472 473 , mirror: function(pos) { 474 if (pos < 0.5) 475 return easings.sinusoidal(pos*2); 476 else 477 return easings.sinusoidal(1-(pos-0.5)*2); 478 } 479 480 }; 481 482 //extend 483 var extend = J.extend || function (target, source) { 484 for (var k in source) { 485 target[k] = source[k]; 486 } 487 return target; 488 } 489 extend(J.fx.easings, easings) 490 491 }); 492 493