script.aculo.usを読み解く

第6回effects.js(後編)基礎エフェクトの組み合わせからなる15種類の複合エフェクト

前回の基礎エフェクトの解説に引き続いて、今回は、それら基礎エフェクトの組み合わせからなる15種類の複合エフェクトを解説します。

このような複雑でドラマチックなエフェクトが、実は単純な、移動や拡大縮小や透明度変化といった基礎エフェクトの組み合わせからできているというのは、このライブラリのクラス設計が成功している証拠でしょう。

基礎エフェクトを組み合わせてできる15種類の複合エフェクト
Effect.Fadeフェードアウトするエフェクト Effect.Appearフェードインするエフェクト Effect.Puffホワッと消えるエフェクト Effect.BlindUpすだれを上げるようなエフェクト Effect.BlindDownすだれを降ろすようなエフェクト Effect.SwitchOffテレビのスイッチを切るように消えるエフェクト Effect.DropOutポロリと落ちるように消えるエフェクト Effect.Shakeブルブルと左右に震えるエフェクト Effect.SlideDownお店のシャッターを降ろすようなエフェクト Effect.SlideUpお店のシャッターを上げるようなエフェクト Effect.Squish縮小して消えるエフェクト Effect.Grow飛び出すように拡大して現れるエフェクト Effect.Shrink吸い込まれるように縮小して消えるエフェクト Effect.Pulsate透明と不透明の間で点滅するエフェクト Effect.Fold縦に縮んだあと、横に縮むエフェクト

それでは前回の続きからコードを見ていきましょう。

Effect.Fade

0526:/* ------------- combination effects ------------- */
0527:
0528:Effect.Fade = function(element) {
0529:  element = $(element);
0530:  var oldOpacity = element.getInlineOpacity();
0531:  var options = Object.extend({
0532:    from: element.getOpacity() || 1.0,
0533:    to:   0.0,
0534:    afterFinishInternal: function(effect) { 
0535:      if (effect.options.to!=0) return;
0536:      effect.element.hide().setStyle({opacity: oldOpacity}); 
0537:    }
0538:  }, arguments[1] || { });
0539:  return new Effect.Opacity(element,options);
0540:};
0541:

526~541行目のEffect.Fadeは、フェードアウトをするエフェクトです。Effect.Opacityからできています。Effect.Opacityでだんだん透明にするまでは同じですが、最後に、要素をhideで非表示にするところが違います。

530行目で、エフェクトをかける前の透明度を保存しておきます。

532行目で、初期値は、現在の透明度か、それが取得できなければ1.0(不透明)です。

533行目で、目標値は、デフォルトでは0.0(透明)です。

534行目で、終了後のフックで、要素をhideで非表示にします。透明度はエフェクトをかける前の値に戻すようにします。これから何度もこのinternalフックが出てきます。これには、エフェクトを崩さないために必須なもので、ライブラリの利用者にいじってほしくない、というニュアンスがあります。

536行目の、hideメソッドとsetStyleメソッドのチェーンが美しいです。

539行目で、以上のオプションでEffect.Opacityを使います。

Effect.Appear

0542:Effect.Appear = function(element) {
0543:  element = $(element);
0544:  var options = Object.extend({
0545:  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
0546:  to:   1.0,
0547:  // force Safari to render floated elements properly
0548:  afterFinishInternal: function(effect) {
0549:    effect.element.forceRerendering();
0550:  },
0551:  beforeSetup: function(effect) {
0552:    effect.element.setOpacity(effect.options.from).show(); 
0553:  }}, arguments[1] || { });
0554:  return new Effect.Opacity(element,options);
0555:};
0556:

542~556行目のEffect.Appearは、フェードインをするエフェクトです。Effect.Opacityからできています。 Effect.Opacityでだんだん不透明にするまでは同じですが、エフェクトの前に要素をshowで表示にするところが違います。

545行目で、初期値は、要素が非表示ならば0.0(透明)を、表示ならば現在の透明度を、それが取得できなければ0.0(透明)です。

546行目で、目標値は、デフォルトでは1.0(不透明)です。

548行目で、終了後のフックで、forceRerenderingを呼びます。これはSafariのバグを回避するためです。

551行目で、初期化前のフックで、要素の透明度を初期値に戻してから、showで表示にします。

554行目で、以上のオプションでEffect.Opacityを使います。

Effect.Puff

0557:Effect.Puff = function(element) {
0558:  element = $(element);
0559:  var oldStyle = { 
0560:    opacity: element.getInlineOpacity(), 
0561:    position: element.getStyle('position'),
0562:    top:  element.style.top,
0563:    left: element.style.left,
0564:    width: element.style.width,
0565:    height: element.style.height
0566:  };
0567:  return new Effect.Parallel(
0568:   [ new Effect.Scale(element, 200, 
0569:      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
0570:     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
0571:     Object.extend({ duration: 1.0, 
0572:      beforeSetupInternal: function(effect) {
0573:        Position.absolutize(effect.effects[0].element)
0574:      },
0575:      afterFinishInternal: function(effect) {
0576:         effect.effects[0].element.hide().setStyle(oldStyle); }
0577:     }, arguments[1] || { })
0578:   );
0579:};
0580:

557~580行目のEffect.Puffは、ホワッと消えるエフェクトです。だんだん大きくするEffect.Scaleと、だんだん透明にするEffect.Opacityを、Effect.Parallelを使って並列に動かすことからできています。並列に動かすために、sync : true として、キューで管理されないようにする必要があります。

559~566行目で、エフェクトをかける前のCSSの値をいくつか保存しておきます。

567行目で、Effect.Parallelで2つのエフェクト(ScaleとOpacity)を並列に動かします。

568行目で、Effect.Scaleで大きさを200%(2倍)にします。中心を動かさない拡大をします。内容のフォントサイズも拡大します。restoreAfterFinishは、エフェクトの後でCSSを元に戻すオプションです(これは先ほど保存したCSSと一部かぶっている項目もあります⁠⁠。

570行目で、Effect.Opacityでだんだん透明にします。よって目標値は0.0(透明)です。

572行目で、初期化前のフックで、Position.absolutizeを使って要素をposition: 'absolute'に変更します。

575行目で、終了後のフックで、要素を隠し、CSSのいくつかの値をエフェクトをかける前の値に戻すようにします。

Effect.BlindUp

0581:Effect.BlindUp = function(element) {
0582:  element = $(element);
0583:  element.makeClipping();
0584:  return new Effect.Scale(element, 0,
0585:    Object.extend({ scaleContent: false, 
0586:      scaleX: false, 
0587:      restoreAfterFinish: true,
0588:      afterFinishInternal: function(effect) {
0589:        effect.element.hide().undoClipping();
0590:      } 
0591:    }, arguments[1] || { })
0592:  );
0593:};
0594:

581~594行目のEffect.BlindUpは、すだれを上げるようなエフェクトです。だんだん縦幅を小さくするEffect.Scaleからできています。

583行目で、makeClippingを使って、要素をoverflow: 'hidden'にして、クリップされる状態にします。これで要素の縮小につれて文字が隠れます。

584行目で、Effect.Scaleを、倍率0%(大きさ0)でかけます。オプションは以下の通りです。

585行目で、要素の内容のフォントサイズは変えません。(scaleContent : false)

586行目で、横幅は変えません。(scaleX : false)

587行目で、エフェクト後に大きさを元に戻します。(restoreAfterFinish : true)

588行目で、終了後のフックで、要素をhideで隠すとともに、undoClippingを使ってoverflowの設定を元に戻します。

Effect.BlindDown

0595:Effect.BlindDown = function(element) {
0596:  element = $(element);
0597:  var elementDimensions = element.getDimensions();
0598:  return new Effect.Scale(element, 100, Object.extend({ 
0599:    scaleContent: false, 
0600:    scaleX: false,
0601:    scaleFrom: 0,
0602:    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
0603:    restoreAfterFinish: true,
0604:    afterSetup: function(effect) {
0605:      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
0606:    },  
0607:    afterFinishInternal: function(effect) {
0608:      effect.element.undoClipping();
0609:    }
0610:  }, arguments[1] || { }));
0611:};
0612:

595~612行目のEffect.BlindDownは、すだれを降ろすようなエフェクトです。だんだん縦幅を大きくするEffect.Scaleからできています。

597行目で、getDimensionsで、要素のサイズを取得します。

598行目で、Effect.Scaleを、倍率100%(等倍)でかけます。オプションは以下の通りです。

599行目で、要素の内容のフォントサイズは変えません。

600行目で、横幅は変えません。

601行目で、最初の大きさは倍率0%(大きさ0)から始めます。

602行目で、高さ、幅については先ほどのgetDimensionsで取得したものを基準にします。

603行目で、エフェクト後に大きさを元に戻します。

604行目で、初期化後のフックで、要素をmakeClippingでoverflow: 'hidden'にして、クリップされる状態にするとともに、CSSのheightプロパティを'0px'にし(これは初期化後なのでrestoreAfterFinishに影響しません⁠⁠、showで表示にします。

607行目で、終了後のフックで、undoClippingでoverflowの設定を元に戻します。

Effect.SwitchOff

0613:Effect.SwitchOff = function(element) {
0614:  element = $(element);
0615:  var oldOpacity = element.getInlineOpacity();
0616:  return new Effect.Appear(element, Object.extend({
0617:    duration: 0.4,
0618:    from: 0,
0619:    transition: Effect.Transitions.flicker,
0620:    afterFinishInternal: function(effect) {
0621:      new Effect.Scale(effect.element, 1, { 
0622:        duration: 0.3, scaleFromCenter: true,
0623:        scaleX: false, scaleContent: false, restoreAfterFinish: true,
0624:        beforeSetup: function(effect) { 
0625:          effect.element.makePositioned().makeClipping();
0626:        },
0627:        afterFinishInternal: function(effect) {
0628:          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
0629:        }
0630:      })
0631:    }
0632:  }, arguments[1] || { }));
0633:};
0634:

613~634行目のEffect.SwitchOffは、テレビのスイッチを切るように消えるエフェクトです。一瞬明るく見せるEffect.Appearと、その後で中央に向かってだんだん縦幅を小さくするEffect.Scaleの2段がまえでできています。このEffect.Appearの芸が細かい!ブラウン管の電子線のゆらめきをflickerを使って表現しています。

615行目で、要素の透明度を取得します。エフェクト後に元に戻すためです。

616行目で、Effect.Appearでブラウン管をまたたかせるように見せます。オプションは以下です。

617行目で、持続時間は0.4秒です。

618行目で、初期透明度は0(不透明)です。

619行目で、透明度の動きは、flicker(乱数で震えつつ1.0に収束する)です。

620行目で、終了後のフックで、Effect.Scaleで中央に向かって縦幅を小さくします。倍率は1%(とても小さい)です。オプションは以下です。

622行目で、持続時間は0.3秒です。中央を動かさないように縮小します。

623行目で、横幅は変えません。要素の内容のフォントサイズは変えません。終了後に元に戻します。

624行目で、初期化前のフックで、makePositionedでpositionを'relative'にして、移動に備えます。makeClippingでoverflowを'hidden'にし、クリップされるようにします。

627行目で、終了後のフックで、要素を隠すとともに、undoClippingでoverflowを元に戻し、undoPositionedでpositionを元に戻し、要素の透明度をエフェクト前の値に戻します。

Effect.DropOut

0635:Effect.DropOut = function(element) {
0636:  element = $(element);
0637:  var oldStyle = {
0638:    top: element.getStyle('top'),
0639:    left: element.getStyle('left'),
0640:    opacity: element.getInlineOpacity() };
0641:  return new Effect.Parallel(
0642:    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
0643:      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
0644:    Object.extend(
0645:      { duration: 0.5,
0646:        beforeSetup: function(effect) {
0647:          effect.effects[0].element.makePositioned(); 
0648:        },
0649:        afterFinishInternal: function(effect) {
0650:          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
0651:        } 
0652:      }, arguments[1] || { }));
0653:};
0654:

635~654行目のEffect.DropOutは、ポロリと落ちるように消えるエフェクトです。位置を下に動かす Effect.Moveと、だんだん透明にする Effect.Opacityを、Effect.Parallelで並列に動かすことからできています。

637行目で、要素の位置と透明度を取得します。エフェクト後に元に戻すためです。

641行目で、Effect.Parallelで、2つのエフェクト(MoveとOpacity)を並列に動かします。

642行目で、Effect.Moveで、yを100px、下に移動します。ポロリと落ちるように見えます。

643行目で、Effect.Opacityで、透明(0.0)にします。消えるように見えます。

645行目で、これらの持続時間はデフォルトでは0.5秒です。

646行目で、初期化前のフックで、makePositionedでpositionを'relative'にして、移動に備えます。

649行目で、終了後のフックで、要素を隠すとともに、undoPositionedでpositionを元に戻し、要素の位置と透明度をエフェクト前の値に戻します。

Effect.Shake

0655:Effect.Shake = function(element) {
0656:  element = $(element);
0657:  var options = Object.extend({
0658:    distance: 20,
0659:    duration: 0.5
0660:  }, arguments[1] || {});
0661:  var distance = parseFloat(options.distance);
0662:  var split = parseFloat(options.duration) / 10.0;
0663:  var oldStyle = {
0664:    top: element.getStyle('top'),
0665:    left: element.getStyle('left') };
0666:    return new Effect.Move(element,
0667:      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
0668:    new Effect.Move(effect.element,
0669:      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
0670:    new Effect.Move(effect.element,
0671:      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
0672:    new Effect.Move(effect.element,
0673:      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
0674:    new Effect.Move(effect.element,
0675:      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
0676:    new Effect.Move(effect.element,
0677:      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
0678:        effect.element.undoPositioned().setStyle(oldStyle);
0679:  }}) }}) }}) }}) }}) }});
0680:};
0681:

655~681行目のEffect.Shakeは、ブルブルと左右に震えるエフェクトです。左に動かすEffect.Moveと右に動かすそれを、交互に数珠つなぎにして作られています。

658行目で、振動の幅をデフォルトでは20pxにします。

659行目で、持続時間はデフォルトでは0.5秒です。

661行目で、options.distanceの値を浮動小数に変換しておきます。後でEffect.Moveにx軸移動量として渡すからです。

662行目で、持続時間を、移動の回数で割ります。

663~665行目で、要素の位置を取得します。エフェクト後に元に戻すためです。

666~679行目で、Effect.Moveを、afterFinishInternalというフックで数珠つなぎにしています。

右に20px動かします。左に40px動かします。右に40px動かします。左に40px動かします。右に40px動かします。左に20px動かします。これで原点に戻ってきました。

678行目で、最後のフックで、undoPositionedでpositionを元に戻し、要素の位置をエフェクト前の値に戻します。

ちなみに、以下のように、Effect.Moveでオプションでtransitonにsin関数を使う工夫をしても同様のものが実装できます。

Effect.YetanotherShake = function (element) {
  element = $(element);
  var options = Object.extend({distance:20, duration:0.5}, arguments[1] || {});
  var distance = parseFloat(options.distance) * 2;
  var oldStyle = {top:element.getStyle("top"), left:element.getStyle("left")};
  options = Object.extend({x:distance, y:0, transition:function(pos){
    return (Math.sin(6*pos*Math.PI)/2)
  }, afterFinish: function(effect) {
    effect.element.undoPositioned().setStyle(oldStyle)
  }},options);
  return new Effect.Move(element, options);
}

Effect.SlideDown

0682:Effect.SlideDown = function(element) {
0683:  element = $(element).cleanWhitespace();
0684:  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
0685:  var oldInnerBottom = element.down().getStyle('bottom');
0686:  var elementDimensions = element.getDimensions();
0687:  return new Effect.Scale(element, 100, Object.extend({ 
0688:    scaleContent: false, 
0689:    scaleX: false, 
0690:    scaleFrom: window.opera ? 0 : 1,
0691:    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
0692:    restoreAfterFinish: true,
0693:    afterSetup: function(effect) {
0694:      effect.element.makePositioned();
0695:      effect.element.down().makePositioned();
0696:      if (window.opera) effect.element.setStyle({top: ''});
0697:      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
0698:    },
0699:    afterUpdateInternal: function(effect) {
0700:      effect.element.down().setStyle({bottom:
0701:        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
0702:    },
0703:    afterFinishInternal: function(effect) {
0704:      effect.element.undoClipping().undoPositioned();
0705:      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
0706:    }, arguments[1] || { })
0707:  );
0708:};
0709:

682~709行目のEffect.SlideDownは、お店のシャッターを降ろすようなエフェクトで、Effect.BlindDownに似ていますが、要素の内容が吐き出されるように流れてくるのが違います。BlindDownと同じくEffect.Scaleを使っていても、そのぶん処理が少し複雑です。

683行目で、要素の内容からcleanWhitespaceで空白を取り除きます。文字列を流すときに邪魔になるからです。

685行目で、要素のdownメソッドで子要素を取得し、そのCSSのbottomプロパティの値を保存しておきます。エフェクト後に元に戻すためです。

686行目で、要素のサイズを取得します。

687行目で、Effect.Scaleを、倍率100%(等倍)でかけます。オプションは以下です。

688行目で、要素の内容のフォントサイズを変えません。

689行目で、横幅を変えません。

690行目で、大きさの初期値は1%です。0にしないのは、IEで起こるチラツキを防ぐためです。

691行目で、高さ、幅については先ほどのgetDimensionsで取得したものを基準にします。

692行目で、終了後に大きさを元に戻します。

693行目で、初期化後のフックで、次の処理をします。

694行目で、makePositionedで、要素のその子要素のpositionを'relative'にして、移動に備えます。

696行目で、positionをstaticからrelativeに変えたときのoperaの仕様に対応しているようです。この仕様については「prototype.jsを読み解く」第6回のmakePositionedの項に詳しく書かれています。

697行目で、makeClippingでoverflowを'hidden'にしてクリップされるようにし、heightを0pxにしてから、showで表示します。

699行目で、更新後のフックで、子要素のbottomプロパティの値を要素の高さと同期させて、要素の内容が吐き出されるように流れてくるように見せます。

703行目で、終了後のフックで、要素をundoClippingしてoverflowを元に戻し、undoPositionedでpositionを元に戻します。

705行目で、子要素もundoPositionedして、さらに、bottomプロパティの値を元に戻します。

Effect.SlideUp

0710:Effect.SlideUp = function(element) {
0711:  element = $(element).cleanWhitespace();
0712:  var oldInnerBottom = element.down().getStyle('bottom');
0713:  var elementDimensions = element.getDimensions();
0714:  return new Effect.Scale(element, window.opera ? 0 : 1,
0715:   Object.extend({ scaleContent: false, 
0716:    scaleX: false, 
0717:    scaleMode: 'box',
0718:    scaleFrom: 100,
0719:    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
0720:    restoreAfterFinish: true,
0721:    afterSetup: function(effect) {
0722:      effect.element.makePositioned();
0723:      effect.element.down().makePositioned();
0724:      if (window.opera) effect.element.setStyle({top: ''});
0725:      effect.element.makeClipping().show();
0726:    },  
0727:    afterUpdateInternal: function(effect) {
0728:      effect.element.down().setStyle({bottom:
0729:        (effect.dims[0] - effect.element.clientHeight) + 'px' });
0730:    },
0731:    afterFinishInternal: function(effect) {
0732:      effect.element.hide().undoClipping().undoPositioned();
0733:      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
0734:    }
0735:   }, arguments[1] || { })
0736:  );
0737:};
0738:

710~738行目のEffect.SlideUpは、お店のシャッターを上げるようなエフェクトで、Effect.BlindUpに似ていますが、要素の内容が吸い込まれるように流れていくのが違います。BlindUpと同じくEffect.Scaleを使っていても、そのぶん処理が少し複雑です。コードはEffect.SlideDownとほとんど同じですので、解説は省略します。

717行目と719行目でscaleModeが2回登場するのはバグでしょう。

Effect.Squish

0739:// Bug in opera makes the TD containing this element expand for a instance after finish 
0740:Effect.Squish = function(element) {
0741:  return new Effect.Scale(element, window.opera ? 1 : 0, { 
0742:    restoreAfterFinish: true,
0743:    beforeSetup: function(effect) {
0744:      effect.element.makeClipping(); 
0745:    },  
0746:    afterFinishInternal: function(effect) {
0747:      effect.element.hide().undoClipping(); 
0748:    }
0749:  });
0750:};
0751:

739~751行目のEffect.Squishは、縮小して消えるエフェクトです。ほとんどEffect.Scaleと変わりません。

741行目で、Effect.Scaleを、倍率0IEではチラツキが起きるので1でかけます。オプションは以下です。

742行目で、エフェクト後に大きさを戻します。

743行目で、初期化前のフックで、要素をmakeClippingでoverflow: 'hidden'にして、クリップされる状態にします。

746行目で、終了後のフックで、要素を隠すとともに、要素をundoClippingしてoverflowを元に戻します。

Effect.Grow

0752:Effect.Grow = function(element) {
0753:  element = $(element);
0754:  var options = Object.extend({
0755:    direction: 'center',
0756:    moveTransition: Effect.Transitions.sinoidal,
0757:    scaleTransition: Effect.Transitions.sinoidal,
0758:    opacityTransition: Effect.Transitions.full
0759:  }, arguments[1] || { });
0760:  var oldStyle = {
0761:    top: element.style.top,
0762:    left: element.style.left,
0763:    height: element.style.height,
0764:    width: element.style.width,
0765:    opacity: element.getInlineOpacity() };
0766:
0767:  var dims = element.getDimensions();    
0768:  var initialMoveX, initialMoveY;
0769:  var moveX, moveY;
0770:  
0771:  switch (options.direction) {
0772:    case 'top-left':
0773:      initialMoveX = initialMoveY = moveX = moveY = 0; 
0774:      break;
0775:    case 'top-right':
0776:      initialMoveX = dims.width;
0777:      initialMoveY = moveY = 0;
0778:      moveX = -dims.width;
0779:      break;
0780:    case 'bottom-left':
0781:      initialMoveX = moveX = 0;
0782:      initialMoveY = dims.height;
0783:      moveY = -dims.height;
0784:      break;
0785:    case 'bottom-right':
0786:      initialMoveX = dims.width;
0787:      initialMoveY = dims.height;
0788:      moveX = -dims.width;
0789:      moveY = -dims.height;
0790:      break;
0791:    case 'center':
0792:      initialMoveX = dims.width / 2;
0793:      initialMoveY = dims.height / 2;
0794:      moveX = -dims.width / 2;
0795:      moveY = -dims.height / 2;
0796:      break;
0797:  }
0798:  
0799:  return new Effect.Move(element, {
0800:    x: initialMoveX,
0801:    y: initialMoveY,
0802:    duration: 0.01, 
0803:    beforeSetup: function(effect) {
0804:      effect.element.hide().makeClipping().makePositioned();
0805:    },
0806:    afterFinishInternal: function(effect) {
0807:      new Effect.Parallel(
0808:        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
0809:          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
0810:          new Effect.Scale(effect.element, 100, {
0811:            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
0812:            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
0813:        ], Object.extend({
0814:             beforeSetup: function(effect) {
0815:               effect.effects[0].element.setStyle({height: '0px'}).show(); 
0816:             },
0817:             afterFinishInternal: function(effect) {
0818:               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
0819:             }
0820:           }, options)
0821:      )
0822:    }
0823:  });
0824:};
0825:

752~825行目のEffect.Growは、飛び出すように拡大して現れるエフェクトです。飛び出しの出発点を選べるようになっています。出発点までの移動にEffect.Moveを0.01秒だけ使っています。その後の飛び出すエフェクトは、Effect.OpacityとEffect.MoveとEffect.ScaleをEffect.Parallelで並列に動かすことからできています。Effect.MoveとEffect.Scaleを同時に使うのは、後述するように、出発点を動かさずに拡大しているように見せるためです。

754~759行目で、デフォルト設定を作ります。

755行目で、飛び出しの出発点は'center'(中央)です。

756行目で、飛び出すときの角の位置の変化は、sinoidalです。

757行目で、飛び出すときの大きさの変化は、sinoidalです。角の動きと広がりが同期するので、出発点を中心に広がるように見えます。

758行目で、飛び出すときの透明度の変化は、fullで、常に1(不透明)です。デフォルトでは、変化がないので、後述のEffect.Opacityの意味がありません。

760~765行目で、要素の位置と大きさと透明度を保存します。エフェクト後に元に戻すためです。

767行目で、要素のサイズを取得します。

768行目で、initialMoveXとinitialMoveYは出発点の相対位置です。

769行目で、moveXとmoveYは、角(かど)の位置の移動量です。initialMoveXとinitialMoveYの符号を逆にしたものに等しいです。

771~797行目で、飛び出しの出発点のオプションについて場合分けします。

'top-left'移動する必要がありません。Effect.Scaleだけで充分です。
'top-right'右上の出発点が動いていないように見せるため、左に移動しながら大きさを変えます。
'bottom-left'同様に、上に移動しながら大きさを変えます。
'bottom-right'同様に、左上に移動しながら大きさを変えます。
'center'同様に、左上に移動しながら大きさを変えます。これはEffect.ScaleでscaleFromCenterとしても同じでしょう。

799行目で、Effect.Moveで、出発点まで0.01秒で移動します。これはEffect.Eventを使ってもよいでしょう。

803行目で、初期化前のフックで、要素を隠すとともに、makeClippingでoverflowを'hidden'にし、クリップされるようにし、makePositionedでpositionを'relative'にして、移動に備えます。

806行目で、終了後のフックで、飛び出すエフェクトをかけます。Effect.Parallelで、3つのエフェクト(Opacity、Move,Scale)を並列にかけます。

808行目で、Effect.Opacityで、0.0(透明)から1.0(不透明)に向けて変化させますが、デフォルトだとtransitionがfullになるので、ずっと1.0(不透明)です。デフォルトだとこのエフェクトの意味はありません。

809行目で、Effect.Moveで、位置を大きさの変化と同期して動かすことで、出発点を動かさずに広がっているように見せます。

810行目で、Effect.Scaleで、目標倍率は100%(等倍⁠⁠、大きさの基準はgetDimensionsで取得したもの、初期倍率は0IEではチラツキが起きるので1⁠、終了後に元に戻します。

814行目で、初期化前のフックで、要素の高さを0pxにして、表示します。

817行目で、終了後のフックで、要素をundoClippingしてoverflowを元に戻し、undoPositionedでpositionを元に戻し、位置と大きさと透明度を元に戻します。

Effect.Shrink

0826:Effect.Shrink = function(element) {
0827:  element = $(element);
0828:  var options = Object.extend({
0829:    direction: 'center',
0830:    moveTransition: Effect.Transitions.sinoidal,
0831:    scaleTransition: Effect.Transitions.sinoidal,
0832:    opacityTransition: Effect.Transitions.none
0833:  }, arguments[1] || { });
0834:  var oldStyle = {
0835:    top: element.style.top,
0836:    left: element.style.left,
0837:    height: element.style.height,
0838:    width: element.style.width,
0839:    opacity: element.getInlineOpacity() };
0840:
0841:  var dims = element.getDimensions();
0842:  var moveX, moveY;
0843:  
0844:  switch (options.direction) {
0845:    case 'top-left':
0846:      moveX = moveY = 0;
0847:      break;
0848:    case 'top-right':
0849:      moveX = dims.width;
0850:      moveY = 0;
0851:      break;
0852:    case 'bottom-left':
0853:      moveX = 0;
0854:      moveY = dims.height;
0855:      break;
0856:    case 'bottom-right':
0857:      moveX = dims.width;
0858:      moveY = dims.height;
0859:      break;
0860:    case 'center':  
0861:      moveX = dims.width / 2;
0862:      moveY = dims.height / 2;
0863:      break;
0864:  }
0865:  
0866:  return new Effect.Parallel(
0867:    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
0868:      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
0869:      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
0870:    ], Object.extend({            
0871:         beforeStartInternal: function(effect) {
0872:           effect.effects[0].element.makePositioned().makeClipping(); 
0873:         },
0874:         afterFinishInternal: function(effect) {
0875:           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
0876:       }, options)
0877:  );
0878:};
0879:

826~879行目のEffect.Shrinkは、吸い込まれるように縮小して消えるエフェクトです。吸い込まれる方向を選べるようになっています。吸い込むエフェクトは、Effect.OpacityとEffect.MoveとEffect.ScaleをEffect.Parallelで並列に動かすことからできています。Effect.Growのように出発点に移動する必要はありません。

Effect.Growとほとんど同じなので、解説は省略します。

'top-left'
'top-right'
'bottom-left'
'bottom-right'
'center'

Effect.Pulsate

0880:Effect.Pulsate = function(element) {
0881:  element = $(element);
0882:  var options    = arguments[1] || { };
0883:  var oldOpacity = element.getInlineOpacity();
0884:  var transition = options.transition || Effect.Transitions.sinoidal;
0885:  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
0886:  reverser.bind(transition);
0887:  return new Effect.Opacity(element, 
0888:    Object.extend(Object.extend({  duration: 2.0, from: 0,
0889:      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
0890:    }, options), {transition: reverser}));
0891:};
0892:

880~892行目のEffect.Pulsateは、透明と不透明の間で点滅するエフェクトです。Effect.Opacityでできています。

883行目で、要素の透明度を保存します。エフェクト後に元に戻すためです。

884行目で、透明度の変化の元となる関数をオプションから設定します。デフォルトはsinoidalです。

885行目で、pulse関数をx軸対称にしたものに、先ほどのtransitionの関数を適用する関数です。オプションでパルスの回数を与えることができます。

886行目で、bindメソッドを使っていますが、結果を受けていないので、意味がありません。バグでしょう。

887行目で、Effect.Opacityを以下のオプションでかけます。

888行目で、デフォルトでは、持続時間は2.0秒、初期透明度は0(透明)です。

889行目で、終了後のフックで、要素の透明度を元に戻します。

890行目で、transitionには先ほどのreverserを使います。

Effect.Fold

0893:Effect.Fold = function(element) {
0894:  element = $(element);
0895:  var oldStyle = {
0896:    top: element.style.top,
0897:    left: element.style.left,
0898:    width: element.style.width,
0899:    height: element.style.height };
0900:  element.makeClipping();
0901:  return new Effect.Scale(element, 5, Object.extend({   
0902:    scaleContent: false,
0903:    scaleX: false,
0904:    afterFinishInternal: function(effect) {
0905:    new Effect.Scale(element, 1, { 
0906:      scaleContent: false, 
0907:      scaleY: false,
0908:      afterFinishInternal: function(effect) {
0909:        effect.element.hide().undoClipping().setStyle(oldStyle);
0910:      } });
0911:  }}, arguments[1] || { }));
0912:};
0913:

893~913行目のEffect.Foldは、縦に縮んだあと、横に縮むエフェクトです。Effect.Scaleを2段使ってできています。

895~899行目で、要素の位置と大きさを保存します。エフェクト後に元に戻すためです。

900行目で、makePositionedでpositionを'relative'にして、移動に備えます。

901行目で、Effect.Scaleを倍率5%(かなり小さい)でかけます。オプションは以下です。これで縦に縮めます。

902行目で、要素の内容のフォントサイズは変えません。

903行目で、横幅は変えません。

904行目で、終了後のフックで、もう一段のEffect.Scaleを倍率1%(とても小さい)でかけます。オプションは以下です。これで横に縮めます。

906行目で、要素の内容のフォントサイズは変えません。

907行目で、縦幅は変えません。

908行目で、終了後のフックで、要素を隠すとともに、要素をundoClippingしてoverflowを元に戻し、位置と大きさを元に戻します。

その他のエフェクト

Effect.Morph

914~1003行目のEffect.Morphは、目標のCSSに向かって変化するエフェクトです。基本エフェクトの組み合わせではありません。バージョン1.7で導入されました。まだ文書化が進んでいない機能です。まずはmir.aculo.usで公開されているデモをご覧ください。このように使います。

new Effect.Morph('error_message',{
  style:'background:#f00; color:#fff;'+
    'border: 20px solid #f88; font-size:2em',
  duration:0.8
});
0914:Effect.Morph = Class.create(Effect.Base, {
0915:  initialize: function(element) {
0916:    this.element = $(element);
0917:    if (!this.element) throw(Effect._elementDoesNotExistError);
0918:    var options = Object.extend({
0919:      style: { }
0920:    }, arguments[1] || { });
0921:    
0922:    if (!Object.isString(options.style)) this.style = $H(options.style);
0923:    else {
0924:      if (options.style.include(':'))
0925:        this.style = options.style.parseStyle();
0926:      else {
0927:        this.element.addClassName(options.style);
0928:        this.style = $H(this.element.getStyles());
0929:        this.element.removeClassName(options.style);
0930:        var css = this.element.getStyles();
0931:        this.style = this.style.reject(function(style) {
0932:          return style.value == css[style.key];
0933:        });
0934:        options.afterFinishInternal = function(effect) {
0935:          effect.element.addClassName(effect.options.style);
0936:          effect.transforms.each(function(transform) {
0937:            effect.element.style[transform.style] = '';
0938:          });
0939:        }
0940:      }
0941:    }
0942:    this.start(options);
0943:  },
0944:  

915~944行目のinitializeは、初期化をする関数です。

918行目で、options.styleのデフォルトは{}です。何もしません。

options.styleは、オブジェクトでプロパティ名と値の組を渡す方法と、文字列で"background-color: '#ffff99', font-size: 15pt, opacity: 0.5"となどと渡す方法と、CSSクラス名を渡す方法が用意されています。

922行目で、オブジェクトの方法なら、ハッシュテーブルに変換するだけです。

924行目で、文字列の方法なら、':'を含んでいることで判断します。後述のparseStyleでパースし、同様のハッシュテーブルに変換します。

926行目で、CSSクラス名の方法なら、以下の処理をします。

927行目で、一時的にそのクラス名を要素に追加します。

928行目で、要素のCSSスタイルを取得して、ハッシュテーブルにします。

929行目で、そのクラス名を要素から削除して元に戻します。

930行目で、クラス名を追加していない状態の、要素のCSSスタイルを取得します。

931行目で、rejectメソッドを使って、クラス名を追加してもしなくても値が変わらなかったプロパティを、処理から外します。

934行目で、終了後のフックで、クラス名を実際に追加すると同時に、クラス名を追加することで変化があったプロパティをスタイルから外します

0945:  setup: function(){
0946:    function parseColor(color){
0947:      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
0948:      color = color.parseColor();
0949:      return $R(0,2).map(function(i){
0950:        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
0951:      });
0952:    }
0953:    this.transforms = this.style.map(function(pair){
0954:      var property = pair[0], value = pair[1], unit = null;
0955:
0956:      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
0957:        value = value.parseColor();
0958:        unit  = 'color';
0959:      } else if (property == 'opacity') {
0960:        value = parseFloat(value);
0961:        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
0962:          this.element.setStyle({zoom: 1});
0963:      } else if (Element.CSS_LENGTH.test(value)) {
0964:          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
0965:          value = parseFloat(components[1]);
0966:          unit = (components.length == 3) ? components[2] : null;
0967:      }
0968:
0969:      var originalValue = this.element.getStyle(property);
0970:      return { 
0971:        style: property.camelize(), 
0972:        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
0973:        targetValue: unit=='color' ? parseColor(value) : value,
0974:        unit: unit
0975:      };
0976:    }.bind(this)).reject(function(transform){
0977:      return (
0978:        (transform.originalValue == transform.targetValue) ||
0979:        (
0980:          transform.unit != 'color' &&
0981:          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
0982:        )
0983:      )
0984:    });
0985:  },

945~985行目のsetupは、エフェクトをかける前の下準備をする関数です。

946~952行目のparseColorは、引数に色を表現した文字列をとって、前回解説したString.prototype.parseColorを内部的に使ったうえで、その結果を3原色の配列[0~255,0~255,0~255]にして返す関数です。

947行目で、引数無しで呼ばれた場合と、引数が'rgba(0,0,0,0)'か'transparent'だった場合は、'#ffffff'(白)と解釈します。

948行目で、String.prototype.parseColorでパースして、'rgb(...'や'#xxx'や'#xxxxxx'といった指定を'#xxxxxx'に統一します。

949行目で、統一した'#xxxxxx'の16進数を、10進数に解釈して、3原色の配列[0~255,0~255,0~255]にして返します。これはEffect.Highlightにも似た処理がありました。以上でParsecolorの定義は終わりです。

953行目で、initializeで作った、CSSのプロパティ名と値のハッシュテーブルであるthis.styleに、map関数を使います。

954行目で、propertyにプロパティ名を、valueに値を、unit(日本語で"単位"です)にnullを、それぞれ代入します。

956~967行目で、3パターン(色関係のプロパティの場合、透明度の場合、長さ関係のプロパティの場合)の場合分けをします。

第1のパターンでは、値の形から、プロパティが色関係のものかどうか調べます。

956行目で、値が色を表現した文字列('rgb(...)'や'#xxx'や'#xxxxxx')かどうか確かめます。前回解説したとおり、String.prototype.parseColorには、パースに失敗したら引数を返す(この場合'#zzzzzz')機能があるので、このif文は、"valueをparseColorでパースして失敗しなかったら"という意味です。

957行目で、valueが色を表現した文字列('rgb(...)'や'#xxx'や'#xxxxxx')であったら、'#xxxxxx'の形に統一します。

958行目で、単位は'color'、つまり色関係のプロパティということです。

第2のパターンでは、プロパティ名が'opacity'かを調べます。

959行目で、IEのCSSのバグを回避するために、CSSのzoomプロパティを1にします。

第3のパターンでは、値の形から、後述のCSS_LENGTHを使って、プロパティが長さ関係のものかどうか調べます。

964行目で、正規表現で、+3.0emや-10pxなどを、数字と単位にわけます。この正規表現の3つのエスケープは、実は必要ありません。

965行目で、valueには、数字の部分が入ります。

966行目で、unitには、単位の部分が入ります(正規表現が思惑どおり機能しなければ、nullが入ります⁠⁠。

969行目で、originalValueに、エフェクトのプロパティの初期値として、現在の要素のCSSのpropertyの値を入れます。

970~975行目で、プロパティ名と、プロパティの初期値と、プロパティの目標値と、単位の情報を返します。ここまでがmap関数です。

976行目で、map関数の結果を走査して、余分なものを取り除きます。初期値と目標値に差がないものと、初期値か目標値が数値ではないもの(ただし色関係は除く)です。

以上の結果を、this.transformsに代入します。

0986:  update: function(position) {
0987:    var style = { }, transform, i = this.transforms.length;
0988:    while(i--)
0989:      style[(transform = this.transforms[i]).style] = 
0990:        transform.unit=='color' ? '#'+
0991:          (Math.round(transform.originalValue[0]+
0992:            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
0993:          (Math.round(transform.originalValue[1]+
0994:            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
0995:          (Math.round(transform.originalValue[2]+
0996:            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
0997:        (transform.originalValue +
0998:          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
0999:            (transform.unit === null ? '' : transform.unit);
1000:    this.element.setStyle(style, true);
1001:  }
1002:});
1003:

986~1003行目のupdateは、実際にプロパティの値をpositionに応じて変える関数です。この関数は頻繁に呼ばれることになるので、whileを使うなど性能を意識して書かれています。

988行目で、setupで作ったthis.transformsを、whileループで走査します。

989行目で、unitが'color'なら色関係のプロパティなので、3原色の配列から現在の色を計算します。

997行目で、そうでなければ透明度か長さ関係のプロパティなので、現在の値を計算して、toFixed(3)で小数点第3位で数値を丸めます。単位があれば付け加えます。

1000行目で、以上の結果を要素のCSSに反映します。setStyleで2番めの引数にtrueを渡しているのは、Prototype.jsの公式APIドキュメントにはまだ記載されていませんが、内部での自動的なString.camelized()呼び出しを省くオプションです。無駄が減ります(参考 「prototype.jsを読み解く」第5回⁠。

Effect.Transform

Effect.Transformは、これまでのエフェクトの枠組を抽象化する試みです。idやCSSセレクタで複数の要素を指定して、まとめてEffect.Morphエフェクトに渡すことができます。基本エフェクトの組み合わせではなく、そもそもEffect.Baseを継承していません。バージョン1.7で導入されました。

デモをご覧ください。このように使います。

// Effect.Transformを作って、後で呼び出す例
var transformation = new Effect.Transform([
  { 'div.morphing': // DIV要素で、CSSクラスが'morphing'のものを指定する
      'font-size:20px;padding-left:40em;opacity:0.5' }, //目標のCSSに向かって徐々に変化する
  { 'error_message': // DOM idでの指定。やはり'#error_message'とCSSセレクタで指定することもできる。
      'width:480px;border-width:10px;' + //目標のCSS
      'border-right-width:20px;' +
      'margin:20px;margin-bottom:-20px;' +
      'font-size:30px;' +
      'background:#954' }
  ],{ duration: 1.3 });
// 後で呼ぶ。
transformation.play();
1004:Effect.Transform = Class.create({
1005:  initialize: function(tracks){
1006:    this.tracks  = [];
1007:    this.options = arguments[1] || { };
1008:    this.addTracks(tracks);
1009:  },

1004~1021行目のinitializeは、初期化をする関数です。tracksは、並列に実行するエフェクトの情報を入れる配列です。

1010:  addTracks: function(tracks){
1011:    tracks.each(function(track){
1012:      track = $H(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 }
1018:      }));
1019:    }.bind(this));
1020:    return this;
1021:  },

1010~1021行目のaddTracksは、tracksの情報を統一してから追加する関数です。

配列で与えられた、要素のidと目標のCSSをそれぞれkeys().first()とvalues().first()を使って取りだして、this.tracksに追加します。

1022:  play: function(){
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)) });
1028:      }).flatten(),
1029:      this.options
1030:    );
1031:  }
1032:});
1033:

1022~1033行目のplayは、Effect.Parallelで並列に、tracksのエフェクトたちを実行する関数です。

1025行目で、tracksの情報を取り出します。

1026行目で、DOM id か CSSセレクタで、複数の要素を対象に指定できるようになっています。$$(ids)のほうは入れ子になった配列を返すかもしれないので、flattenが必要です。

1027行目で、複数の要素のそれぞれにEffect.Morphをかけます。

1028行目で、mapのmapからできた配列の入れ子を、flattenでつぶして、Effect.Parallelに渡せる形にします。

その他の実用的な関数

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');
1044:  
1045:Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1046:

1034~1044行目のElement.CSS_PROPERTIESは、CSSプロパティ名を列挙したものです。

1045行目で、Element.CSS_LENGTHは、CSSで長さ(+2.0em,-5pxなど)を表現した文字列を、数字と単位に分解する正規表現です。

String.prototype.parseStyle

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;
1052:  else {
1053:    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
1054:    style = String.__parseStyleElement.childNodes[0].style;
1055:  }
1056:  
1057:  Element.CSS_PROPERTIES.each(function(property){
1058:    if (style[property]) styleRules.set(property, style[property]); 
1059:  });
1060:  
1061:  if (Prototype.Browser.IE && this.include('opacity'))
1062:    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1063:
1064:  return styleRules;
1065:};
1066:

1048~1066行目のparseStyleは、CSSを表現した文字列をパースして、プロパティ名と値のハッシュテーブルを作って返す関数です。実際にこの文字列をdiv要素のCSSとして、ブラウザにパースしてもらい、その結果を取り出すという方法をとっています。

1047行目で、String.__parseStyleElementは、名前空間を汚すリスクをとって、何度もcreateElementをする負荷を省いています。

1050行目で、Safariブラウザの場合は、Prototype.jsのElementコンストラクタでdiv要素を作ってCSSを渡しています。

1053行目で、それ以外のブラウザの場合は、createElementでdiv要素を作って、そのinnerHTMLにdiv要素とCSSを書き込んでいます。これでブラウザにCSSをパースしてもらいます。後述のgetStylesを使ってもよかったのではないでしょうか。

1057行目で、Element.CSS_PROPERTIESをもとに、プロパティ名と値のハッシュテーブルを作ります。

1061行目で、IEだけは'opacity'の仕様が違うので、ブラウザのパースに頼らず、正規表現で文字列をパースした結果を使います。

Element.getStyles

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];
1072:      return styles;
1073:    });
1074:  };
1075:} else {
1076:  Element.getStyles = function(element) {
1077:    element = $(element);
1078:    var css = element.currentStyle, styles;
1079:    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
1080:      results[property] = css[property];
1081:      return results;
1082:    });
1083:    if (!styles.opacity) styles.opacity = element.getOpacity();
1084:    return styles;
1085:  };
1086:};
1087:

1067~1087行目のgetStylesは、要素のCSSを、プロパティ名と値のハッシュテーブルにして返す関数です。IE以外でgetComputedStyleを使う定義と、IEではcurrentStyleを使う定義とで、2種類の定義を使い分けます。

1068行目で、getComputedStyleがある場合は、次のような定義をします。

1069行目で、getComputedStyleで要素のCSSを計算して取り出します。

1070行目で、その結果を、CSS_PROPERTIESをもとに、プロパティ名と値のハッシュテーブルにして返します。

1076行目で、IEで、getComputedStyleがない場合は、次のような定義をします。

1078行目で、currentStyleで要素のCSSを計算して取り出します。

1079行目で、その結果を、CSS_PROPERTIESをもとに、プロパティ名と値のハッシュテーブルにします。

1083行目で、透明度が取り出せなかった場合は、getOpacityで取り出して追加します。

1084行目で、結果のハッシュテーブルを返します。

エフェクトをElementクラスのメソッドにする

addMethodsメソッドを使います。

1088:Effect.Methods = {
1089:  morph: function(element, style) {
1090:    element = $(element);
1091:    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
1092:    return element;
1093:  },
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);
1098:    return element;
1099:  },
1100:  highlight: function(element, options) {
1101:    element = $(element);
1102:    new Effect.Highlight(element, options);
1103:    return element;
1104:  }
1105:};
1106:
1107:$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1108:  'pulsate shake puff squish switchOff dropOut').each(
1109:  function(effect) { 
1110:    Effect.Methods[effect] = function(element, options){
1111:      element = $(element);
1112:      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
1113:      return element;
1114:    }
1115:  }
1116:);
1117:
1118:$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
1119:  function(f) { Effect.Methods[f] = Element[f]; }
1120:);
1121:
1122:Element.addMethods(Effect.Methods);

1088~1122行目で、effects.jsのすべてのエフェクトや関数を、Elementクラスのメソッドにします。具体的には、Effect.Morph('foo',...)を$('foo').morph(...)と呼べるようにします。

1089~1093行目で、morphメソッドにEffect.Morphが対応します。

1094行目で、visualEffectメソッドに対応する関数を定義します。$('foo').visualEffect('fade',{...})と呼べるようになります。

1096行目で、引数で'fade'のように指定されたエフェクト名を、実際のエフェクト名'Fade'に直します。そのために頭文字を大文字にします。dasherizeは必要ないと思います。

1097行目で、エフェクト名で指定されたエフェクトをかけます。

1100~1104行目で、highlightメソッドにEffect.Highlightが対応します。なぜhighlightだけ別なのかは謎です。リビジョン6984でこうなりました。

1107~1116行目で、同様に、fade、appear、grow...などのメソッドと、エフェクトを対応づけます。

1118~1120行目で、getInlineOpacity、forceRerenderingなどもメソッドにします。

1122行目で、addMethodsを使って、以上のメソッドを実際にElementクラスに追加します。

おすすめ記事

記事・ニュース一覧