今回解説するeffects.
Web開発によく使われるライブラリとして、
今回の解説の見どころは、
effects.
それではコードを見ていきましょう。
0001:// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
0002:
0003:// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
0004:// Contributors:
0005://  Justin Palmer (http://encytemedia.com/)
0006://  Mark Pilgrim (http://diveintomark.org/)
0007://  Martin Bialasinki
0008:// 
0009:// script.aculo.us is freely distributable under the terms of an MIT-style license.
0010:// For details, see the script.aculo.us web site: http://script.aculo.us/ 
0011:1~11行目は、
0012:// converts rgb() and #xxx to #xxxxxx format,  
0013:// returns self (or first argument) if not convertable  
0014:String.prototype.parseColor = function() {  
0015:  var color = '#';
0016:  if (this.slice(0,4) == 'rgb(') {  
0017:    var cols = this.slice(4,this.length-1).split(',');  
0018:    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
0019:  } else {  
0020:    if (this.slice(0,1) == '#') {  
0021:      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
0022:      if (this.length==7) color = this.toLowerCase();  
0023:    }  
0024:  }  
0025:  return (color.length==7 ? color : (arguments[0] || this));  
0026:};
0027:
0028:/*--------------------------------------------------------------------------*/
0029:12~28行目のparseColorは、
16行目で、
17行目で、
18行目で、
20行目で、
21行目で、
22行目で、
25行目で、
0030:Element.collectTextNodes = function(element) {  
0031:  return $A($(element).childNodes).collect( function(node) {
0032:    return (node.nodeType==3 ? node.nodeValue : 
0033:      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
0034:  }).flatten().join('');
0035:};
0036:30~36行目のcollectTextNodesは、
31行目で、
32行目で、
33行目で、
34行目で、
0037:Element.collectTextNodesIgnoreClass = function(element, className) {  
0038:  return $A($(element).childNodes).collect( function(node) {
0039:    return (node.nodeType==3 ? node.nodeValue : 
0040:      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
0041:        Element.collectTextNodesIgnoreClass(node, className) : ''));
0042:  }).flatten().join('');
0043:};
0044:37~44行目のcollectTextNodesIgnoreClassは、
0045:Element.setContentZoom = function(element, percent) {
0046:  element = $(element);  
0047:  element.setStyle({fontSize: (percent/100) + 'em'});   
0048:  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
0049:  return element;
0050:};
0051:45~51行目のsetContentZoomは、
47行目で、 48行目で、 52~55行目のgetInlineOpacityは、 53行目で、 56~66行目のforceRerenderingは、 67~71行目の_elementDoesNotExistErrorは、 72~104行目のTransitionsには、 105~113行目のDefaultOptionsは、 114~130行目のtagifyTextは、 116行目で、 119行目で、 120行目で、 121行目で、 122~125行目で、 124行目で、 127行目で、 131~149行目のmultipleは、 133~136行目で、 138行目で、 140~143行目で、 146行目で、 150~154行目のPAIRSは、 155~165行目のtoggleは、 157行目で、 158行目で、 161行目で、 166行目で、 Effect. 171~174行目のinitializeは、 172行目で、 173行目で、 175~177行目の_eachは、 178~209行目のaddは、 179行目で、 181行目で、 184~199行目で、 204行目で、 207行目で、 210~216行目のremoveは、 211行目で、 212行目で、 217~223行目のloopは、 218行目で、 219行目で、 Effect. 225行目で、 226~231行目のgetは、 233行目で、 全てのエフェクトの基礎となるクラスです。 236行目のpositionには、 237~271行目のstartは、 238~243行目のcodeForEventは、 245行目で、 246行目で、 247行目で、 248行目で、 249行目で、 250行目で、 251行目で、 252行目で、 254~265行目は後述します。 267行目で、 268行目で、 254~265行目でrenderメソッドを、 ここで、 272~289行目のloopは、 274行目で、 275行目で、 276行目で、 277行目で、 278行目で、 279行目で、 開始時刻後の場合、 282行目で、 283行目で、 284行目で、 290~295行目のcancelは、 296~299行目のeventは、 300~306行目のinspectは、 Effectクラスでは、 309~312行目のinitializeは、 310行目で、 311行目で、 313~315行目のupdateは、 314行目で、 316~326行目のfinishは、 配列のエフェクトのそれぞれに、 Effect. 328~336行目のinitializeは、 322行目で、 337~340行目のupdateは、 持続時間0で、 343~345行目のinitializeは、 346行目で、 要素の透明度が変化するエフェクトのクラスです。デフォルトでは現在の透明度から不透明になるフェードインエフェクトを作ります。 350~361行目のinitializeは初期化をする関数です。2番めの引数にオプションをとることができます。 354行目で、 356行目で、 362~365行目のupdateは、 要素の位置が変化するエフェクトのクラスです。デフォルトでは幅0で移動する 368~377行目のinitializeは、 371行目で、 378~386行目のsetupは、 379行目で、 380,381行目で、 382行目で、 387~393行目のupdateは、 395~400行目のEffect. 要素の表示の大きさが変化するエフェクトのクラスです。終了時の大きさの倍率をパーセントで指定 402~415行目のinitializeは、 416~446行目のsetupは、 417行目で、 418行目で、 421~426行目で、 428~434行目で、 436行目で、 438~445行目で、 439行目で、 441行目で、 443行目で、 447~452行目のupdateは、 448行目で、 449行目で、 451行目で、 453~455行目のfinishは、 454行目で、 456~474行目のsetDimensionsは、 458行目で、 459行目で、 460行目で、 461、 464、 467、 471行目で、 要素の背景色が変化するエフェクトのクラスです。デフォルトでは明るい黄色から元の背景色 476~481行目のinitializeは、 479行目で、 482~498行目のsetupは、 484行目で、 487行目で、 491行目で、 493行目で、 496行目の、 497行目の、 499~502行目のupdateは、 500行目で、 503~509行目のfinishは終了時に呼ばれる関数です。 504行目で、 要素に向かってメインウィンドウをスクロールするエフェクトのクラスです。 512行目で、 513行目で、 514行目で、 516行目で、 518行目で、 522行目で、0052:Element.getInlineOpacity = function(element){
0053:  return $(element).style.opacity || '';
0054:};
0055:0056:Element.forceRerendering = function(element) {
0057:  try {
0058:    element = $(element);
0059:    var n = document.createTextNode(' ');
0060:    element.appendChild(n);
0061:    element.removeChild(n);
0062:  } catch(e) { }
0063:};
0064:
0065:/*--------------------------------------------------------------------------*/
0066:Effect
0067:var Effect = {
0068:  _elementDoesNotExistError: {
0069:    name: 'ElementDoesNotExistError',
0070:    message: 'The specified DOM element does not exist, but is required for this effect to operate'
0071:  },0072:  Transitions: {
0073:    linear: Prototype.K,
0074:    sinoidal: function(pos) {
0075:      return (-Math.cos(pos*Math.PI)/2) + 0.5;
0076:    },
0077:    reverse: function(pos) {
0078:      return 1-pos;
0079:    },
0080:    flicker: function(pos) {
0081:      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
0082:      return pos > 1 ? 1 : pos;
0083:    },
0084:    wobble: function(pos) {
0085:      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
0086:    },
0087:    pulse: function(pos, pulses) { 
0088:      pulses = pulses || 5; 
0089:      return (
0090:        ((pos % (1/pulses)) * pulses).round() == 0 ? 
0091:              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
0092:          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
0093:        );
0094:    },
0095:    spring: function(pos) { 
0096:      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
0097:    },
0098:    none: function(pos) {
0099:      return 0;
0100:    },
0101:    full: function(pos) {
0102:      return 1;
0103:    }
0104:  },
linear 比例の関数(function(x) { return x }) 
sinoidal sin関数 
reverse 逆比例の関数 
flicker 乱数でゆらめいてから収束する関数 
wobble 徐々に激しくなる震えのような関数 
pulse 三角波のような関数 
spring バネのようにビョーンとする関数 
none 常に0の定数関数   full 常に1の定数関数   0105:  DefaultOptions: {
0106:    duration:   1.0,   // seconds
0107:    fps:        100,   // 100= assume 66fps max.
0108:    sync:       false, // true for combining
0109:    from:       0.0,
0110:    to:         1.0,
0111:    delay:      0.0,
0112:    queue:      'parallel'
0113:  },
0114:  tagifyText: function(element) {
0115:    var tagifyStyle = 'position:relative';
0116:    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
0117:    
0118:    element = $(element);
0119:    $A(element.childNodes).each( function(child) {
0120:      if (child.nodeType==3) {
0121:        child.nodeValue.toArray().each( function(character) {
0122:          element.insertBefore(
0123:            new Element('span', {style: tagifyStyle}).update(
0124:              character == ' ' ? String.fromCharCode(160) : character), 
0125:              child);
0126:        });
0127:        Element.remove(child);
0128:      }
0129:    });
0130:  },0131:  multiple: function(element, effect) {
0132:    var elements;
0133:    if (((typeof element == 'object') || 
0134:        Object.isFunction(element)) && 
0135:       (element.length))
0136:      elements = element;
0137:    else
0138:      elements = $(element).childNodes;
0139:      
0140:    var options = Object.extend({
0141:      speed: 0.1,
0142:      delay: 0.0
0143:    }, arguments[2] || { });
0144:    var masterDelay = options.delay;
0145:
0146:    $A(elements).each( function(element, index) {
0147:      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
0148:    });
0149:  },0150:  PAIRS: {
0151:    'slide':  ['SlideDown','SlideUp'],
0152:    'blind':  ['BlindDown','BlindUp'],
0153:    'appear': ['Appear','Fade']
0154:  },0155:  toggle: function(element, effect) {
0156:    element = $(element);
0157:    effect = (effect || 'appear').toLowerCase();
0158:    var options = Object.extend({
0159:      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
0160:    }, arguments[2] || { });
0161:    Effect[element.visible() ? 
0162:      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
0163:  }
0164:};
0165:0166:Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
0167:Effect.
0168:/* ------------- core effects ------------- */
0169:
0170:Effect.ScopedQueue = Class.create(Enumerable, {
0171:  initialize: function() {
0172:    this.effects  = [];
0173:    this.interval = null;    
0174:  },0175:  _each: function(iterator) {
0176:    this.effects._each(iterator);
0177:  },0178:  add: function(effect) {
0179:    var timestamp = new Date().getTime();
0180:    
0181:    var position = Object.isString(effect.options.queue) ? 
0182:      effect.options.queue : effect.options.queue.position;
0183:    
0184:    switch(position) {
0185:      case 'front':
0186:        // move unstarted effects after this effect  
0187:        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
0188:            e.startOn  += effect.finishOn;
0189:            e.finishOn += effect.finishOn;
0190:          });
0191:        break;
0192:      case 'with-last':
0193:        timestamp = this.effects.pluck('startOn').max() || timestamp;
0194:        break;
0195:      case 'end':
0196:        // start effect after last queued effect has finished
0197:        timestamp = this.effects.pluck('finishOn').max() || timestamp;
0198:        break;
0199:    }
0200:    
0201:    effect.startOn  += timestamp;
0202:    effect.finishOn += timestamp;
0203:
0204:    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
0205:      this.effects.push(effect);
0206:    
0207:    if (!this.interval)
0208:      this.interval = setInterval(this.loop.bind(this), 15);
0209:  },
0210:  remove: function(effect) {
0211:    this.effects = this.effects.reject(function(e) { return e==effect });
0212:    if (this.effects.length == 0) {
0213:      clearInterval(this.interval);
0214:      this.interval = null;
0215:    }
0216:  },0217:  loop: function() {
0218:    var timePos = new Date().getTime();
0219:    for(var i=0, len=this.effects.length;i<len;i++) 
0220:      this.effects[i] && this.effects[i].loop(timePos);
0221:  }
0222:});
0223:Effect.
0224:Effect.Queues = {
0225:  instances: $H(),
0226:  get: function(queueName) {
0227:    if (!Object.isString(queueName)) return queueName;
0228:    
0229:    return this.instances.get(queueName) ||
0230:      this.instances.set(queueName, new Effect.ScopedQueue());
0231:  }
0232:};Effect.
0233:Effect.Queue = Effect.Queues.get('global');
0234:
Effect.
0235:Effect.Base = Class.create({
0236:  position: null,0237:  start: function(options) {
0238:    function codeForEvent(options,eventName){
0239:      return (
0240:        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
0241:        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
0242:      );
0243:    }
0244:    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
0245:    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
0246:    this.currentFrame = 0;
0247:    this.state        = 'idle';
0248:    this.startOn      = this.options.delay*1000;
0249:    this.finishOn     = this.startOn+(this.options.duration*1000);
0250:    this.fromToDelta  = this.options.to-this.options.from;
0251:    this.totalTime    = this.finishOn-this.startOn;
0252:    this.totalFrames  = this.options.fps*this.options.duration;
0253:    
0254:    eval('this.render = function(pos){ '+
0255:      'if (this.state=="idle"){this.state="running";'+
0256:      codeForEvent(this.options,'beforeSetup')+
0257:      (this.setup ? 'this.setup();':'')+ 
0258:      codeForEvent(this.options,'afterSetup')+
0259:      '};if (this.state=="running"){'+
0260:      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
0261:      'this.position=pos;'+
0262:      codeForEvent(this.options,'beforeUpdate')+
0263:      (this.update ? 'this.update(pos);':'')+
0264:      codeForEvent(this.options,'afterUpdate')+
0265:      '}}');
0266:    
0267:    this.event('beforeStart');
0268:    if (!this.options.sync)
0269:      Effect.Queues.get(Object.isString(this.options.queue) ? 
0270:        'global' : this.options.queue.scope).add(this);
0271:  },ループ内不変式をevalで削除する
this.render = function(pos){
  if (this.state=="idle"){
    this.state="running";
    if (this.options['beforeSetupInternal']) this.options['beforeSetupInternal'](this);
    if (this.options['beforeSetup']) this.options['beforeSetup'](this);
    if (this.setup) this.setup();
    if (this.options['afterSetupInternal']) this.options['afterSetupInternal'](this);
    if (this.options['afterSetup']) this.options['afterSetup'](this);
  };
  if (this.state=="running"){
    pos=this.options.transition(pos)* this.fromToDelta + this.options.from;
    this.position=pos;
    if (this.options['beforeUpdateInternal']) this.options['beforeUpdateInternal'](this);
    if (this.options['beforeUpdate']) this.options['beforeUpdate'](this);
    if (this.update) this.update(pos);
    if (this.options['afterUpdateInternal']) this.options['afterUpdateInternal'](this);
    if (this.options['afterUpdate']) this.options['afterUpdate'](this);
  }
};this.render = function(pos){
  if (this.state=="idle"){
    this.state="running";
  };
  if (this.state=="running"){
    pos=this.options.transition(pos)* this.fromToDelta + this.options.from;
    this.position=pos;
    this.update(pos);
  }
};0272:  loop: function(timePos) {
0273:    if (timePos >= this.startOn) {
0274:      if (timePos >= this.finishOn) {
0275:        this.render(1.0);
0276:        this.cancel();
0277:        this.event('beforeFinish');
0278:        if (this.finish) this.finish(); 
0279:        this.event('afterFinish');
0280:        return;  
0281:      }
0282:      var pos   = (timePos - this.startOn) / this.totalTime,
0283:          frame = (pos * this.totalFrames).round();
0284:      if (frame > this.currentFrame) {
0285:        this.render(pos);
0286:        this.currentFrame = frame;
0287:      }
0288:    }
0289:  },0290:  cancel: function() {
0291:    if (!this.options.sync)
0292:      Effect.Queues.get(Object.isString(this.options.queue) ? 
0293:        'global' : this.options.queue.scope).remove(this);
0294:    this.state = 'finished';
0295:  },0296:  event: function(eventName) {
0297:    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
0298:    if (this.options[eventName]) this.options[eventName](this);
0299:  },0300:  inspect: function() {
0301:    var data = $H();
0302:    for(property in this)
0303:      if (!Object.isFunction(this[property])) data.set(property, this[property]);
0304:    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
0305:  }
0306:});
0307:Effect.
0308:Effect.Parallel = Class.create(Effect.Base, {
0309:  initialize: function(effects) {
0310:    this.effects = effects || [];
0311:    this.start(arguments[1]);
0312:  },0313:  update: function(position) {
0314:    this.effects.invoke('render', position);
0315:  },0316:  finish: function(position) {
0317:    this.effects.each( function(effect) {
0318:      effect.render(1.0);
0319:      effect.cancel();
0320:      effect.event('beforeFinish');
0321:      if (effect.finish) effect.finish(position);
0322:      effect.event('afterFinish');
0323:    });
0324:  }
0325:});
0326:Effect.
0327:Effect.Tween = Class.create(Effect.Base, {
0328:  initialize: function(object, from, to) {
0329:    object = Object.isString(object) ? $(object) : object;
0330:    var args = $A(arguments), method = args.last(), 
0331:      options = args.length == 5 ? args[3] : null;
0332:    this.method = Object.isFunction(method) ? method.bind(object) :
0333:      Object.isFunction(object[method]) ? object[method].bind(object) : 
0334:      function(value) { object[method] = value };
0335:    this.start(Object.extend({ from: from, to: to }, options || { }));
0336:  },0337:  update: function(position) {
0338:    this.method(position);
0339:  }
0340:});
0341:Effect.
0342:Effect.Event = Class.create(Effect.Base, {
0343:  initialize: function() {
0344:    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
0345:  },0346:  update: Prototype.emptyFunction
0347:});
0348:Effect.
0349:Effect.Opacity = Class.create(Effect.Base, {
0350:  initialize: function(element) {
0351:    this.element = $(element);
0352:    if (!this.element) throw(Effect._elementDoesNotExistError);
0353:    // make this work on IE on elements without 'layout'
0354:    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
0355:      this.element.setStyle({zoom: 1});
0356:    var options = Object.extend({
0357:      from: this.element.getOpacity() || 0.0,
0358:      to:   1.0
0359:    }, arguments[1] || { });
0360:    this.start(options);
0361:  },0362:  update: function(position) {
0363:    this.element.setOpacity(position);
0364:  }
0365:});
0366:Effect.
0367:Effect.Move = Class.create(Effect.Base, {
0368:  initialize: function(element) {
0369:    this.element = $(element);
0370:    if (!this.element) throw(Effect._elementDoesNotExistError);
0371:    var options = Object.extend({
0372:      x:    0,
0373:      y:    0,
0374:      mode: 'relative'
0375:    }, arguments[1] || { });
0376:    this.start(options);
0377:  },0378:  setup: function() {
0379:    this.element.makePositioned();
0380:    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
0381:    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
0382:    if (this.options.mode == 'absolute') {
0383:      this.options.x = this.options.x - this.originalLeft;
0384:      this.options.y = this.options.y - this.originalTop;
0385:    }
0386:  },0387:  update: function(position) {
0388:    this.element.setStyle({
0389:      left: (this.options.x  * position + this.originalLeft).round() + 'px',
0390:      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
0391:    });
0392:  }
0393:});
0394:0395:// for backwards compatibility
0396:Effect.MoveBy = function(element, toTop, toLeft) {
0397:  return new Effect.Move(element, 
0398:    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
0399:};
0400:Effect.
0401:Effect.Scale = Class.create(Effect.Base, {
0402:  initialize: function(element, percent) {
0403:    this.element = $(element);
0404:    if (!this.element) throw(Effect._elementDoesNotExistError);
0405:    var options = Object.extend({
0406:      scaleX: true,
0407:      scaleY: true,
0408:      scaleContent: true,
0409:      scaleFromCenter: false,
0410:      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
0411:      scaleFrom: 100.0,
0412:      scaleTo:   percent
0413:    }, arguments[2] || { });
0414:    this.start(options);
0415:  },
0416:  setup: function() {
0417:    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
0418:    this.elementPositioning = this.element.getStyle('position');
0419:    
0420:    this.originalStyle = { };
0421:    ['top','left','width','height','fontSize'].each( function(k) {
0422:      this.originalStyle[k] = this.element.style[k];
0423:    }.bind(this));
0424:      
0425:    this.originalTop  = this.element.offsetTop;
0426:    this.originalLeft = this.element.offsetLeft;
0427:    
0428:    var fontSize = this.element.getStyle('font-size') || '100%';
0429:    ['em','px','%','pt'].each( function(fontSizeType) {
0430:      if (fontSize.indexOf(fontSizeType)>0) {
0431:        this.fontSize     = parseFloat(fontSize);
0432:        this.fontSizeType = fontSizeType;
0433:      }
0434:    }.bind(this));
0435:    
0436:    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
0437:    
0438:    this.dims = null;
0439:    if (this.options.scaleMode=='box')
0440:      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
0441:    if (/^content/.test(this.options.scaleMode))
0442:      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
0443:    if (!this.dims)
0444:      this.dims = [this.options.scaleMode.originalHeight,
0445:                   this.options.scaleMode.originalWidth];
0446:  },0447:  update: function(position) {
0448:    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
0449:    if (this.options.scaleContent && this.fontSize)
0450:      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
0451:    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
0452:  },0453:  finish: function(position) {
0454:    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
0455:  },0456:  setDimensions: function(height, width) {
0457:    var d = { };
0458:    if (this.options.scaleX) d.width = width.round() + 'px';
0459:    if (this.options.scaleY) d.height = height.round() + 'px';
0460:    if (this.options.scaleFromCenter) {
0461:      var topd  = (height - this.dims[0])/2;
0462:      var leftd = (width  - this.dims[1])/2;
0463:      if (this.elementPositioning == 'absolute') {
0464:        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
0465:        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
0466:      } else {
0467:        if (this.options.scaleY) d.top = -topd + 'px';
0468:        if (this.options.scaleX) d.left = -leftd + 'px';
0469:      }
0470:    }
0471:    this.element.setStyle(d);
0472:  }
0473:});
0474:Effect.
0475:Effect.Highlight = Class.create(Effect.Base, {
0476:  initialize: function(element) {
0477:    this.element = $(element);
0478:    if (!this.element) throw(Effect._elementDoesNotExistError);
0479:    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
0480:    this.start(options);
0481:  },0482:  setup: function() {
0483:    // Prevent executing on elements not in the layout flow
0484:    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
0485:    // Disable background image during the effect
0486:    this.oldStyle = { };
0487:    if (!this.options.keepBackgroundImage) {
0488:      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
0489:      this.element.setStyle({backgroundImage: 'none'});
0490:    }
0491:    if (!this.options.endcolor)
0492:      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
0493:    if (!this.options.restorecolor)
0494:      this.options.restorecolor = this.element.getStyle('background-color');
0495:    // init color calculations
0496:    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
0497:    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));
0498:  },0499:  update: function(position) {
0500:    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
0501:      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
0502:  },0503:  finish: function() {
0504:    this.element.setStyle(Object.extend(this.oldStyle, {
0505:      backgroundColor: this.options.restorecolor
0506:    }));
0507:  }
0508:});
0509:Effect.
0510:Effect.ScrollTo = function(element) {
0511:  var options = arguments[1] || { },
0512:    scrollOffsets = document.viewport.getScrollOffsets(),
0513:    elementOffsets = $(element).cumulativeOffset(),
0514:    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  
0515:
0516:  if (options.offset) elementOffsets[1] += options.offset;
0517:
0518:  return new Effect.Tween(null,
0519:    scrollOffsets.top,
0520:    elementOffsets[1] > max ? max : elementOffsets[1],
0521:    options,
0522:    function(p){ scrollTo(scrollOffsets.left, p.round()) }
0523:  );
0524:};
0525: