1 // script.aculo.us effects.js v1.8.0_pre1, Fri Oct 12 21:34:51 +0200 2007
3 // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
5 // Justin Palmer (http://encytemedia.com/)
6 // Mark Pilgrim (http://diveintomark.org/)
9 // script.aculo.us is freely distributable under the terms of an MIT-style license.
10 // For details, see the script.aculo.us web site: http://script.aculo.us/
12 // converts rgb() and #xxx to #xxxxxx format,
13 // returns self (or first argument) if not convertable
14 String.prototype.parseColor = function() {
16 if (this.slice(0,4) == 'rgb(') {
17 var cols = this.slice(4,this.length-1).split(',');
18 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
20 if (this.slice(0,1) == '#') {
21 if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
22 if (this.length==7) color = this.toLowerCase();
25 return (color.length==7 ? color : (arguments[0] || this));
28 /*--------------------------------------------------------------------------*/
30 Element.collectTextNodes = function(element) {
31 return $A($(element).childNodes).collect( function(node) {
32 return (node.nodeType==3 ? node.nodeValue :
33 (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
34 }).flatten().join('');
37 Element.collectTextNodesIgnoreClass = function(element, className) {
38 return $A($(element).childNodes).collect( function(node) {
39 return (node.nodeType==3 ? node.nodeValue :
40 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
41 Element.collectTextNodesIgnoreClass(node, className) : ''));
42 }).flatten().join('');
45 Element.setContentZoom = function(element, percent) {
47 element.setStyle({fontSize: (percent/100) + 'em'});
48 if (Prototype.Browser.WebKit) window.scrollBy(0,0);
52 Element.getInlineOpacity = function(element){
53 return $(element).style.opacity || '';
56 Element.forceRerendering = function(element) {
59 var n = document.createTextNode(' ');
60 element.appendChild(n);
61 element.removeChild(n);
65 /*--------------------------------------------------------------------------*/
68 _elementDoesNotExistError: {
69 name: 'ElementDoesNotExistError',
70 message: 'The specified DOM element does not exist, but is required for this effect to operate'
74 sinoidal: function(pos) {
75 return (-Math.cos(pos*Math.PI)/2) + 0.5;
77 reverse: function(pos) {
80 flicker: function(pos) {
81 var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
82 return pos > 1 ? 1 : pos;
84 wobble: function(pos) {
85 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
87 pulse: function(pos, pulses) {
90 ((pos % (1/pulses)) * pulses).round() == 0 ?
91 ((pos * pulses * 2) - (pos * pulses * 2).floor()) :
92 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
95 spring: function(pos) {
96 return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
101 full: function(pos) {
106 duration: 1.0, // seconds
107 fps: 100, // 100= assume 66fps max.
108 sync: false, // true for combining
114 tagifyText: function(element) {
115 var tagifyStyle = 'position:relative';
116 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),
127 Element.remove(child);
131 multiple: function(element, effect) {
133 if (((typeof element == 'object') ||
134 Object.isFunction(element)) &&
138 elements = $(element).childNodes;
140 var options = Object.extend({
143 }, arguments[2] || { });
144 var masterDelay = options.delay;
146 $A(elements).each( function(element, index) {
147 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
151 'slide': ['SlideDown','SlideUp'],
152 'blind': ['BlindDown','BlindUp'],
153 'appear': ['Appear','Fade']
155 toggle: function(element, effect) {
156 element = $(element);
157 effect = (effect || 'appear').toLowerCase();
158 var options = Object.extend({
159 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
160 }, arguments[2] || { });
161 Effect[element.visible() ?
162 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
166 Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
168 /* ------------- core effects ------------- */
170 Effect.ScopedQueue = Class.create(Enumerable, {
171 initialize: function() {
173 this.interval = null;
175 _each: function(iterator) {
176 this.effects._each(iterator);
178 add: function(effect) {
179 var timestamp = new Date().getTime();
181 var position = Object.isString(effect.options.queue) ?
182 effect.options.queue : effect.options.queue.position;
186 // move unstarted effects after this effect
187 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
188 e.startOn += effect.finishOn;
189 e.finishOn += effect.finishOn;
193 timestamp = this.effects.pluck('startOn').max() || timestamp;
196 // start effect after last queued effect has finished
197 timestamp = this.effects.pluck('finishOn').max() || timestamp;
201 effect.startOn += timestamp;
202 effect.finishOn += timestamp;
204 if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
205 this.effects.push(effect);
208 this.interval = setInterval(this.loop.bind(this), 15);
210 remove: function(effect) {
211 this.effects = this.effects.reject(function(e) { return e==effect });
212 if (this.effects.length == 0) {
213 clearInterval(this.interval);
214 this.interval = null;
218 var timePos = new Date().getTime();
219 for(var i=0, len=this.effects.length;i<len;i++)
220 this.effects[i] && this.effects[i].loop(timePos);
226 get: function(queueName) {
227 if (!Object.isString(queueName)) return queueName;
229 if (!this.instances[queueName])
230 this.instances[queueName] = new Effect.ScopedQueue();
232 return this.instances[queueName];
235 Effect.Queue = Effect.Queues.get('global');
237 Effect.Base = Class.create();
238 Effect.Base.prototype = {
240 start: function(options) {
241 function codeForEvent(options,eventName){
243 (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
244 (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
247 if (options && options.transition === false) options.transition = Effect.Transitions.linear;
248 this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
249 this.currentFrame = 0;
251 this.startOn = this.options.delay*1000;
252 this.finishOn = this.startOn+(this.options.duration*1000);
253 this.fromToDelta = this.options.to-this.options.from;
254 this.totalTime = this.finishOn-this.startOn;
255 this.totalFrames = this.options.fps*this.options.duration;
257 eval('this.render = function(pos){ '+
258 'if (this.state=="idle"){this.state="running";'+
259 codeForEvent(this.options,'beforeSetup')+
260 (this.setup ? 'this.setup();':'')+
261 codeForEvent(this.options,'afterSetup')+
262 '};if (this.state=="running"){'+
263 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
264 'this.position=pos;'+
265 codeForEvent(this.options,'beforeUpdate')+
266 (this.update ? 'this.update(pos);':'')+
267 codeForEvent(this.options,'afterUpdate')+
270 this.event('beforeStart');
271 if (!this.options.sync)
272 Effect.Queues.get(Object.isString(this.options.queue) ?
273 'global' : this.options.queue.scope).add(this);
275 loop: function(timePos) {
276 if (timePos >= this.startOn) {
277 if (timePos >= this.finishOn) {
280 this.event('beforeFinish');
281 if (this.finish) this.finish();
282 this.event('afterFinish');
285 var pos = (timePos - this.startOn) / this.totalTime,
286 frame = (pos * this.totalFrames).round();
287 if (frame > this.currentFrame) {
289 this.currentFrame = frame;
294 if (!this.options.sync)
295 Effect.Queues.get(Object.isString(this.options.queue) ?
296 'global' : this.options.queue.scope).remove(this);
297 this.state = 'finished';
299 event: function(eventName) {
300 if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
301 if (this.options[eventName]) this.options[eventName](this);
303 inspect: function() {
305 for(property in this)
306 if (!Object.isFunction(this[property])) data[property] = this[property];
307 return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
311 Effect.Parallel = Class.create(Effect.Base, {
312 initialize: function(effects) {
313 this.effects = effects || [];
314 this.start(arguments[1]);
316 update: function(position) {
317 this.effects.invoke('render', position);
319 finish: function(position) {
320 this.effects.each( function(effect) {
323 effect.event('beforeFinish');
324 if (effect.finish) effect.finish(position);
325 effect.event('afterFinish');
330 Effect.Tween = Class.create(Effect.Base, {
331 initialize: function(object, from, to) {
332 object = Object.isString(object) ? $(object) : object;
333 var args = $A(arguments), method = args.last(),
334 options = args.length == 5 ? args[3] : null;
335 this.method = Object.isFunction(method) ? method.bind(object) :
336 Object.isFunction(object[method]) ? object[method].bind(object) :
337 function(value) { object[method] = value };
338 this.start(Object.extend({ from: from, to: to }, options || { }));
340 update: function(position) {
341 this.method(position);
345 Effect.Event = Class.create(Effect.Base, {
346 initialize: function() {
347 this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
349 update: Prototype.emptyFunction
352 Effect.Opacity = Class.create(Effect.Base, {
353 initialize: function(element) {
354 this.element = $(element);
355 if (!this.element) throw(Effect._elementDoesNotExistError);
356 // make this work on IE on elements without 'layout'
357 if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
358 this.element.setStyle({zoom: 1});
359 var options = Object.extend({
360 from: this.element.getOpacity() || 0.0,
362 }, arguments[1] || { });
365 update: function(position) {
366 this.element.setOpacity(position);
370 Effect.Move = Class.create(Effect.Base, {
371 initialize: function(element) {
372 this.element = $(element);
373 if (!this.element) throw(Effect._elementDoesNotExistError);
374 var options = Object.extend({
378 }, arguments[1] || { });
382 this.element.makePositioned();
383 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
384 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
385 if (this.options.mode == 'absolute') {
386 this.options.x = this.options.x - this.originalLeft;
387 this.options.y = this.options.y - this.originalTop;
390 update: function(position) {
391 this.element.setStyle({
392 left: (this.options.x * position + this.originalLeft).round() + 'px',
393 top: (this.options.y * position + this.originalTop).round() + 'px'
398 // for backwards compatibility
399 Effect.MoveBy = function(element, toTop, toLeft) {
400 return new Effect.Move(element,
401 Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
404 Effect.Scale = Class.create(Effect.Base, {
405 initialize: function(element, percent) {
406 this.element = $(element);
407 if (!this.element) throw(Effect._elementDoesNotExistError);
408 var options = Object.extend({
412 scaleFromCenter: false,
413 scaleMode: 'box', // 'box' or 'contents' or { } with provided values
416 }, arguments[2] || { });
420 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
421 this.elementPositioning = this.element.getStyle('position');
423 this.originalStyle = { };
424 ['top','left','width','height','fontSize'].each( function(k) {
425 this.originalStyle[k] = this.element.style[k];
428 this.originalTop = this.element.offsetTop;
429 this.originalLeft = this.element.offsetLeft;
431 var fontSize = this.element.getStyle('font-size') || '100%';
432 ['em','px','%','pt'].each( function(fontSizeType) {
433 if (fontSize.indexOf(fontSizeType)>0) {
434 this.fontSize = parseFloat(fontSize);
435 this.fontSizeType = fontSizeType;
439 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
442 if (this.options.scaleMode=='box')
443 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
444 if (/^content/.test(this.options.scaleMode))
445 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
447 this.dims = [this.options.scaleMode.originalHeight,
448 this.options.scaleMode.originalWidth];
450 update: function(position) {
451 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
452 if (this.options.scaleContent && this.fontSize)
453 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
454 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
456 finish: function(position) {
457 if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
459 setDimensions: function(height, width) {
461 if (this.options.scaleX) d.width = width.round() + 'px';
462 if (this.options.scaleY) d.height = height.round() + 'px';
463 if (this.options.scaleFromCenter) {
464 var topd = (height - this.dims[0])/2;
465 var leftd = (width - this.dims[1])/2;
466 if (this.elementPositioning == 'absolute') {
467 if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
468 if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
470 if (this.options.scaleY) d.top = -topd + 'px';
471 if (this.options.scaleX) d.left = -leftd + 'px';
474 this.element.setStyle(d);
478 Effect.Highlight = Class.create(Effect.Base, {
479 initialize: function(element) {
480 this.element = $(element);
481 if (!this.element) throw(Effect._elementDoesNotExistError);
482 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
486 // Prevent executing on elements not in the layout flow
487 if (this.element.getStyle('display')=='none') { this.cancel(); return; }
488 // Disable background image during the effect
490 if (!this.options.keepBackgroundImage) {
491 this.oldStyle.backgroundImage = this.element.getStyle('background-image');
492 this.element.setStyle({backgroundImage: 'none'});
494 if (!this.options.endcolor)
495 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
496 if (!this.options.restorecolor)
497 this.options.restorecolor = this.element.getStyle('background-color');
498 // init color calculations
499 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
500 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));
502 update: function(position) {
503 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
504 return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
507 this.element.setStyle(Object.extend(this.oldStyle, {
508 backgroundColor: this.options.restorecolor
513 Effect.ScrollTo = function(element) {
514 var options = arguments[1] || { },
515 scrollOffsets = document.viewport.getScrollOffsets(),
516 elementOffsets = $(element).cumulativeOffset(),
517 max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();
519 if (options.offset) elementOffsets[1] += options.offset;
521 return new Effect.Tween(null,
523 elementOffsets[1] > max ? max : elementOffsets[1],
525 function(p){ scrollTo(scrollOffsets.left, p.round()) }
529 /* ------------- combination effects ------------- */
531 Effect.Fade = function(element) {
532 element = $(element);
533 var oldOpacity = element.getInlineOpacity();
534 var options = Object.extend({
535 from: element.getOpacity() || 1.0,
537 afterFinishInternal: function(effect) {
538 if (effect.options.to!=0) return;
539 effect.element.hide().setStyle({opacity: oldOpacity});
541 }, arguments[1] || { });
542 return new Effect.Opacity(element,options);
545 Effect.Appear = function(element) {
546 element = $(element);
547 var options = Object.extend({
548 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
550 // force Safari to render floated elements properly
551 afterFinishInternal: function(effect) {
552 effect.element.forceRerendering();
554 beforeSetup: function(effect) {
555 effect.element.setOpacity(effect.options.from).show();
556 }}, arguments[1] || { });
557 return new Effect.Opacity(element,options);
560 Effect.Puff = function(element) {
561 element = $(element);
563 opacity: element.getInlineOpacity(),
564 position: element.getStyle('position'),
565 top: element.style.top,
566 left: element.style.left,
567 width: element.style.width,
568 height: element.style.height
570 return new Effect.Parallel(
571 [ new Effect.Scale(element, 200,
572 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
573 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
574 Object.extend({ duration: 1.0,
575 beforeSetupInternal: function(effect) {
576 Position.absolutize(effect.effects[0].element)
578 afterFinishInternal: function(effect) {
579 effect.effects[0].element.hide().setStyle(oldStyle); }
580 }, arguments[1] || { })
584 Effect.BlindUp = function(element) {
585 element = $(element);
586 element.makeClipping();
587 return new Effect.Scale(element, 0,
588 Object.extend({ scaleContent: false,
590 restoreAfterFinish: true,
591 afterFinishInternal: function(effect) {
592 effect.element.hide().undoClipping();
594 }, arguments[1] || { })
598 Effect.BlindDown = function(element) {
599 element = $(element);
600 var elementDimensions = element.getDimensions();
601 return new Effect.Scale(element, 100, Object.extend({
605 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
606 restoreAfterFinish: true,
607 afterSetup: function(effect) {
608 effect.element.makeClipping().setStyle({height: '0px'}).show();
610 afterFinishInternal: function(effect) {
611 effect.element.undoClipping();
613 }, arguments[1] || { }));
616 Effect.SwitchOff = function(element) {
617 element = $(element);
618 var oldOpacity = element.getInlineOpacity();
619 return new Effect.Appear(element, Object.extend({
622 transition: Effect.Transitions.flicker,
623 afterFinishInternal: function(effect) {
624 new Effect.Scale(effect.element, 1, {
625 duration: 0.3, scaleFromCenter: true,
626 scaleX: false, scaleContent: false, restoreAfterFinish: true,
627 beforeSetup: function(effect) {
628 effect.element.makePositioned().makeClipping();
630 afterFinishInternal: function(effect) {
631 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
635 }, arguments[1] || { }));
638 Effect.DropOut = function(element) {
639 element = $(element);
641 top: element.getStyle('top'),
642 left: element.getStyle('left'),
643 opacity: element.getInlineOpacity() };
644 return new Effect.Parallel(
645 [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
646 new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
649 beforeSetup: function(effect) {
650 effect.effects[0].element.makePositioned();
652 afterFinishInternal: function(effect) {
653 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
655 }, arguments[1] || { }));
658 Effect.Shake = function(element) {
659 element = $(element);
661 top: element.getStyle('top'),
662 left: element.getStyle('left') };
663 return new Effect.Move(element,
664 { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
665 new Effect.Move(effect.element,
666 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
667 new Effect.Move(effect.element,
668 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
669 new Effect.Move(effect.element,
670 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
671 new Effect.Move(effect.element,
672 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
673 new Effect.Move(effect.element,
674 { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
675 effect.element.undoPositioned().setStyle(oldStyle);
676 }}) }}) }}) }}) }}) }});
679 Effect.SlideDown = function(element) {
680 element = $(element).cleanWhitespace();
681 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
682 var oldInnerBottom = element.down().getStyle('bottom');
683 var elementDimensions = element.getDimensions();
684 return new Effect.Scale(element, 100, Object.extend({
687 scaleFrom: window.opera ? 0 : 1,
688 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
689 restoreAfterFinish: true,
690 afterSetup: function(effect) {
691 effect.element.makePositioned();
692 effect.element.down().makePositioned();
693 if (window.opera) effect.element.setStyle({top: ''});
694 effect.element.makeClipping().setStyle({height: '0px'}).show();
696 afterUpdateInternal: function(effect) {
697 effect.element.down().setStyle({bottom:
698 (effect.dims[0] - effect.element.clientHeight) + 'px' });
700 afterFinishInternal: function(effect) {
701 effect.element.undoClipping().undoPositioned();
702 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
703 }, arguments[1] || { })
707 Effect.SlideUp = function(element) {
708 element = $(element).cleanWhitespace();
709 var oldInnerBottom = element.down().getStyle('bottom');
710 var elementDimensions = element.getDimensions();
711 return new Effect.Scale(element, window.opera ? 0 : 1,
712 Object.extend({ scaleContent: false,
716 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
717 restoreAfterFinish: true,
718 afterSetup: function(effect) {
719 effect.element.makePositioned();
720 effect.element.down().makePositioned();
721 if (window.opera) effect.element.setStyle({top: ''});
722 effect.element.makeClipping().show();
724 afterUpdateInternal: function(effect) {
725 effect.element.down().setStyle({bottom:
726 (effect.dims[0] - effect.element.clientHeight) + 'px' });
728 afterFinishInternal: function(effect) {
729 effect.element.hide().undoClipping().undoPositioned();
730 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
732 }, arguments[1] || { })
736 // Bug in opera makes the TD containing this element expand for a instance after finish
737 Effect.Squish = function(element) {
738 return new Effect.Scale(element, window.opera ? 1 : 0, {
739 restoreAfterFinish: true,
740 beforeSetup: function(effect) {
741 effect.element.makeClipping();
743 afterFinishInternal: function(effect) {
744 effect.element.hide().undoClipping();
749 Effect.Grow = function(element) {
750 element = $(element);
751 var options = Object.extend({
753 moveTransition: Effect.Transitions.sinoidal,
754 scaleTransition: Effect.Transitions.sinoidal,
755 opacityTransition: Effect.Transitions.full
756 }, arguments[1] || { });
758 top: element.style.top,
759 left: element.style.left,
760 height: element.style.height,
761 width: element.style.width,
762 opacity: element.getInlineOpacity() };
764 var dims = element.getDimensions();
765 var initialMoveX, initialMoveY;
768 switch (options.direction) {
770 initialMoveX = initialMoveY = moveX = moveY = 0;
773 initialMoveX = dims.width;
774 initialMoveY = moveY = 0;
778 initialMoveX = moveX = 0;
779 initialMoveY = dims.height;
780 moveY = -dims.height;
783 initialMoveX = dims.width;
784 initialMoveY = dims.height;
786 moveY = -dims.height;
789 initialMoveX = dims.width / 2;
790 initialMoveY = dims.height / 2;
791 moveX = -dims.width / 2;
792 moveY = -dims.height / 2;
796 return new Effect.Move(element, {
800 beforeSetup: function(effect) {
801 effect.element.hide().makeClipping().makePositioned();
803 afterFinishInternal: function(effect) {
805 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
806 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
807 new Effect.Scale(effect.element, 100, {
808 scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
809 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
811 beforeSetup: function(effect) {
812 effect.effects[0].element.setStyle({height: '0px'}).show();
814 afterFinishInternal: function(effect) {
815 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
823 Effect.Shrink = function(element) {
824 element = $(element);
825 var options = Object.extend({
827 moveTransition: Effect.Transitions.sinoidal,
828 scaleTransition: Effect.Transitions.sinoidal,
829 opacityTransition: Effect.Transitions.none
830 }, arguments[1] || { });
832 top: element.style.top,
833 left: element.style.left,
834 height: element.style.height,
835 width: element.style.width,
836 opacity: element.getInlineOpacity() };
838 var dims = element.getDimensions();
841 switch (options.direction) {
858 moveX = dims.width / 2;
859 moveY = dims.height / 2;
863 return new Effect.Parallel(
864 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
865 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
866 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
868 beforeStartInternal: function(effect) {
869 effect.effects[0].element.makePositioned().makeClipping();
871 afterFinishInternal: function(effect) {
872 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
877 Effect.Pulsate = function(element) {
878 element = $(element);
879 var options = arguments[1] || { };
880 var oldOpacity = element.getInlineOpacity();
881 var transition = options.transition || Effect.Transitions.sinoidal;
882 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
883 reverser.bind(transition);
884 return new Effect.Opacity(element,
885 Object.extend(Object.extend({ duration: 2.0, from: 0,
886 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
887 }, options), {transition: reverser}));
890 Effect.Fold = function(element) {
891 element = $(element);
893 top: element.style.top,
894 left: element.style.left,
895 width: element.style.width,
896 height: element.style.height };
897 element.makeClipping();
898 return new Effect.Scale(element, 5, Object.extend({
901 afterFinishInternal: function(effect) {
902 new Effect.Scale(element, 1, {
905 afterFinishInternal: function(effect) {
906 effect.element.hide().undoClipping().setStyle(oldStyle);
908 }}, arguments[1] || { }));
911 Effect.Morph = Class.create(Effect.Base, {
912 initialize: function(element) {
913 this.element = $(element);
914 if (!this.element) throw(Effect._elementDoesNotExistError);
915 var options = Object.extend({
917 }, arguments[1] || { });
919 if (!Object.isString(options.style)) this.style = $H(options.style);
921 if (options.style.include(':'))
922 this.style = options.style.parseStyle();
924 this.element.addClassName(options.style);
925 this.style = $H(this.element.getStyles());
926 this.element.removeClassName(options.style);
927 var css = this.element.getStyles();
928 this.style = this.style.reject(function(style) {
929 return style.value == css[style.key];
931 options.afterFinishInternal = function(effect) {
932 effect.element.addClassName(effect.options.style);
933 effect.transforms.each(function(transform) {
934 effect.element.style[transform.style] = '';
943 function parseColor(color){
944 if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
945 color = color.parseColor();
946 return $R(0,2).map(function(i){
947 return parseInt( color.slice(i*2+1,i*2+3), 16 )
950 this.transforms = this.style.map(function(pair){
951 var property = pair[0], value = pair[1], unit = null;
953 if (value.parseColor('#zzzzzz') != '#zzzzzz') {
954 value = value.parseColor();
956 } else if (property == 'opacity') {
957 value = parseFloat(value);
958 if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
959 this.element.setStyle({zoom: 1});
960 } else if (Element.CSS_LENGTH.test(value)) {
961 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
962 value = parseFloat(components[1]);
963 unit = (components.length == 3) ? components[2] : null;
966 var originalValue = this.element.getStyle(property);
968 style: property.camelize(),
969 originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
970 targetValue: unit=='color' ? parseColor(value) : value,
973 }.bind(this)).reject(function(transform){
975 (transform.originalValue == transform.targetValue) ||
977 transform.unit != 'color' &&
978 (isNaN(transform.originalValue) || isNaN(transform.targetValue))
983 update: function(position) {
984 var style = { }, transform, i = this.transforms.length;
986 style[(transform = this.transforms[i]).style] =
987 transform.unit=='color' ? '#'+
988 (Math.round(transform.originalValue[0]+
989 (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
990 (Math.round(transform.originalValue[1]+
991 (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
992 (Math.round(transform.originalValue[2]+
993 (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
994 (transform.originalValue +
995 (transform.targetValue - transform.originalValue) * position).toFixed(3) +
996 (transform.unit === null ? '' : transform.unit);
997 this.element.setStyle(style, true);
1001 Effect.Transform = Class.create({
1002 initialize: function(tracks){
1004 this.options = arguments[1] || { };
1005 this.addTracks(tracks);
1007 addTracks: function(tracks){
1008 tracks.each(function(track){
1009 var data = $H(track).values().first();
1010 this.tracks.push($H({
1011 ids: $H(track).keys().first(),
1012 effect: Effect.Morph,
1013 options: { style: data }
1019 return new Effect.Parallel(
1020 this.tracks.map(function(track){
1021 var elements = [$(track.ids) || $$(track.ids)].flatten();
1022 return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
1029 Element.CSS_PROPERTIES = $w(
1030 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1031 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1032 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1033 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1034 'fontSize fontWeight height left letterSpacing lineHeight ' +
1035 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1036 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1037 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1038 'right textIndent top width wordSpacing zIndex');
1040 Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1042 String.__parseStyleElement = document.createElement('div');
1043 String.prototype.parseStyle = function(){
1044 var style, styleRules = $H();
1045 if (Prototype.Browser.WebKit)
1046 style = new Element('div',{style:this}).style;
1048 String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
1049 style = String.__parseStyleElement.childNodes[0].style;
1052 Element.CSS_PROPERTIES.each(function(property){
1053 if (style[property]) styleRules[property] = style[property];
1056 if (Prototype.Browser.IE && this.include('opacity'))
1057 styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
1062 if (document.defaultView && document.defaultView.getComputedStyle) {
1063 Element.getStyles = function(element) {
1064 var css = document.defaultView.getComputedStyle($(element), null);
1065 return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
1066 styles[property] = css[property];
1071 Element.getStyles = function(element) {
1072 element = $(element);
1073 var css = element.currentStyle, styles;
1074 styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) {
1075 hash[property] = css[property];
1078 if (!styles.opacity) styles.opacity = element.getOpacity();
1084 morph: function(element, style) {
1085 element = $(element);
1086 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
1089 visualEffect: function(element, effect, options) {
1090 element = $(element)
1091 var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
1092 new Effect[klass](element, options);
1095 highlight: function(element, options) {
1096 element = $(element);
1097 new Effect.Highlight(element, options);
1102 $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1103 'pulsate shake puff squish switchOff dropOut').each(
1105 Effect.Methods[effect] = function(element, options){
1106 element = $(element);
1107 Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
1113 $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1114 function(f) { Effect.Methods[f] = Element[f]; }
1117 Element.addMethods(Effect.Methods);