script.aculo.usを読み解く

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

この記事を読むのに必要な時間:およそ 23.5 分

その他の実用的な関数

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クラスに追加します。

著者プロフィール

源馬照明(げんまてるあき)

名古屋大学大学院多元数理科学研究科1年。学部生のときにSchemeの素晴らしさを知ったのをきっかけに,関数型言語の世界へ。JavaScriptに,ブラウザからすぐに試せる関数型言語としての魅力と将来性を感じている。

ブログ:Gemmaの日記