1 // script.aculo.us effects.js v1.8.0, Tue Nov 06 15:01:40 +0300 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 return this.instances
.get(queueName
) ||
230 this.instances
.set(queueName
, new Effect
.ScopedQueue());
233 Effect
.Queue
= Effect
.Queues
.get('global');
235 Effect
.Base
= Class
.create({
237 start: function(options
) {
238 function codeForEvent(options
,eventName
){
240 (options
[eventName
+'Internal'] ? 'this.options.'+eventName
+'Internal(this);' : '') +
241 (options
[eventName
] ? 'this.options.'+eventName
+'(this);' : '')
244 if (options
&& options
.transition
=== false) options
.transition
= Effect
.Transitions
.linear
;
245 this.options
= Object
.extend(Object
.extend({ },Effect
.DefaultOptions
), options
|| { });
246 this.currentFrame
= 0;
248 this.startOn
= this.options
.delay
*1000;
249 this.finishOn
= this.startOn
+(this.options
.duration
*1000);
250 this.fromToDelta
= this.options
.to
-this.options
.from;
251 this.totalTime
= this.finishOn
-this.startOn
;
252 this.totalFrames
= this.options
.fps
*this.options
.duration
;
254 eval('this.render = function(pos){ '+
255 'if (this.state=="idle"){this.state="running";'+
256 codeForEvent(this.options
,'beforeSetup')+
257 (this.setup
? 'this.setup();':'')+
258 codeForEvent(this.options
,'afterSetup')+
259 '};if (this.state=="running"){'+
260 'pos=this.options.transition(pos)*'+this.fromToDelta
+'+'+this.options
.from+';'+
261 'this.position=pos;'+
262 codeForEvent(this.options
,'beforeUpdate')+
263 (this.update
? 'this.update(pos);':'')+
264 codeForEvent(this.options
,'afterUpdate')+
267 this.event('beforeStart');
268 if (!this.options
.sync
)
269 Effect
.Queues
.get(Object
.isString(this.options
.queue
) ?
270 'global' : this.options
.queue
.scope
).add(this);
272 loop: function(timePos
) {
273 if (timePos
>= this.startOn
) {
274 if (timePos
>= this.finishOn
) {
277 this.event('beforeFinish');
278 if (this.finish
) this.finish();
279 this.event('afterFinish');
282 var pos
= (timePos
- this.startOn
) / this.totalTime
,
283 frame
= (pos
* this.totalFrames
).round();
284 if (frame
> this.currentFrame
) {
286 this.currentFrame
= frame
;
291 if (!this.options
.sync
)
292 Effect
.Queues
.get(Object
.isString(this.options
.queue
) ?
293 'global' : this.options
.queue
.scope
).remove(this);
294 this.state
= 'finished';
296 event: function(eventName
) {
297 if (this.options
[eventName
+ 'Internal']) this.options
[eventName
+ 'Internal'](this);
298 if (this.options
[eventName
]) this.options
[eventName
](this);
300 inspect: function() {
302 for(property
in this)
303 if (!Object
.isFunction(this[property
])) data
.set(property
, this[property
]);
304 return '#<Effect:' + data
.inspect() + ',options:' + $H(this.options
).inspect() + '>';
308 Effect
.Parallel
= Class
.create(Effect
.Base
, {
309 initialize: function(effects
) {
310 this.effects
= effects
|| [];
311 this.start(arguments
[1]);
313 update: function(position
) {
314 this.effects
.invoke('render', position
);
316 finish: function(position
) {
317 this.effects
.each( function(effect
) {
320 effect
.event('beforeFinish');
321 if (effect
.finish
) effect
.finish(position
);
322 effect
.event('afterFinish');
327 Effect
.Tween
= Class
.create(Effect
.Base
, {
328 initialize: function(object
, from, to
) {
329 object
= Object
.isString(object
) ? $(object
) : object
;
330 var args
= $A(arguments
), method
= args
.last(),
331 options
= args
.length
== 5 ? args
[3] : null;
332 this.method
= Object
.isFunction(method
) ? method
.bind(object
) :
333 Object
.isFunction(object
[method
]) ? object
[method
].bind(object
) :
334 function(value
) { object
[method
] = value
};
335 this.start(Object
.extend({ from: from, to
: to
}, options
|| { }));
337 update: function(position
) {
338 this.method(position
);
342 Effect
.Event
= Class
.create(Effect
.Base
, {
343 initialize: function() {
344 this.start(Object
.extend({ duration
: 0 }, arguments
[0] || { }));
346 update
: Prototype
.emptyFunction
349 Effect
.Opacity
= Class
.create(Effect
.Base
, {
350 initialize: function(element
) {
351 this.element
= $(element
);
352 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
353 // make this work on IE on elements without 'layout'
354 if (Prototype
.Browser
.IE
&& (!this.element
.currentStyle
.hasLayout
))
355 this.element
.setStyle({zoom
: 1});
356 var options
= Object
.extend({
357 from: this.element
.getOpacity() || 0.0,
359 }, arguments
[1] || { });
362 update: function(position
) {
363 this.element
.setOpacity(position
);
367 Effect
.Move
= Class
.create(Effect
.Base
, {
368 initialize: function(element
) {
369 this.element
= $(element
);
370 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
371 var options
= Object
.extend({
375 }, arguments
[1] || { });
379 this.element
.makePositioned();
380 this.originalLeft
= parseFloat(this.element
.getStyle('left') || '0');
381 this.originalTop
= parseFloat(this.element
.getStyle('top') || '0');
382 if (this.options
.mode
== 'absolute') {
383 this.options
.x
= this.options
.x
- this.originalLeft
;
384 this.options
.y
= this.options
.y
- this.originalTop
;
387 update: function(position
) {
388 this.element
.setStyle({
389 left
: (this.options
.x
* position
+ this.originalLeft
).round() + 'px',
390 top
: (this.options
.y
* position
+ this.originalTop
).round() + 'px'
395 // for backwards compatibility
396 Effect
.MoveBy = function(element
, toTop
, toLeft
) {
397 return new Effect
.Move(element
,
398 Object
.extend({ x
: toLeft
, y
: toTop
}, arguments
[3] || { }));
401 Effect
.Scale
= Class
.create(Effect
.Base
, {
402 initialize: function(element
, percent
) {
403 this.element
= $(element
);
404 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
405 var options
= Object
.extend({
409 scaleFromCenter
: false,
410 scaleMode
: 'box', // 'box' or 'contents' or { } with provided values
413 }, arguments
[2] || { });
417 this.restoreAfterFinish
= this.options
.restoreAfterFinish
|| false;
418 this.elementPositioning
= this.element
.getStyle('position');
420 this.originalStyle
= { };
421 ['top','left','width','height','fontSize'].each( function(k
) {
422 this.originalStyle
[k
] = this.element
.style
[k
];
425 this.originalTop
= this.element
.offsetTop
;
426 this.originalLeft
= this.element
.offsetLeft
;
428 var fontSize
= this.element
.getStyle('font-size') || '100%';
429 ['em','px','%','pt'].each( function(fontSizeType
) {
430 if (fontSize
.indexOf(fontSizeType
)>0) {
431 this.fontSize
= parseFloat(fontSize
);
432 this.fontSizeType
= fontSizeType
;
436 this.factor
= (this.options
.scaleTo
- this.options
.scaleFrom
)/100;
439 if (this.options
.scaleMode
=='box')
440 this.dims
= [this.element
.offsetHeight
, this.element
.offsetWidth
];
441 if (/^content/.test(this.options
.scaleMode
))
442 this.dims
= [this.element
.scrollHeight
, this.element
.scrollWidth
];
444 this.dims
= [this.options
.scaleMode
.originalHeight
,
445 this.options
.scaleMode
.originalWidth
];
447 update: function(position
) {
448 var currentScale
= (this.options
.scaleFrom
/100.0) + (this.factor
* position
);
449 if (this.options
.scaleContent
&& this.fontSize
)
450 this.element
.setStyle({fontSize
: this.fontSize
* currentScale
+ this.fontSizeType
});
451 this.setDimensions(this.dims
[0] * currentScale
, this.dims
[1] * currentScale
);
453 finish: function(position
) {
454 if (this.restoreAfterFinish
) this.element
.setStyle(this.originalStyle
);
456 setDimensions: function(height
, width
) {
458 if (this.options
.scaleX
) d
.width
= width
.round() + 'px';
459 if (this.options
.scaleY
) d
.height
= height
.round() + 'px';
460 if (this.options
.scaleFromCenter
) {
461 var topd
= (height
- this.dims
[0])/2;
462 var leftd
= (width
- this.dims
[1])/2;
463 if (this.elementPositioning
== 'absolute') {
464 if (this.options
.scaleY
) d
.top
= this.originalTop
-topd
+ 'px';
465 if (this.options
.scaleX
) d
.left
= this.originalLeft
-leftd
+ 'px';
467 if (this.options
.scaleY
) d
.top
= -topd
+ 'px';
468 if (this.options
.scaleX
) d
.left
= -leftd
+ 'px';
471 this.element
.setStyle(d
);
475 Effect
.Highlight
= Class
.create(Effect
.Base
, {
476 initialize: function(element
) {
477 this.element
= $(element
);
478 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
479 var options
= Object
.extend({ startcolor
: '#ffff99' }, arguments
[1] || { });
483 // Prevent executing on elements not in the layout flow
484 if (this.element
.getStyle('display')=='none') { this.cancel(); return; }
485 // Disable background image during the effect
487 if (!this.options
.keepBackgroundImage
) {
488 this.oldStyle
.backgroundImage
= this.element
.getStyle('background-image');
489 this.element
.setStyle({backgroundImage
: 'none'});
491 if (!this.options
.endcolor
)
492 this.options
.endcolor
= this.element
.getStyle('background-color').parseColor('#ffffff');
493 if (!this.options
.restorecolor
)
494 this.options
.restorecolor
= this.element
.getStyle('background-color');
495 // init color calculations
496 this._base
= $R(0,2).map(function(i
){ return parseInt(this.options
.startcolor
.slice(i
*2+1,i
*2+3),16) }.bind(this));
497 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));
499 update: function(position
) {
500 this.element
.setStyle({backgroundColor
: $R(0,2).inject('#',function(m
,v
,i
){
501 return m
+((this._base
[i
]+(this._delta
[i
]*position
)).round().toColorPart()); }.bind(this)) });
504 this.element
.setStyle(Object
.extend(this.oldStyle
, {
505 backgroundColor
: this.options
.restorecolor
510 Effect
.ScrollTo = function(element
) {
511 var options
= arguments
[1] || { },
512 scrollOffsets
= document
.viewport
.getScrollOffsets(),
513 elementOffsets
= $(element
).cumulativeOffset(),
514 max
= (window
.height
|| document
.body
.scrollHeight
) - document
.viewport
.getHeight();
516 if (options
.offset
) elementOffsets
[1] += options
.offset
;
518 return new Effect
.Tween(null,
520 elementOffsets
[1] > max
? max
: elementOffsets
[1],
522 function(p
){ scrollTo(scrollOffsets
.left
, p
.round()) }
526 /* ------------- combination effects ------------- */
528 Effect
.Fade = function(element
) {
529 element
= $(element
);
530 var oldOpacity
= element
.getInlineOpacity();
531 var options
= Object
.extend({
532 from: element
.getOpacity() || 1.0,
534 afterFinishInternal: function(effect
) {
535 if (effect
.options
.to
!=0) return;
536 effect
.element
.hide().setStyle({opacity
: oldOpacity
});
538 }, arguments
[1] || { });
539 return new Effect
.Opacity(element
,options
);
542 Effect
.Appear = function(element
) {
543 element
= $(element
);
544 var options
= Object
.extend({
545 from: (element
.getStyle('display') == 'none' ? 0.0 : element
.getOpacity() || 0.0),
547 // force Safari to render floated elements properly
548 afterFinishInternal: function(effect
) {
549 effect
.element
.forceRerendering();
551 beforeSetup: function(effect
) {
552 effect
.element
.setOpacity(effect
.options
.from).show();
553 }}, arguments
[1] || { });
554 return new Effect
.Opacity(element
,options
);
557 Effect
.Puff = function(element
) {
558 element
= $(element
);
560 opacity
: element
.getInlineOpacity(),
561 position
: element
.getStyle('position'),
562 top
: element
.style
.top
,
563 left
: element
.style
.left
,
564 width
: element
.style
.width
,
565 height
: element
.style
.height
567 return new Effect
.Parallel(
568 [ new Effect
.Scale(element
, 200,
569 { sync
: true, scaleFromCenter
: true, scaleContent
: true, restoreAfterFinish
: true }),
570 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 } ) ],
571 Object
.extend({ duration
: 1.0,
572 beforeSetupInternal: function(effect
) {
573 Position
.absolutize(effect
.effects
[0].element
)
575 afterFinishInternal: function(effect
) {
576 effect
.effects
[0].element
.hide().setStyle(oldStyle
); }
577 }, arguments
[1] || { })
581 Effect
.BlindUp = function(element
) {
582 element
= $(element
);
583 element
.makeClipping();
584 return new Effect
.Scale(element
, 0,
585 Object
.extend({ scaleContent
: false,
587 restoreAfterFinish
: true,
588 afterFinishInternal: function(effect
) {
589 effect
.element
.hide().undoClipping();
591 }, arguments
[1] || { })
595 Effect
.BlindDown = function(element
) {
596 element
= $(element
);
597 var elementDimensions
= element
.getDimensions();
598 return new Effect
.Scale(element
, 100, Object
.extend({
602 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
603 restoreAfterFinish
: true,
604 afterSetup: function(effect
) {
605 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
607 afterFinishInternal: function(effect
) {
608 effect
.element
.undoClipping();
610 }, arguments
[1] || { }));
613 Effect
.SwitchOff = function(element
) {
614 element
= $(element
);
615 var oldOpacity
= element
.getInlineOpacity();
616 return new Effect
.Appear(element
, Object
.extend({
619 transition
: Effect
.Transitions
.flicker
,
620 afterFinishInternal: function(effect
) {
621 new Effect
.Scale(effect
.element
, 1, {
622 duration
: 0.3, scaleFromCenter
: true,
623 scaleX
: false, scaleContent
: false, restoreAfterFinish
: true,
624 beforeSetup: function(effect
) {
625 effect
.element
.makePositioned().makeClipping();
627 afterFinishInternal: function(effect
) {
628 effect
.element
.hide().undoClipping().undoPositioned().setStyle({opacity
: oldOpacity
});
632 }, arguments
[1] || { }));
635 Effect
.DropOut = function(element
) {
636 element
= $(element
);
638 top
: element
.getStyle('top'),
639 left
: element
.getStyle('left'),
640 opacity
: element
.getInlineOpacity() };
641 return new Effect
.Parallel(
642 [ new Effect
.Move(element
, {x
: 0, y
: 100, sync
: true }),
643 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 }) ],
646 beforeSetup: function(effect
) {
647 effect
.effects
[0].element
.makePositioned();
649 afterFinishInternal: function(effect
) {
650 effect
.effects
[0].element
.hide().undoPositioned().setStyle(oldStyle
);
652 }, arguments
[1] || { }));
655 Effect
.Shake = function(element
) {
656 element
= $(element
);
657 var options
= Object
.extend({
660 }, arguments
[1] || {});
661 var distance
= parseFloat(options
.distance
);
662 var split
= parseFloat(options
.duration
) / 10.0;
664 top
: element
.getStyle('top'),
665 left
: element
.getStyle('left') };
666 return new Effect
.Move(element
,
667 { x
: distance
, y
: 0, duration
: split
, afterFinishInternal: function(effect
) {
668 new Effect
.Move(effect
.element
,
669 { x
: -distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
670 new Effect
.Move(effect
.element
,
671 { x
: distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
672 new Effect
.Move(effect
.element
,
673 { x
: -distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
674 new Effect
.Move(effect
.element
,
675 { x
: distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
676 new Effect
.Move(effect
.element
,
677 { x
: -distance
, y
: 0, duration
: split
, afterFinishInternal: function(effect
) {
678 effect
.element
.undoPositioned().setStyle(oldStyle
);
679 }}) }}) }}) }}) }}) }});
682 Effect
.SlideDown = function(element
) {
683 element
= $(element
).cleanWhitespace();
684 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
685 var oldInnerBottom
= element
.down().getStyle('bottom');
686 var elementDimensions
= element
.getDimensions();
687 return new Effect
.Scale(element
, 100, Object
.extend({
690 scaleFrom
: window
.opera
? 0 : 1,
691 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
692 restoreAfterFinish
: true,
693 afterSetup: function(effect
) {
694 effect
.element
.makePositioned();
695 effect
.element
.down().makePositioned();
696 if (window
.opera
) effect
.element
.setStyle({top
: ''});
697 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
699 afterUpdateInternal: function(effect
) {
700 effect
.element
.down().setStyle({bottom
:
701 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
703 afterFinishInternal: function(effect
) {
704 effect
.element
.undoClipping().undoPositioned();
705 effect
.element
.down().undoPositioned().setStyle({bottom
: oldInnerBottom
}); }
706 }, arguments
[1] || { })
710 Effect
.SlideUp = function(element
) {
711 element
= $(element
).cleanWhitespace();
712 var oldInnerBottom
= element
.down().getStyle('bottom');
713 var elementDimensions
= element
.getDimensions();
714 return new Effect
.Scale(element
, window
.opera
? 0 : 1,
715 Object
.extend({ scaleContent
: false,
719 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
720 restoreAfterFinish
: true,
721 afterSetup: function(effect
) {
722 effect
.element
.makePositioned();
723 effect
.element
.down().makePositioned();
724 if (window
.opera
) effect
.element
.setStyle({top
: ''});
725 effect
.element
.makeClipping().show();
727 afterUpdateInternal: function(effect
) {
728 effect
.element
.down().setStyle({bottom
:
729 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
731 afterFinishInternal: function(effect
) {
732 effect
.element
.hide().undoClipping().undoPositioned();
733 effect
.element
.down().undoPositioned().setStyle({bottom
: oldInnerBottom
});
735 }, arguments
[1] || { })
739 // Bug in opera makes the TD containing this element expand for a instance after finish
740 Effect
.Squish = function(element
) {
741 return new Effect
.Scale(element
, window
.opera
? 1 : 0, {
742 restoreAfterFinish
: true,
743 beforeSetup: function(effect
) {
744 effect
.element
.makeClipping();
746 afterFinishInternal: function(effect
) {
747 effect
.element
.hide().undoClipping();
752 Effect
.Grow = function(element
) {
753 element
= $(element
);
754 var options
= Object
.extend({
756 moveTransition
: Effect
.Transitions
.sinoidal
,
757 scaleTransition
: Effect
.Transitions
.sinoidal
,
758 opacityTransition
: Effect
.Transitions
.full
759 }, arguments
[1] || { });
761 top
: element
.style
.top
,
762 left
: element
.style
.left
,
763 height
: element
.style
.height
,
764 width
: element
.style
.width
,
765 opacity
: element
.getInlineOpacity() };
767 var dims
= element
.getDimensions();
768 var initialMoveX
, initialMoveY
;
771 switch (options
.direction
) {
773 initialMoveX
= initialMoveY
= moveX
= moveY
= 0;
776 initialMoveX
= dims
.width
;
777 initialMoveY
= moveY
= 0;
781 initialMoveX
= moveX
= 0;
782 initialMoveY
= dims
.height
;
783 moveY
= -dims
.height
;
786 initialMoveX
= dims
.width
;
787 initialMoveY
= dims
.height
;
789 moveY
= -dims
.height
;
792 initialMoveX
= dims
.width
/ 2;
793 initialMoveY
= dims
.height
/ 2;
794 moveX
= -dims
.width
/ 2;
795 moveY
= -dims
.height
/ 2;
799 return new Effect
.Move(element
, {
803 beforeSetup: function(effect
) {
804 effect
.element
.hide().makeClipping().makePositioned();
806 afterFinishInternal: function(effect
) {
808 [ new Effect
.Opacity(effect
.element
, { sync
: true, to
: 1.0, from: 0.0, transition
: options
.opacityTransition
}),
809 new Effect
.Move(effect
.element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
}),
810 new Effect
.Scale(effect
.element
, 100, {
811 scaleMode
: { originalHeight
: dims
.height
, originalWidth
: dims
.width
},
812 sync
: true, scaleFrom
: window
.opera
? 1 : 0, transition
: options
.scaleTransition
, restoreAfterFinish
: true})
814 beforeSetup: function(effect
) {
815 effect
.effects
[0].element
.setStyle({height
: '0px'}).show();
817 afterFinishInternal: function(effect
) {
818 effect
.effects
[0].element
.undoClipping().undoPositioned().setStyle(oldStyle
);
826 Effect
.Shrink = function(element
) {
827 element
= $(element
);
828 var options
= Object
.extend({
830 moveTransition
: Effect
.Transitions
.sinoidal
,
831 scaleTransition
: Effect
.Transitions
.sinoidal
,
832 opacityTransition
: Effect
.Transitions
.none
833 }, arguments
[1] || { });
835 top
: element
.style
.top
,
836 left
: element
.style
.left
,
837 height
: element
.style
.height
,
838 width
: element
.style
.width
,
839 opacity
: element
.getInlineOpacity() };
841 var dims
= element
.getDimensions();
844 switch (options
.direction
) {
861 moveX
= dims
.width
/ 2;
862 moveY
= dims
.height
/ 2;
866 return new Effect
.Parallel(
867 [ new Effect
.Opacity(element
, { sync
: true, to
: 0.0, from: 1.0, transition
: options
.opacityTransition
}),
868 new Effect
.Scale(element
, window
.opera
? 1 : 0, { sync
: true, transition
: options
.scaleTransition
, restoreAfterFinish
: true}),
869 new Effect
.Move(element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
})
871 beforeStartInternal: function(effect
) {
872 effect
.effects
[0].element
.makePositioned().makeClipping();
874 afterFinishInternal: function(effect
) {
875 effect
.effects
[0].element
.hide().undoClipping().undoPositioned().setStyle(oldStyle
); }
880 Effect
.Pulsate = function(element
) {
881 element
= $(element
);
882 var options
= arguments
[1] || { };
883 var oldOpacity
= element
.getInlineOpacity();
884 var transition
= options
.transition
|| Effect
.Transitions
.sinoidal
;
885 var reverser = function(pos
){ return transition(1-Effect
.Transitions
.pulse(pos
, options
.pulses
)) };
886 reverser
.bind(transition
);
887 return new Effect
.Opacity(element
,
888 Object
.extend(Object
.extend({ duration
: 2.0, from: 0,
889 afterFinishInternal: function(effect
) { effect
.element
.setStyle({opacity
: oldOpacity
}); }
890 }, options
), {transition
: reverser
}));
893 Effect
.Fold = function(element
) {
894 element
= $(element
);
896 top
: element
.style
.top
,
897 left
: element
.style
.left
,
898 width
: element
.style
.width
,
899 height
: element
.style
.height
};
900 element
.makeClipping();
901 return new Effect
.Scale(element
, 5, Object
.extend({
904 afterFinishInternal: function(effect
) {
905 new Effect
.Scale(element
, 1, {
908 afterFinishInternal: function(effect
) {
909 effect
.element
.hide().undoClipping().setStyle(oldStyle
);
911 }}, arguments
[1] || { }));
914 Effect
.Morph
= Class
.create(Effect
.Base
, {
915 initialize: function(element
) {
916 this.element
= $(element
);
917 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
918 var options
= Object
.extend({
920 }, arguments
[1] || { });
922 if (!Object
.isString(options
.style
)) this.style
= $H(options
.style
);
924 if (options
.style
.include(':'))
925 this.style
= options
.style
.parseStyle();
927 this.element
.addClassName(options
.style
);
928 this.style
= $H(this.element
.getStyles());
929 this.element
.removeClassName(options
.style
);
930 var css
= this.element
.getStyles();
931 this.style
= this.style
.reject(function(style
) {
932 return style
.value
== css
[style
.key
];
934 options
.afterFinishInternal = function(effect
) {
935 effect
.element
.addClassName(effect
.options
.style
);
936 effect
.transforms
.each(function(transform
) {
937 effect
.element
.style
[transform
.style
] = '';
946 function parseColor(color
){
947 if (!color
|| ['rgba(0, 0, 0, 0)','transparent'].include(color
)) color
= '#ffffff';
948 color
= color
.parseColor();
949 return $R(0,2).map(function(i
){
950 return parseInt( color
.slice(i
*2+1,i
*2+3), 16 )
953 this.transforms
= this.style
.map(function(pair
){
954 var property
= pair
[0], value
= pair
[1], unit
= null;
956 if (value
.parseColor('#zzzzzz') != '#zzzzzz') {
957 value
= value
.parseColor();
959 } else if (property
== 'opacity') {
960 value
= parseFloat(value
);
961 if (Prototype
.Browser
.IE
&& (!this.element
.currentStyle
.hasLayout
))
962 this.element
.setStyle({zoom
: 1});
963 } else if (Element
.CSS_LENGTH
.test(value
)) {
964 var components
= value
.match(/^([\+\-]?[0-9\.]+)(.*)$/);
965 value
= parseFloat(components
[1]);
966 unit
= (components
.length
== 3) ? components
[2] : null;
969 var originalValue
= this.element
.getStyle(property
);
971 style
: property
.camelize(),
972 originalValue
: unit
=='color' ? parseColor(originalValue
) : parseFloat(originalValue
|| 0),
973 targetValue
: unit
=='color' ? parseColor(value
) : value
,
976 }.bind(this)).reject(function(transform
){
978 (transform
.originalValue
== transform
.targetValue
) ||
980 transform
.unit
!= 'color' &&
981 (isNaN(transform
.originalValue
) || isNaN(transform
.targetValue
))
986 update: function(position
) {
987 var style
= { }, transform
, i
= this.transforms
.length
;
989 style
[(transform
= this.transforms
[i
]).style
] =
990 transform
.unit
=='color' ? '#'+
991 (Math
.round(transform
.originalValue
[0]+
992 (transform
.targetValue
[0]-transform
.originalValue
[0])*position
)).toColorPart() +
993 (Math
.round(transform
.originalValue
[1]+
994 (transform
.targetValue
[1]-transform
.originalValue
[1])*position
)).toColorPart() +
995 (Math
.round(transform
.originalValue
[2]+
996 (transform
.targetValue
[2]-transform
.originalValue
[2])*position
)).toColorPart() :
997 (transform
.originalValue
+
998 (transform
.targetValue
- transform
.originalValue
) * position
).toFixed(3) +
999 (transform
.unit
=== null ? '' : transform
.unit
);
1000 this.element
.setStyle(style
, true);
1004 Effect
.Transform
= Class
.create({
1005 initialize: function(tracks
){
1007 this.options
= arguments
[1] || { };
1008 this.addTracks(tracks
);
1010 addTracks: function(tracks
){
1011 tracks
.each(function(track
){
1013 var data
= track
.values().first();
1014 this.tracks
.push($H({
1015 ids
: track
.keys().first(),
1016 effect
: Effect
.Morph
,
1017 options
: { style
: data
}
1023 return new Effect
.Parallel(
1024 this.tracks
.map(function(track
){
1025 var ids
= track
.get('ids'), effect
= track
.get('effect'), options
= track
.get('options');
1026 var elements
= [$(ids
) || $$(ids
)].flatten();
1027 return elements
.map(function(e
){ return new effect(e
, Object
.extend({ sync
:true }, options
)) });
1034 Element
.CSS_PROPERTIES
= $w(
1035 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1036 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1037 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1038 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1039 'fontSize fontWeight height left letterSpacing lineHeight ' +
1040 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1041 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1042 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1043 'right textIndent top width wordSpacing zIndex');
1045 Element
.CSS_LENGTH
= /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1047 String
.__parseStyleElement
= document
.createElement('div');
1048 String
.prototype.parseStyle = function(){
1049 var style
, styleRules
= $H();
1050 if (Prototype
.Browser
.WebKit
)
1051 style
= new Element('div',{style
:this}).style
;
1053 String
.__parseStyleElement
.innerHTML
= '<div style="' + this + '"></div>';
1054 style
= String
.__parseStyleElement
.childNodes
[0].style
;
1057 Element
.CSS_PROPERTIES
.each(function(property
){
1058 if (style
[property
]) styleRules
.set(property
, style
[property
]);
1061 if (Prototype
.Browser
.IE
&& this.include('opacity'))
1062 styleRules
.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1067 if (document
.defaultView
&& document
.defaultView
.getComputedStyle
) {
1068 Element
.getStyles = function(element
) {
1069 var css
= document
.defaultView
.getComputedStyle($(element
), null);
1070 return Element
.CSS_PROPERTIES
.inject({ }, function(styles
, property
) {
1071 styles
[property
] = css
[property
];
1076 Element
.getStyles = function(element
) {
1077 element
= $(element
);
1078 var css
= element
.currentStyle
, styles
;
1079 styles
= Element
.CSS_PROPERTIES
.inject({ }, function(hash
, property
) {
1080 hash
.set(property
, css
[property
]);
1083 if (!styles
.opacity
) styles
.set('opacity', element
.getOpacity());
1089 morph: function(element
, style
) {
1090 element
= $(element
);
1091 new Effect
.Morph(element
, Object
.extend({ style
: style
}, arguments
[2] || { }));
1094 visualEffect: function(element
, effect
, options
) {
1095 element
= $(element
)
1096 var s
= effect
.dasherize().camelize(), klass
= s
.charAt(0).toUpperCase() + s
.substring(1);
1097 new Effect
[klass
](element
, options
);
1100 highlight: function(element
, options
) {
1101 element
= $(element
);
1102 new Effect
.Highlight(element
, options
);
1107 $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1108 'pulsate shake puff squish switchOff dropOut').each(
1110 Effect
.Methods
[effect
] = function(element
, options
){
1111 element
= $(element
);
1112 Effect
[effect
.charAt(0).toUpperCase() + effect
.substring(1)](element
, options
);
1118 $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1119 function(f
) { Effect
.Methods
[f
] = Element
[f
]; }
1122 Element
.addMethods(Effect
.Methods
);