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