effects.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144
  1. /***
  2. * Excerpted from "Agile Web Development with Rails",
  3. * published by The Pragmatic Bookshelf.
  4. * Copyrights apply to this code. It may not be used to create training material,
  5. * courses, books, articles, and the like. Contact us if you are in doubt.
  6. * We make no guarantees that this code is fit for any purpose.
  7. * Visit http://www.pragmaticprogrammer.com/titles/rails4 for more book information.
  8. ***/
  9. /***
  10. * Excerpted from "Agile Web Development with Rails, 4rd Ed.",
  11. * published by The Pragmatic Bookshelf.
  12. * Copyrights apply to this code. It may not be used to create training material,
  13. * courses, books, articles, and the like. Contact us if you are in doubt.
  14. * We make no guarantees that this code is fit for any purpose.
  15. * Visit http://www.pragmaticprogrammer.com/titles/rails4 for more book information.
  16. ***/
  17. // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  18. // Contributors:
  19. // Justin Palmer (http://encytemedia.com/)
  20. // Mark Pilgrim (http://diveintomark.org/)
  21. // Martin Bialasinki
  22. //
  23. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  24. // For details, see the script.aculo.us web site: http://script.aculo.us/
  25. // converts rgb() and #xxx to #xxxxxx format,
  26. // returns self (or first argument) if not convertable
  27. String.prototype.parseColor = function() {
  28. var color = '#';
  29. if (this.slice(0,4) == 'rgb(') {
  30. var cols = this.slice(4,this.length-1).split(',');
  31. var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
  32. } else {
  33. if (this.slice(0,1) == '#') {
  34. if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
  35. if (this.length==7) color = this.toLowerCase();
  36. }
  37. }
  38. return (color.length==7 ? color : (arguments[0] || this));
  39. };
  40. /*--------------------------------------------------------------------------*/
  41. Element.collectTextNodes = function(element) {
  42. return $A($(element).childNodes).collect( function(node) {
  43. return (node.nodeType==3 ? node.nodeValue :
  44. (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  45. }).flatten().join('');
  46. };
  47. Element.collectTextNodesIgnoreClass = function(element, className) {
  48. return $A($(element).childNodes).collect( function(node) {
  49. return (node.nodeType==3 ? node.nodeValue :
  50. ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
  51. Element.collectTextNodesIgnoreClass(node, className) : ''));
  52. }).flatten().join('');
  53. };
  54. Element.setContentZoom = function(element, percent) {
  55. element = $(element);
  56. element.setStyle({fontSize: (percent/100) + 'em'});
  57. if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  58. return element;
  59. };
  60. Element.getInlineOpacity = function(element){
  61. return $(element).style.opacity || '';
  62. };
  63. Element.forceRerendering = function(element) {
  64. try {
  65. element = $(element);
  66. var n = document.createTextNode(' ');
  67. element.appendChild(n);
  68. element.removeChild(n);
  69. } catch(e) { }
  70. };
  71. /*--------------------------------------------------------------------------*/
  72. var Effect = {
  73. _elementDoesNotExistError: {
  74. name: 'ElementDoesNotExistError',
  75. message: 'The specified DOM element does not exist, but is required for this effect to operate'
  76. },
  77. Transitions: {
  78. linear: Prototype.K,
  79. sinoidal: function(pos) {
  80. return (-Math.cos(pos*Math.PI)/2) + .5;
  81. },
  82. reverse: function(pos) {
  83. return 1-pos;
  84. },
  85. flicker: function(pos) {
  86. var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
  87. return pos > 1 ? 1 : pos;
  88. },
  89. wobble: function(pos) {
  90. return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
  91. },
  92. pulse: function(pos, pulses) {
  93. return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
  94. },
  95. spring: function(pos) {
  96. return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
  97. },
  98. none: function(pos) {
  99. return 0;
  100. },
  101. full: function(pos) {
  102. return 1;
  103. }
  104. },
  105. DefaultOptions: {
  106. duration: 1.0, // seconds
  107. fps: 100, // 100= assume 66fps max.
  108. sync: false, // true for combining
  109. from: 0.0,
  110. to: 1.0,
  111. delay: 0.0,
  112. queue: 'parallel'
  113. },
  114. tagifyText: function(element) {
  115. var tagifyStyle = 'position:relative';
  116. if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
  117. element = $(element);
  118. $A(element.childNodes).each( function(child) {
  119. if (child.nodeType==3) {
  120. child.nodeValue.toArray().each( function(character) {
  121. element.insertBefore(
  122. new Element('span', {style: tagifyStyle}).update(
  123. character == ' ' ? String.fromCharCode(160) : character),
  124. child);
  125. });
  126. Element.remove(child);
  127. }
  128. });
  129. },
  130. multiple: function(element, effect) {
  131. var elements;
  132. if (((typeof element == 'object') ||
  133. Object.isFunction(element)) &&
  134. (element.length))
  135. elements = element;
  136. else
  137. elements = $(element).childNodes;
  138. var options = Object.extend({
  139. speed: 0.1,
  140. delay: 0.0
  141. }, arguments[2] || { });
  142. var masterDelay = options.delay;
  143. $A(elements).each( function(element, index) {
  144. new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
  145. });
  146. },
  147. PAIRS: {
  148. 'slide': ['SlideDown','SlideUp'],
  149. 'blind': ['BlindDown','BlindUp'],
  150. 'appear': ['Appear','Fade']
  151. },
  152. toggle: function(element, effect) {
  153. element = $(element);
  154. effect = (effect || 'appear').toLowerCase();
  155. var options = Object.extend({
  156. queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
  157. }, arguments[2] || { });
  158. Effect[element.visible() ?
  159. Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  160. }
  161. };
  162. Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
  163. /* ------------- core effects ------------- */
  164. Effect.ScopedQueue = Class.create(Enumerable, {
  165. initialize: function() {
  166. this.effects = [];
  167. this.interval = null;
  168. },
  169. _each: function(iterator) {
  170. this.effects._each(iterator);
  171. },
  172. add: function(effect) {
  173. var timestamp = new Date().getTime();
  174. var position = Object.isString(effect.options.queue) ?
  175. effect.options.queue : effect.options.queue.position;
  176. switch(position) {
  177. case 'front':
  178. // move unstarted effects after this effect
  179. this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
  180. e.startOn += effect.finishOn;
  181. e.finishOn += effect.finishOn;
  182. });
  183. break;
  184. case 'with-last':
  185. timestamp = this.effects.pluck('startOn').max() || timestamp;
  186. break;
  187. case 'end':
  188. // start effect after last queued effect has finished
  189. timestamp = this.effects.pluck('finishOn').max() || timestamp;
  190. break;
  191. }
  192. effect.startOn += timestamp;
  193. effect.finishOn += timestamp;
  194. if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
  195. this.effects.push(effect);
  196. if (!this.interval)
  197. this.interval = setInterval(this.loop.bind(this), 15);
  198. },
  199. remove: function(effect) {
  200. this.effects = this.effects.reject(function(e) { return e==effect });
  201. if (this.effects.length == 0) {
  202. clearInterval(this.interval);
  203. this.interval = null;
  204. }
  205. },
  206. loop: function() {
  207. var timePos = new Date().getTime();
  208. for(var i=0, len=this.effects.length;i<len;i++)
  209. this.effects[i] && this.effects[i].loop(timePos);
  210. }
  211. });
  212. Effect.Queues = {
  213. instances: $H(),
  214. get: function(queueName) {
  215. if (!Object.isString(queueName)) return queueName;
  216. return this.instances.get(queueName) ||
  217. this.instances.set(queueName, new Effect.ScopedQueue());
  218. }
  219. };
  220. Effect.Queue = Effect.Queues.get('global');
  221. Effect.Base = Class.create({
  222. position: null,
  223. start: function(options) {
  224. function codeForEvent(options,eventName){
  225. return (
  226. (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
  227. (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
  228. );
  229. }
  230. if (options && options.transition === false) options.transition = Effect.Transitions.linear;
  231. this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
  232. this.currentFrame = 0;
  233. this.state = 'idle';
  234. this.startOn = this.options.delay*1000;
  235. this.finishOn = this.startOn+(this.options.duration*1000);
  236. this.fromToDelta = this.options.to-this.options.from;
  237. this.totalTime = this.finishOn-this.startOn;
  238. this.totalFrames = this.options.fps*this.options.duration;
  239. this.render = (function() {
  240. function dispatch(effect, eventName) {
  241. if (effect.options[eventName + 'Internal'])
  242. effect.options[eventName + 'Internal'](effect);
  243. if (effect.options[eventName])
  244. effect.options[eventName](effect);
  245. }
  246. return function(pos) {
  247. if (this.state === "idle") {
  248. this.state = "running";
  249. dispatch(this, 'beforeSetup');
  250. if (this.setup) this.setup();
  251. dispatch(this, 'afterSetup');
  252. }
  253. if (this.state === "running") {
  254. pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
  255. this.position = pos;
  256. dispatch(this, 'beforeUpdate');
  257. if (this.update) this.update(pos);
  258. dispatch(this, 'afterUpdate');
  259. }
  260. };
  261. })();
  262. this.event('beforeStart');
  263. if (!this.options.sync)
  264. Effect.Queues.get(Object.isString(this.options.queue) ?
  265. 'global' : this.options.queue.scope).add(this);
  266. },
  267. loop: function(timePos) {
  268. if (timePos >= this.startOn) {
  269. if (timePos >= this.finishOn) {
  270. this.render(1.0);
  271. this.cancel();
  272. this.event('beforeFinish');
  273. if (this.finish) this.finish();
  274. this.event('afterFinish');
  275. return;
  276. }
  277. var pos = (timePos - this.startOn) / this.totalTime,
  278. frame = (pos * this.totalFrames).round();
  279. if (frame > this.currentFrame) {
  280. this.render(pos);
  281. this.currentFrame = frame;
  282. }
  283. }
  284. },
  285. cancel: function() {
  286. if (!this.options.sync)
  287. Effect.Queues.get(Object.isString(this.options.queue) ?
  288. 'global' : this.options.queue.scope).remove(this);
  289. this.state = 'finished';
  290. },
  291. event: function(eventName) {
  292. if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
  293. if (this.options[eventName]) this.options[eventName](this);
  294. },
  295. inspect: function() {
  296. var data = $H();
  297. for(property in this)
  298. if (!Object.isFunction(this[property])) data.set(property, this[property]);
  299. return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  300. }
  301. });
  302. Effect.Parallel = Class.create(Effect.Base, {
  303. initialize: function(effects) {
  304. this.effects = effects || [];
  305. this.start(arguments[1]);
  306. },
  307. update: function(position) {
  308. this.effects.invoke('render', position);
  309. },
  310. finish: function(position) {
  311. this.effects.each( function(effect) {
  312. effect.render(1.0);
  313. effect.cancel();
  314. effect.event('beforeFinish');
  315. if (effect.finish) effect.finish(position);
  316. effect.event('afterFinish');
  317. });
  318. }
  319. });
  320. Effect.Tween = Class.create(Effect.Base, {
  321. initialize: function(object, from, to) {
  322. object = Object.isString(object) ? $(object) : object;
  323. var args = $A(arguments), method = args.last(),
  324. options = args.length == 5 ? args[3] : null;
  325. this.method = Object.isFunction(method) ? method.bind(object) :
  326. Object.isFunction(object[method]) ? object[method].bind(object) :
  327. function(value) { object[method] = value };
  328. this.start(Object.extend({ from: from, to: to }, options || { }));
  329. },
  330. update: function(position) {
  331. this.method(position);
  332. }
  333. });
  334. Effect.Event = Class.create(Effect.Base, {
  335. initialize: function() {
  336. this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  337. },
  338. update: Prototype.emptyFunction
  339. });
  340. Effect.Opacity = Class.create(Effect.Base, {
  341. initialize: function(element) {
  342. this.element = $(element);
  343. if (!this.element) throw(Effect._elementDoesNotExistError);
  344. // make this work on IE on elements without 'layout'
  345. if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
  346. this.element.setStyle({zoom: 1});
  347. var options = Object.extend({
  348. from: this.element.getOpacity() || 0.0,
  349. to: 1.0
  350. }, arguments[1] || { });
  351. this.start(options);
  352. },
  353. update: function(position) {
  354. this.element.setOpacity(position);
  355. }
  356. });
  357. Effect.Move = Class.create(Effect.Base, {
  358. initialize: function(element) {
  359. this.element = $(element);
  360. if (!this.element) throw(Effect._elementDoesNotExistError);
  361. var options = Object.extend({
  362. x: 0,
  363. y: 0,
  364. mode: 'relative'
  365. }, arguments[1] || { });
  366. this.start(options);
  367. },
  368. setup: function() {
  369. this.element.makePositioned();
  370. this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
  371. this.originalTop = parseFloat(this.element.getStyle('top') || '0');
  372. if (this.options.mode == 'absolute') {
  373. this.options.x = this.options.x - this.originalLeft;
  374. this.options.y = this.options.y - this.originalTop;
  375. }
  376. },
  377. update: function(position) {
  378. this.element.setStyle({
  379. left: (this.options.x * position + this.originalLeft).round() + 'px',
  380. top: (this.options.y * position + this.originalTop).round() + 'px'
  381. });
  382. }
  383. });
  384. // for backwards compatibility
  385. Effect.MoveBy = function(element, toTop, toLeft) {
  386. return new Effect.Move(element,
  387. Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
  388. };
  389. Effect.Scale = Class.create(Effect.Base, {
  390. initialize: function(element, percent) {
  391. this.element = $(element);
  392. if (!this.element) throw(Effect._elementDoesNotExistError);
  393. var options = Object.extend({
  394. scaleX: true,
  395. scaleY: true,
  396. scaleContent: true,
  397. scaleFromCenter: false,
  398. scaleMode: 'box', // 'box' or 'contents' or { } with provided values
  399. scaleFrom: 100.0,
  400. scaleTo: percent
  401. }, arguments[2] || { });
  402. this.start(options);
  403. },
  404. setup: function() {
  405. this.restoreAfterFinish = this.options.restoreAfterFinish || false;
  406. this.elementPositioning = this.element.getStyle('position');
  407. this.originalStyle = { };
  408. ['top','left','width','height','fontSize'].each( function(k) {
  409. this.originalStyle[k] = this.element.style[k];
  410. }.bind(this));
  411. this.originalTop = this.element.offsetTop;
  412. this.originalLeft = this.element.offsetLeft;
  413. var fontSize = this.element.getStyle('font-size') || '100%';
  414. ['em','px','%','pt'].each( function(fontSizeType) {
  415. if (fontSize.indexOf(fontSizeType)>0) {
  416. this.fontSize = parseFloat(fontSize);
  417. this.fontSizeType = fontSizeType;
  418. }
  419. }.bind(this));
  420. this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
  421. this.dims = null;
  422. if (this.options.scaleMode=='box')
  423. this.dims = [this.element.offsetHeight, this.element.offsetWidth];
  424. if (/^content/.test(this.options.scaleMode))
  425. this.dims = [this.element.scrollHeight, this.element.scrollWidth];
  426. if (!this.dims)
  427. this.dims = [this.options.scaleMode.originalHeight,
  428. this.options.scaleMode.originalWidth];
  429. },
  430. update: function(position) {
  431. var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
  432. if (this.options.scaleContent && this.fontSize)
  433. this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
  434. this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  435. },
  436. finish: function(position) {
  437. if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  438. },
  439. setDimensions: function(height, width) {
  440. var d = { };
  441. if (this.options.scaleX) d.width = width.round() + 'px';
  442. if (this.options.scaleY) d.height = height.round() + 'px';
  443. if (this.options.scaleFromCenter) {
  444. var topd = (height - this.dims[0])/2;
  445. var leftd = (width - this.dims[1])/2;
  446. if (this.elementPositioning == 'absolute') {
  447. if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
  448. if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
  449. } else {
  450. if (this.options.scaleY) d.top = -topd + 'px';
  451. if (this.options.scaleX) d.left = -leftd + 'px';
  452. }
  453. }
  454. this.element.setStyle(d);
  455. }
  456. });
  457. Effect.Highlight = Class.create(Effect.Base, {
  458. initialize: function(element) {
  459. this.element = $(element);
  460. if (!this.element) throw(Effect._elementDoesNotExistError);
  461. var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
  462. this.start(options);
  463. },
  464. setup: function() {
  465. // Prevent executing on elements not in the layout flow
  466. if (this.element.getStyle('display')=='none') { this.cancel(); return; }
  467. // Disable background image during the effect
  468. this.oldStyle = { };
  469. if (!this.options.keepBackgroundImage) {
  470. this.oldStyle.backgroundImage = this.element.getStyle('background-image');
  471. this.element.setStyle({backgroundImage: 'none'});
  472. }
  473. if (!this.options.endcolor)
  474. this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
  475. if (!this.options.restorecolor)
  476. this.options.restorecolor = this.element.getStyle('background-color');
  477. // init color calculations
  478. this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
  479. this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  480. },
  481. update: function(position) {
  482. this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
  483. return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  484. },
  485. finish: function() {
  486. this.element.setStyle(Object.extend(this.oldStyle, {
  487. backgroundColor: this.options.restorecolor
  488. }));
  489. }
  490. });
  491. Effect.ScrollTo = function(element) {
  492. var options = arguments[1] || { },
  493. scrollOffsets = document.viewport.getScrollOffsets(),
  494. elementOffsets = $(element).cumulativeOffset();
  495. if (options.offset) elementOffsets[1] += options.offset;
  496. return new Effect.Tween(null,
  497. scrollOffsets.top,
  498. elementOffsets[1],
  499. options,
  500. function(p){ scrollTo(scrollOffsets.left, p.round()); }
  501. );
  502. };
  503. /* ------------- combination effects ------------- */
  504. Effect.Fade = function(element) {
  505. element = $(element);
  506. var oldOpacity = element.getInlineOpacity();
  507. var options = Object.extend({
  508. from: element.getOpacity() || 1.0,
  509. to: 0.0,
  510. afterFinishInternal: function(effect) {
  511. if (effect.options.to!=0) return;
  512. effect.element.hide().setStyle({opacity: oldOpacity});
  513. }
  514. }, arguments[1] || { });
  515. return new Effect.Opacity(element,options);
  516. };
  517. Effect.Appear = function(element) {
  518. element = $(element);
  519. var options = Object.extend({
  520. from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  521. to: 1.0,
  522. // force Safari to render floated elements properly
  523. afterFinishInternal: function(effect) {
  524. effect.element.forceRerendering();
  525. },
  526. beforeSetup: function(effect) {
  527. effect.element.setOpacity(effect.options.from).show();
  528. }}, arguments[1] || { });
  529. return new Effect.Opacity(element,options);
  530. };
  531. Effect.Puff = function(element) {
  532. element = $(element);
  533. var oldStyle = {
  534. opacity: element.getInlineOpacity(),
  535. position: element.getStyle('position'),
  536. top: element.style.top,
  537. left: element.style.left,
  538. width: element.style.width,
  539. height: element.style.height
  540. };
  541. return new Effect.Parallel(
  542. [ new Effect.Scale(element, 200,
  543. { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
  544. new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
  545. Object.extend({ duration: 1.0,
  546. beforeSetupInternal: function(effect) {
  547. Position.absolutize(effect.effects[0].element);
  548. },
  549. afterFinishInternal: function(effect) {
  550. effect.effects[0].element.hide().setStyle(oldStyle); }
  551. }, arguments[1] || { })
  552. );
  553. };
  554. Effect.BlindUp = function(element) {
  555. element = $(element);
  556. element.makeClipping();
  557. return new Effect.Scale(element, 0,
  558. Object.extend({ scaleContent: false,
  559. scaleX: false,
  560. restoreAfterFinish: true,
  561. afterFinishInternal: function(effect) {
  562. effect.element.hide().undoClipping();
  563. }
  564. }, arguments[1] || { })
  565. );
  566. };
  567. Effect.BlindDown = function(element) {
  568. element = $(element);
  569. var elementDimensions = element.getDimensions();
  570. return new Effect.Scale(element, 100, Object.extend({
  571. scaleContent: false,
  572. scaleX: false,
  573. scaleFrom: 0,
  574. scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  575. restoreAfterFinish: true,
  576. afterSetup: function(effect) {
  577. effect.element.makeClipping().setStyle({height: '0px'}).show();
  578. },
  579. afterFinishInternal: function(effect) {
  580. effect.element.undoClipping();
  581. }
  582. }, arguments[1] || { }));
  583. };
  584. Effect.SwitchOff = function(element) {
  585. element = $(element);
  586. var oldOpacity = element.getInlineOpacity();
  587. return new Effect.Appear(element, Object.extend({
  588. duration: 0.4,
  589. from: 0,
  590. transition: Effect.Transitions.flicker,
  591. afterFinishInternal: function(effect) {
  592. new Effect.Scale(effect.element, 1, {
  593. duration: 0.3, scaleFromCenter: true,
  594. scaleX: false, scaleContent: false, restoreAfterFinish: true,
  595. beforeSetup: function(effect) {
  596. effect.element.makePositioned().makeClipping();
  597. },
  598. afterFinishInternal: function(effect) {
  599. effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
  600. }
  601. });
  602. }
  603. }, arguments[1] || { }));
  604. };
  605. Effect.DropOut = function(element) {
  606. element = $(element);
  607. var oldStyle = {
  608. top: element.getStyle('top'),
  609. left: element.getStyle('left'),
  610. opacity: element.getInlineOpacity() };
  611. return new Effect.Parallel(
  612. [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
  613. new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
  614. Object.extend(
  615. { duration: 0.5,
  616. beforeSetup: function(effect) {
  617. effect.effects[0].element.makePositioned();
  618. },
  619. afterFinishInternal: function(effect) {
  620. effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
  621. }
  622. }, arguments[1] || { }));
  623. };
  624. Effect.Shake = function(element) {
  625. element = $(element);
  626. var options = Object.extend({
  627. distance: 20,
  628. duration: 0.5
  629. }, arguments[1] || {});
  630. var distance = parseFloat(options.distance);
  631. var split = parseFloat(options.duration) / 10.0;
  632. var oldStyle = {
  633. top: element.getStyle('top'),
  634. left: element.getStyle('left') };
  635. return new Effect.Move(element,
  636. { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
  637. new Effect.Move(effect.element,
  638. { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  639. new Effect.Move(effect.element,
  640. { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  641. new Effect.Move(effect.element,
  642. { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  643. new Effect.Move(effect.element,
  644. { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  645. new Effect.Move(effect.element,
  646. { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
  647. effect.element.undoPositioned().setStyle(oldStyle);
  648. }}); }}); }}); }}); }}); }});
  649. };
  650. Effect.SlideDown = function(element) {
  651. element = $(element).cleanWhitespace();
  652. // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  653. var oldInnerBottom = element.down().getStyle('bottom');
  654. var elementDimensions = element.getDimensions();
  655. return new Effect.Scale(element, 100, Object.extend({
  656. scaleContent: false,
  657. scaleX: false,
  658. scaleFrom: window.opera ? 0 : 1,
  659. scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  660. restoreAfterFinish: true,
  661. afterSetup: function(effect) {
  662. effect.element.makePositioned();
  663. effect.element.down().makePositioned();
  664. if (window.opera) effect.element.setStyle({top: ''});
  665. effect.element.makeClipping().setStyle({height: '0px'}).show();
  666. },
  667. afterUpdateInternal: function(effect) {
  668. effect.element.down().setStyle({bottom:
  669. (effect.dims[0] - effect.element.clientHeight) + 'px' });
  670. },
  671. afterFinishInternal: function(effect) {
  672. effect.element.undoClipping().undoPositioned();
  673. effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
  674. }, arguments[1] || { })
  675. );
  676. };
  677. Effect.SlideUp = function(element) {
  678. element = $(element).cleanWhitespace();
  679. var oldInnerBottom = element.down().getStyle('bottom');
  680. var elementDimensions = element.getDimensions();
  681. return new Effect.Scale(element, window.opera ? 0 : 1,
  682. Object.extend({ scaleContent: false,
  683. scaleX: false,
  684. scaleMode: 'box',
  685. scaleFrom: 100,
  686. scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  687. restoreAfterFinish: true,
  688. afterSetup: function(effect) {
  689. effect.element.makePositioned();
  690. effect.element.down().makePositioned();
  691. if (window.opera) effect.element.setStyle({top: ''});
  692. effect.element.makeClipping().show();
  693. },
  694. afterUpdateInternal: function(effect) {
  695. effect.element.down().setStyle({bottom:
  696. (effect.dims[0] - effect.element.clientHeight) + 'px' });
  697. },
  698. afterFinishInternal: function(effect) {
  699. effect.element.hide().undoClipping().undoPositioned();
  700. effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
  701. }
  702. }, arguments[1] || { })
  703. );
  704. };
  705. // Bug in opera makes the TD containing this element expand for a instance after finish
  706. Effect.Squish = function(element) {
  707. return new Effect.Scale(element, window.opera ? 1 : 0, {
  708. restoreAfterFinish: true,
  709. beforeSetup: function(effect) {
  710. effect.element.makeClipping();
  711. },
  712. afterFinishInternal: function(effect) {
  713. effect.element.hide().undoClipping();
  714. }
  715. });
  716. };
  717. Effect.Grow = function(element) {
  718. element = $(element);
  719. var options = Object.extend({
  720. direction: 'center',
  721. moveTransition: Effect.Transitions.sinoidal,
  722. scaleTransition: Effect.Transitions.sinoidal,
  723. opacityTransition: Effect.Transitions.full
  724. }, arguments[1] || { });
  725. var oldStyle = {
  726. top: element.style.top,
  727. left: element.style.left,
  728. height: element.style.height,
  729. width: element.style.width,
  730. opacity: element.getInlineOpacity() };
  731. var dims = element.getDimensions();
  732. var initialMoveX, initialMoveY;
  733. var moveX, moveY;
  734. switch (options.direction) {
  735. case 'top-left':
  736. initialMoveX = initialMoveY = moveX = moveY = 0;
  737. break;
  738. case 'top-right':
  739. initialMoveX = dims.width;
  740. initialMoveY = moveY = 0;
  741. moveX = -dims.width;
  742. break;
  743. case 'bottom-left':
  744. initialMoveX = moveX = 0;
  745. initialMoveY = dims.height;
  746. moveY = -dims.height;
  747. break;
  748. case 'bottom-right':
  749. initialMoveX = dims.width;
  750. initialMoveY = dims.height;
  751. moveX = -dims.width;
  752. moveY = -dims.height;
  753. break;
  754. case 'center':
  755. initialMoveX = dims.width / 2;
  756. initialMoveY = dims.height / 2;
  757. moveX = -dims.width / 2;
  758. moveY = -dims.height / 2;
  759. break;
  760. }
  761. return new Effect.Move(element, {
  762. x: initialMoveX,
  763. y: initialMoveY,
  764. duration: 0.01,
  765. beforeSetup: function(effect) {
  766. effect.element.hide().makeClipping().makePositioned();
  767. },
  768. afterFinishInternal: function(effect) {
  769. new Effect.Parallel(
  770. [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
  771. new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
  772. new Effect.Scale(effect.element, 100, {
  773. scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
  774. sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
  775. ], Object.extend({
  776. beforeSetup: function(effect) {
  777. effect.effects[0].element.setStyle({height: '0px'}).show();
  778. },
  779. afterFinishInternal: function(effect) {
  780. effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
  781. }
  782. }, options)
  783. );
  784. }
  785. });
  786. };
  787. Effect.Shrink = function(element) {
  788. element = $(element);
  789. var options = Object.extend({
  790. direction: 'center',
  791. moveTransition: Effect.Transitions.sinoidal,
  792. scaleTransition: Effect.Transitions.sinoidal,
  793. opacityTransition: Effect.Transitions.none
  794. }, arguments[1] || { });
  795. var oldStyle = {
  796. top: element.style.top,
  797. left: element.style.left,
  798. height: element.style.height,
  799. width: element.style.width,
  800. opacity: element.getInlineOpacity() };
  801. var dims = element.getDimensions();
  802. var moveX, moveY;
  803. switch (options.direction) {
  804. case 'top-left':
  805. moveX = moveY = 0;
  806. break;
  807. case 'top-right':
  808. moveX = dims.width;
  809. moveY = 0;
  810. break;
  811. case 'bottom-left':
  812. moveX = 0;
  813. moveY = dims.height;
  814. break;
  815. case 'bottom-right':
  816. moveX = dims.width;
  817. moveY = dims.height;
  818. break;
  819. case 'center':
  820. moveX = dims.width / 2;
  821. moveY = dims.height / 2;
  822. break;
  823. }
  824. return new Effect.Parallel(
  825. [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
  826. new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
  827. new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
  828. ], Object.extend({
  829. beforeStartInternal: function(effect) {
  830. effect.effects[0].element.makePositioned().makeClipping();
  831. },
  832. afterFinishInternal: function(effect) {
  833. effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
  834. }, options)
  835. );
  836. };
  837. Effect.Pulsate = function(element) {
  838. element = $(element);
  839. var options = arguments[1] || { },
  840. oldOpacity = element.getInlineOpacity(),
  841. transition = options.transition || Effect.Transitions.linear,
  842. reverser = function(pos){
  843. return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
  844. };
  845. return new Effect.Opacity(element,
  846. Object.extend(Object.extend({ duration: 2.0, from: 0,
  847. afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
  848. }, options), {transition: reverser}));
  849. };
  850. Effect.Fold = function(element) {
  851. element = $(element);
  852. var oldStyle = {
  853. top: element.style.top,
  854. left: element.style.left,
  855. width: element.style.width,
  856. height: element.style.height };
  857. element.makeClipping();
  858. return new Effect.Scale(element, 5, Object.extend({
  859. scaleContent: false,
  860. scaleX: false,
  861. afterFinishInternal: function(effect) {
  862. new Effect.Scale(element, 1, {
  863. scaleContent: false,
  864. scaleY: false,
  865. afterFinishInternal: function(effect) {
  866. effect.element.hide().undoClipping().setStyle(oldStyle);
  867. } });
  868. }}, arguments[1] || { }));
  869. };
  870. Effect.Morph = Class.create(Effect.Base, {
  871. initialize: function(element) {
  872. this.element = $(element);
  873. if (!this.element) throw(Effect._elementDoesNotExistError);
  874. var options = Object.extend({
  875. style: { }
  876. }, arguments[1] || { });
  877. if (!Object.isString(options.style)) this.style = $H(options.style);
  878. else {
  879. if (options.style.include(':'))
  880. this.style = options.style.parseStyle();
  881. else {
  882. this.element.addClassName(options.style);
  883. this.style = $H(this.element.getStyles());
  884. this.element.removeClassName(options.style);
  885. var css = this.element.getStyles();
  886. this.style = this.style.reject(function(style) {
  887. return style.value == css[style.key];
  888. });
  889. options.afterFinishInternal = function(effect) {
  890. effect.element.addClassName(effect.options.style);
  891. effect.transforms.each(function(transform) {
  892. effect.element.style[transform.style] = '';
  893. });
  894. };
  895. }
  896. }
  897. this.start(options);
  898. },
  899. setup: function(){
  900. function parseColor(color){
  901. if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
  902. color = color.parseColor();
  903. return $R(0,2).map(function(i){
  904. return parseInt( color.slice(i*2+1,i*2+3), 16 );
  905. });
  906. }
  907. this.transforms = this.style.map(function(pair){
  908. var property = pair[0], value = pair[1], unit = null;
  909. if (value.parseColor('#zzzzzz') != '#zzzzzz') {
  910. value = value.parseColor();
  911. unit = 'color';
  912. } else if (property == 'opacity') {
  913. value = parseFloat(value);
  914. if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
  915. this.element.setStyle({zoom: 1});
  916. } else if (Element.CSS_LENGTH.test(value)) {
  917. var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
  918. value = parseFloat(components[1]);
  919. unit = (components.length == 3) ? components[2] : null;
  920. }
  921. var originalValue = this.element.getStyle(property);
  922. return {
  923. style: property.camelize(),
  924. originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
  925. targetValue: unit=='color' ? parseColor(value) : value,
  926. unit: unit
  927. };
  928. }.bind(this)).reject(function(transform){
  929. return (
  930. (transform.originalValue == transform.targetValue) ||
  931. (
  932. transform.unit != 'color' &&
  933. (isNaN(transform.originalValue) || isNaN(transform.targetValue))
  934. )
  935. );
  936. });
  937. },
  938. update: function(position) {
  939. var style = { }, transform, i = this.transforms.length;
  940. while(i--)
  941. style[(transform = this.transforms[i]).style] =
  942. transform.unit=='color' ? '#'+
  943. (Math.round(transform.originalValue[0]+
  944. (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
  945. (Math.round(transform.originalValue[1]+
  946. (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
  947. (Math.round(transform.originalValue[2]+
  948. (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
  949. (transform.originalValue +
  950. (transform.targetValue - transform.originalValue) * position).toFixed(3) +
  951. (transform.unit === null ? '' : transform.unit);
  952. this.element.setStyle(style, true);
  953. }
  954. });
  955. Effect.Transform = Class.create({
  956. initialize: function(tracks){
  957. this.tracks = [];
  958. this.options = arguments[1] || { };
  959. this.addTracks(tracks);
  960. },
  961. addTracks: function(tracks){
  962. tracks.each(function(track){
  963. track = $H(track);
  964. var data = track.values().first();
  965. this.tracks.push($H({
  966. ids: track.keys().first(),
  967. effect: Effect.Morph,
  968. options: { style: data }
  969. }));
  970. }.bind(this));
  971. return this;
  972. },
  973. play: function(){
  974. return new Effect.Parallel(
  975. this.tracks.map(function(track){
  976. var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
  977. var elements = [$(ids) || $$(ids)].flatten();
  978. return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
  979. }).flatten(),
  980. this.options
  981. );
  982. }
  983. });
  984. Element.CSS_PROPERTIES = $w(
  985. 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
  986. 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  987. 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  988. 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  989. 'fontSize fontWeight height left letterSpacing lineHeight ' +
  990. 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  991. 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  992. 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  993. 'right textIndent top width wordSpacing zIndex');
  994. Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
  995. String.__parseStyleElement = document.createElement('div');
  996. String.prototype.parseStyle = function(){
  997. var style, styleRules = $H();
  998. if (Prototype.Browser.WebKit)
  999. style = new Element('div',{style:this}).style;
  1000. else {
  1001. String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
  1002. style = String.__parseStyleElement.childNodes[0].style;
  1003. }
  1004. Element.CSS_PROPERTIES.each(function(property){
  1005. if (style[property]) styleRules.set(property, style[property]);
  1006. });
  1007. if (Prototype.Browser.IE && this.include('opacity'))
  1008. styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
  1009. return styleRules;
  1010. };
  1011. if (document.defaultView && document.defaultView.getComputedStyle) {
  1012. Element.getStyles = function(element) {
  1013. var css = document.defaultView.getComputedStyle($(element), null);
  1014. return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
  1015. styles[property] = css[property];
  1016. return styles;
  1017. });
  1018. };
  1019. } else {
  1020. Element.getStyles = function(element) {
  1021. element = $(element);
  1022. var css = element.currentStyle, styles;
  1023. styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
  1024. results[property] = css[property];
  1025. return results;
  1026. });
  1027. if (!styles.opacity) styles.opacity = element.getOpacity();
  1028. return styles;
  1029. };
  1030. }
  1031. Effect.Methods = {
  1032. morph: function(element, style) {
  1033. element = $(element);
  1034. new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
  1035. return element;
  1036. },
  1037. visualEffect: function(element, effect, options) {
  1038. element = $(element);
  1039. var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
  1040. new Effect[klass](element, options);
  1041. return element;
  1042. },
  1043. highlight: function(element, options) {
  1044. element = $(element);
  1045. new Effect.Highlight(element, options);
  1046. return element;
  1047. }
  1048. };
  1049. $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  1050. 'pulsate shake puff squish switchOff dropOut').each(
  1051. function(effect) {
  1052. Effect.Methods[effect] = function(element, options){
  1053. element = $(element);
  1054. Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
  1055. return element;
  1056. };
  1057. }
  1058. );
  1059. $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
  1060. function(f) { Effect.Methods[f] = Element[f]; }
  1061. );
  1062. Element.addMethods(Effect.Methods);