script.aculo.usを読み解く

第5回 effects.js(前編)速度的なボトルネックを解消するための土台の最適化

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

今回解説するeffects.jsは,Script.aculo.usの代名詞ともいえる,魅力的な演出効果の数々を提供するライブラリです。Webサイトをよりドラマティックに彩るこれらの演出効果は,当初から新鮮な驚きを持って迎えられ,現在に至るまで,Web2.0なデザインを実現するのに欠かせないものとなっています。

Web開発によく使われるライブラリとして,Ajaxjanの調査では,2006年2007年ともに圧倒的なシェアを誇り,最新の調査では,jQuery,Prototypeに次いで第3位にランクインしました。その反面,遅い,重いと敬遠されている機能でもあります。もちろん,そういった不満の声は開発者たちにも届いており,不満を解消するための様々な最適化が施されています。

今回の解説の見どころは,演出効果の実現方法もさることながら,そういった速度的なボトルネックを解消するために,いったいどんな最適化が行われているか,という点です。それには,evalでループ内不変式を削除する,というテクニックが使われています。

effects.jsの内部構造として,まず,すべての演出効果の基礎となるEffect.Baseというクラスがあり,それを継承する単純な8種類の演出効果があります。今回の解説ではこの土台部分を解説します。さらに次回の解説では,それらの複合からなる18種類の演出効果のコードを解説します。

それではコードを見ていきましょう。

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は,Stringクラスのメソッドで,'rgb(...)'や'#xxx'という形式で表現された文字列を,'#xxxxxx'という形式の文字列に変換する関数です。変換できない場合は,通常は,元の文字列を返しますが,引数を与えることで,変換できない場合にそれを返すようにできます。

16行目で,'rgb(128,255,0)'といった指定を扱います。文字列が'rgb('で始まっているかを調べます。

17行目で,その場合は,続く文字列を','で区切って,色の指定を取り出します。

18行目で,それぞれの色の指定について,parseIntで整数として読み込み,Prototype.jsのtoColorPartを使って16進数表記の文字列にして,結合し,'#xxxxxx'という形式の文字列にします。

20行目で,'#8F0','#80FF00'といった指定を扱います。文字列が'#'で始まっているかを調べます。

21行目で,文字列が4文字ならば'#abc'という形式なので,3つの文字をそれぞれだぶらせて'#aabbcc'という形式にします。さらに,小文字に統一します。

22行目で,文字列が7文字ならば,そのまま使います。さらに,小文字に統一します。

25行目で,変換が成功したかを調べ,失敗していたら,元の文字列か1番めの引数を返します。

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行目で,要素の子ノードをたどります。ここでは,要素のchildNodesを,Prototype.jsの$A関数を使って配列にしてから,collectメソッドを使っています。

32行目で,テキストノードは,nodeTypeが3です。テキストノードならその内容の文字列を返します。

33行目で,さらに孫ノードがあれば,それをたどるように,collectTextNodesを再帰的に呼びます。

34行目で,それらをflattenで配列をつぶして結合し,要素の子孫ノードが持っていた文字列を全て集めます。

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は,collectTextNodesと同様の動作をしますが,40行目にあるように,2番めの引数で指定されたクラス名を持つノードを無視するところが違います。

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は,要素のフォントサイズを変えることで,表示の大きさを調整する関数です。倍率をパーセントで与えます(100で等倍)。

47行目で,要素のCSSのfontSizeプロパティの値を変えることで,表示の大きさを調整します。

48行目で,Safariブラウザには,window.scrollBy(0,0)で再描画を促します。

0052:Element.getInlineOpacity = function(element){
0053:  return $(element).style.opacity || '';
0054:};
0055:

52~55行目のgetInlineOpacityは,要素の透明度を取得して返します。取得に失敗した場合は''を返します。

53行目で,要素のCSSのopacityプロパティの値を取得して返します。取得に失敗した場合は''を返します。

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:

56~66行目のforceRerenderingは,Safariでfloat属性と透明度が絡んだときに,要素が正しくレンダリングされないバグ回避するための関数です。内部的にはEffect.Appearでのみ使われています

著者プロフィール

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

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

ブログ:Gemmaの日記

コメント

コメントの記入