effects.js 38 KB

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