prototype.jsを読み解く

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

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

3145:   // within must be called directly before
3146:   overlap: function(mode, element) {
3147:     if (!mode) return 0;
3148:     if (mode == 'vertical')
3149:       return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
3150:         element.offsetHeight;
3151:     if (mode == 'horizontal')
3152:       return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
3153:         element.offsetWidth;
3154:   },
3155: 

3146行目からはoverlap()関数です。

within()で設定されたthis.offset, this.xcomp, this.ycompを使っています。

modeがverticalかhorizontalかによって分岐していますが,どちらも渡された要素と計算された座標を元に,どれくらいの割合で重なっているかを算出します。

たとえばmodeが'vertical'の場合,(this.offset[1] + element.offsetHeight) で要素の下辺のY座標となり,((this.offset[1] + element.offsetHeight) - this.ycomp)は渡されたXY座標のY位置から下辺までの長さとなります。これをelement.offsetHeightで割っているので,最終的に返されるのは要素の高さを1とした時,渡されたY座標がどの割合の位置にあるか,ということを示す0から1の間の数値となります。

具体的には,渡されたY座標が要素の上辺と同じ場合には1, 下辺と同じ場合には0, 真ん中の場合は0.5となります。

modeが'horizontal'の場合はX軸が対象となります。

3156:   page: function(forElement) {
3157:     var valueT = 0, valueL = 0;
3158: 
3159:     var element = forElement;
3160:     do {
3161:       valueT += element.offsetTop  || 0;
3162:       valueL += element.offsetLeft || 0;
3163: 
3164:       // Safari fix
3165:       if (element.offsetParent == document.body)
3166:         if (Element.getStyle(element,'position')=='absolute') break;
3167: 
3168:     } while (element = element.offsetParent);
3169: 
3170:     element = forElement;
3171:     do {
3172:       if (!window.opera || element.tagName=='BODY') {
3173:         valueT -= element.scrollTop  || 0;
3174:         valueL -= element.scrollLeft || 0;
3175:       }
3176:     } while (element = element.parentNode);
3177: 
3178:     return [valueL, valueT];
3179:   },
3180: 

3156行目からはpage()です。指定された要素の,viewportから見たオフセットを返します。

まず,要素から遡ってoffsetTop, offsetLeftを加算しておきます。これでドキュメント基点からのオフセットが得られます。

途中,cumulativeOffset()と同様にSafari対策が入っています。

次に,再度要素から遡って,scrollTop, scrollLeftを減算していきます。そうするとスクロールした分だけ左上にずれていくので,最終的にviewportでのオフセット値になります。

3181:   clone: function(source, target) {
3182:     var options = Object.extend({
3183:       setLeft:    true,
3184:       setTop:     true,
3185:       setWidth:   true,
3186:       setHeight:  true,
3187:       offsetTop:  0,
3188:       offsetLeft: 0
3189:     }, arguments[2] || {})
3190: 
3191:     // find page position of source
3192:     source = $(source);
3193:     var p = Position.page(source);
3194: 
3195:     // find coordinate system to use
3196:     target = $(target);
3197:     var delta = [0, 0];
3198:     var parent = null;
3199:     // delta [0,0] will do fine with position: fixed elements,
3200:     // position:absolute needs offsetParent deltas
3201:     if (Element.getStyle(target,'position') == 'absolute') {
3202:       parent = Position.offsetParent(target);
3203:       delta = Position.page(parent);
3204:     }
3205: 
3206:     // correct by body offsets (fixes Safari)
3207:     if (parent == document.body) {
3208:       delta[0] -= document.body.offsetLeft;
3209:       delta[1] -= document.body.offsetTop;
3210:     }
3211: 
3212:     // set position
3213:     if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
3214:     if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
3215:     if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
3216:     if(options.setHeight) target.style.height = source.offsetHeight + 'px';
3217:   },
3218: 

3181行目からはclone()関数です。source要素の位置にtarget要素を移動します。

まずoptionsに,デフォルト値を用意した上でマージします。関数の仮引数には明示されていませんが,三番目の引数としてオプション指定のためのオブジェクトが渡せますので、それをarguments[2]として参照しています。

target要素がposition: absoluteの場合,親からの相対位置では正しい位置にならないので,offsetParent()で相対基準の要素を取得し,そのviewport上の位置をdeltaとして保存しておきます。

この場合,たとえばtargetのleft, topに0, 0を指定しても,parentと同じ位置に移動してしまいページの左上には行きません。そこで,left = (p[0] - delta[0]) という形で代入することで,その分を引いて調整するようにしています。

あとは,optionsの指定に従ってtop, left, width, heightを設定します。これらは単位付きで指定しないといけないので,+ 'px' として設定しています。

3219:   absolutize: function(element) {
3220:     element = $(element);
3221:     if (element.style.position == 'absolute') return;
3222:     Position.prepare();
3223: 
3224:     var offsets = Position.positionedOffset(element);
3225:     var top     = offsets[1];
3226:     var left    = offsets[0];
3227:     var width   = element.clientWidth;
3228:     var height  = element.clientHeight;
3229: 
3230:     element._originalLeft   = left - parseFloat(element.style.left  || 0);
3231:     element._originalTop    = top  - parseFloat(element.style.top || 0);
3232:     element._originalWidth  = element.style.width;
3233:     element._originalHeight = element.style.height;
3234: 
3235:     element.style.position = 'absolute';
3236:     element.style.top    = top + 'px';
3237:     element.style.left   = left + 'px';
3238:     element.style.width  = width + 'px';
3239:     element.style.height = height + 'px';
3240:   },
3241: 

3129行目からはabsolutize()です。ページレイアウトを変更せずに,指定された要素をposition: absoluteに変更します。

absoluteにすると直近のstatic 以外の親が基準になるので,Position.positionedOffset()を使ってそこからのオフセットを出します。

あとはaboslute 用に実際に設定する値を用意し,要素の_originalLeftなどのプロパティにrelativeに戻す時のための値を入れておきます。このとき,_originalLeft, _originalTopには,absoluteの基準となる要素の座標からの相対的な位置,として記録しています。これは現在の絶対位置を保存しておくと,absoluteの基準となる要素の位置が変わってしまった場合に追従できなくなってしまうからのようです。

3242:   relativize: function(element) {
3243:     element = $(element);
3244:     if (element.style.position == 'relative') return;
3245:     Position.prepare();
3246: 
3247:     element.style.position = 'relative';
3248:     var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
3249:     var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
3250: 
3251:     element.style.top    = top + 'px';
3252:     element.style.left   = left + 'px';
3253:     element.style.height = element._originalHeight;
3254:     element.style.width  = element._originalWidth;
3255:   }
3256: }
3257: 

3242行目からはrelativize()です。absolutize()でposition: absoluteに変更したものをrelativeに戻します。

absolutize()でも書いたように,元の位置ではなく現在の場所からの相対的な位置への移動,という形で位置を回復しています。

3258: // Safari returns margins on body which is incorrect if the child is absolutely
3259: // positioned.  For performance reasons, redefine Position.cumulativeOffset for
3260: // KHTML/WebKit only.
3261: if (Prototype.Browser.WebKit) {
3262:   Position.cumulativeOffset = function(element) {
3263:     var valueT = 0, valueL = 0;
3264:     do {
3265:       valueT += element.offsetTop  || 0;
3266:       valueL += element.offsetLeft || 0;
3267:       if (element.offsetParent == document.body)
3268:         if (Element.getStyle(element, 'position') == 'absolute') break;
3269: 
3270:       element = element.offsetParent;
3271:     } while (element);
3272: 
3273:     return [valueL, valueT];
3274:   }
3275: }
3276: 

3261行目からは,Safari用にPosition.cumulativeOffset()を上書き定義しています。

Safariはbodyの子要素がpositon:absoluteの時に,offsetTop, offsetLeftでマージンの値を返してしまうようです。

3277: Element.addMethods();

最後に,prototype.jsのロードが完了するタイミングで,Element.addMethods()を実行しています。

Element, Element.Methodsなどと分かれているものを,Object.extend()を使ってElementに集めたり,の初期化処理を行っています。

著者プロフィール

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

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