prototype.jsを読み解く

第10回 Prototypeライブラリ(2846~3276行目)

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

3082:   cumulativeOffset: function(element) {
3083:     var valueT = 0, valueL = 0;
3084:     do {
3085:       valueT += element.offsetTop  || 0;
3086:       valueL += element.offsetLeft || 0;
3087:       element = element.offsetParent;
3088:     } while (element);
3089:     return [valueL, valueT];
3090:   },
3091: 

3082行目からはcumulativeOffset()です。ドキュメント基点からのオフセットを[X, Y]の配列で返します。

実装はrealOffset()と似ていますが,offsetTop, offsetLeftを合計しているのと,遡るのがparentNodeではなくoffsetParentなところが違います。

Safariでは<body>の子供がposition:absoluteの時に正しい値を返さないため,3258行目で関数全体を上書き定義しなおしています。

3092:   positionedOffset: function(element) {
3093:     var valueT = 0, valueL = 0;
3094:     do {
3095:       valueT += element.offsetTop  || 0;
3096:       valueL += element.offsetLeft || 0;
3097:       element = element.offsetParent;
3098:       if (element) {
3099:         if(element.tagName=='BODY') break;
3100:         var p = Element.getStyle(element, 'position');
3101:         if (p == 'relative' || p == 'absolute') break;
3102:       }
3103:     } while (element);
3104:     return [valueL, valueT];
3105:   },
3106: 

3092行目からはpositionedOffset()です。

cumulativeOffset()と似ていますが,ドキュメントからではなく,一番近い親のうちCSS positionがabsoluteかrelativeなものからのオフセットを求めるところが異なります。

3107:   offsetParent: function(element) {
3108:     if (element.offsetParent) return element.offsetParent;
3109:     if (element == document.body) return element;
3110: 
3111:     while ((element = element.parentNode) && element != document.body)
3112:       if (Element.getStyle(element, 'position') != 'static')
3113:         return element;
3114: 
3115:     return document.body;
3116:   },
3117: 

offsetParent プロパティをクロスブラウザ化したものです。CSSでのcontainer blockを求めるのに使います。

もし渡された要素がoffsetParentを持っていればそのまま返し,document.bodyが渡されていたらそれもそのまま返します。

あとはparentNodeを辿っていき,CSS positionがstatic以外の要素が出てきたらそれを返します。

それ以外の場合はdocument.bodyを返しています。

3118:   // caches x/y coordinate pair to use with overlap
3119:   within: function(element, x, y) {
3120:     if (this.includeScrollOffsets)
3121:       return this.withinIncludingScrolloffsets(element, x, y);
3122:     this.xcomp = x;
3123:     this.ycomp = y;
3124:     this.offset = this.cumulativeOffset(element);
3125: 
3126:     return (y >= this.offset[1] &&
3127:             y <  this.offset[1] + element.offsetHeight &&
3128:             x >= this.offset[0] &&
3129:             x <  this.offset[0] + element.offsetWidth);
3130:   },
3131: 

3118行目からはwithin()です。

Position.includeScrollOffsetsの所でも書いたように,その値が真なら代わりにwithinIncludingScrolloffsets()が呼ばれます。

そうでなければ,cumulativeOffset()を呼び出して要素のドキュメント基点からの位置を算出し,渡された座標が要素の枠内に含まれているかどうかをbooleanで返しています。

this.xcomp, this.ycomp, this.offsetは,別途Position.overlap()で使うためにキャッシュされます。といいつつ,これくらいのことをキャッシュするのにわざわざ「overlap()を呼ぶ前には必ずwithin()を呼ばなければならない」という制約を付けている理由は不明です。階層が深い要素に対してoffsetの計算が時間がかかる場合には,一旦計算しておいてその後に何度もoverlap()を呼び出して計算する,という使い方をするのならキャッシュの意味はありそうです。

3132:   withinIncludingScrolloffsets: function(element, x, y) {
3133:     var offsetcache = this.realOffset(element);
3134: 
3135:     this.xcomp = x + offsetcache[0] - this.deltaX;
3136:     this.ycomp = y + offsetcache[1] - this.deltaY;
3137:     this.offset = this.cumulativeOffset(element);
3138: 
3139:     return (this.ycomp >= this.offset[1] &&
3140:             this.ycomp <  this.offset[1] + element.offsetHeight &&
3141:             this.xcomp >= this.offset[0] &&
3142:             this.xcomp <  this.offset[0] + element.offsetWidth);
3143:   },
3144: 

3132行目からはwithinIncludingScrolloffsets()です。対象要素の上位にスクロールする要素がいる場合にwithin()の代わりに使います。

within()と異なるのは,渡されたx, yに関して,ドキュメント自体のスクロールオフセットdeltaX, deltaYの分を引いていることと,realOffset()を使って本来の位置から動いた分を増やしています。

結果として,this.xcomp, this.ycompには「スクロールしていなかったらそこにあるべき位置」が入ります。

これらの値と,指定された要素の座標(cumulativeOffset())を比較して,その要素の中に含まれているかどうかをbooleanで返します。

著者プロフィール

栗山淳(くりやまじゅん)

S2ファクトリー株式会社株式会社イメージソース所属。
本業はWeb制作会社の裏方。得意分野はFreeBSDやPerlのはずだが,必要に迫られるとHTML/CSSやJavaScriptも書く。