Positionオブジェクト(6)

absolutizeメソッド および relativizeメソッド

【抜粋】
  absolutize: function(element) {
    element = $(element);
    if (element.style.position == 'absolute') return;
    Position.prepare();

    var offsets = Position.positionedOffset(element);
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';;
    element.style.left   = left + 'px';;
    element.style.width  = width + 'px';;
    element.style.height = height + 'px';;
  },

  relativize: function(element) {
    element = $(element);
    if (element.style.position == 'relative') return;
    Position.prepare();

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
  }

結論から言うと、この二つのメソッドは、あまり有用ではないと思います。汎用メソッドとしては不可解な部分が多く、なにかの処理に特化したメソッドのように見えます。

なぜPosition.prepareメソッドを呼び出しているのかが分かりづらいですし*1、要素のスタイルtop、leftの取得にElement.getStyleメソッドを使わずに、要素のstyleプロパティを使用しているのもおかしな気がします。また、relativizeメソッドは単独では使用できません。absolutizeメソッドで処理された要素のみを扱うことができます(それ以外を扱うとエラーが発生する)。

どうも、script.aculo.usのDraggableオブジェクト用じゃないかと思うのですが・・・。

要素の高さ・幅の取得にclientWidth、clientHeightプロパティを使用していますが、Netscape7.1では要素のclientWidth、clientHeightプロパティは無効なため、機能しません。ここはoffsetWidth、offsetHeightを使うべきのような。

私の拙い例を見るよりも、script.aculo.usのサンプルを見てもらったほうが理解が早いと思います。

script.aculo.us - downloads

最新バージョンscriptaculous-js-1.6.4.zipをダウンロードしてください(prototype.js(v1.5.0rc1)が含まれています)。解凍したら、「scriptaculous-js-1.6.4\test\run_functional_tests.html」をブラウザで開いてください。左フレームの「dragdrop4_test」をクリックすると右フレームにテストページが表示されます。下にある「Ghost effect」という要素がドラッグ可能です。このとき、処理中でabsolutizeメソッドとrelativizeメソッドが使用されています*2。処理を簡単に説明すると、

  • クリック時、要素の複製を作成して直前に挿入。absolutizeで処理。要素を半透明化。
  • ドロップ時、要素の複製を削除。relativizeで処理。
  • 元の位置までアニメーションさせて移動。要素の半透明化を解除。

ということをしています。

ポイントは「元の位置までアニメーションさせて移動」させたい、ということのような気がします。このため、relativizeメソッド処理時にドラッグ後の位置で表示させているわけです。

特性を理解すれば使えるかもしれませんが・・・。

cumulativeOffsetメソッド(KonquerorSafariKHTML用上書き)

【抜粋】
// Safari returns margins on body which is incorrect if the child is absolutely
// positioned.  For performance reasons, redefine Position.cumulativeOffset for
// KHTML/WebKit only.
if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  Position.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return [valueL, valueT];
  }
}

UAKonquerorSafariKHTMLの場合、cumulativeOffsetを上書きしています。コメントのおおよその意味は以下。

Safariでは、body要素の子要素がposition:absolute;である場合、body要素のマージンは正確でない。パフォーマンス上の理由から、Position.cumulativeOffsetを再定義を、KHTML/WebKitUAの場合のみ行う。」

要素のoffsetParentがbodyで、position:absolute;のとき、offsetTop、offsetLeftの累積を停止するという処理が入っています。この場合、body要素がさらにoffsetParent、offsetTop、offsetLeftを持つが、これは余計である、ということでしょうか。

すみません、環境がないので確認できないです・・・。

*1:多分、スタイルposition変更時のウィンドウスクロール量の変化を考慮していると思いますが

*2:scriptaculous-js-1.6.4\src\dragdrop.js 336行目、400行目