effects.js 38 KB

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