Home > Latest topics

Latest topics > getBoundingClientRect()とgetBoxObjectFor()で取れる座標の違い

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

getBoundingClientRect()とgetBoxObjectFor()で取れる座標の違い - Mar 31, 2009

trunkでとうとうgetBoxObjectForがエラーを吐くようになってしまった - alice0775のファイル置き場 - Yahoo!ジオシティーズ

これを見て焦って今頃になってやっと調べた。

パッチによると、nsIDOMNSDocumentからgetBoxObjectFor()が消えて、nsIXULDocument専用のメソッドになった。ということなので、HTMLDocumentでgetBoxObjectFor()を使っているコードは全滅だ。何とかして代わりの方法を見つけないといけない。

document.getBoxObjectFor(element)で取れるのはnsIBoxObjectelement.getBoundingClientRect()で取れるのはnsIDOMClientRectで、インターフェースが違う。

取りたい値nsIBoxObjectnsIDOMClientRect
ボックスの幅box.widthrect.right-rect.leftまたはrect.width
ボックスの高さbox.heightrect.bottom-rect.topまたはrect.height
ボックスの左上の点のX座標(ドキュメントの原点基準)box.x+左ボーダー幅rect.left+window.scrollX
ボックスの左上の点のY座標(ドキュメントの原点基準)box.y+上ボーダー幅rect.top+window.scrollY
ボックスの右下の点のX座標(ドキュメントの原点基準)box.x-左ボーダー幅+box.widthrect.right+window.scrollX
ボックスの右下の点のY座標(ドキュメントの原点基準)box.y-上ボーダー幅+box.heightrect.bottom+window.scrollY
ボックスの左上の点のX座標(ビューポートの原点基準)box.x+左ボーダー幅-window.scrollXrect.left
ボックスの左上の点のY座標(ビューポートの原点基準)box.y+上ボーダー幅-window.scrollYrect.top
ボックスの右下の点のX座標(ビューポートの原点基準)box.x-左ボーダー幅+box.width-window.scrollXrect.top
ボックスの右下の点のY座標(ビューポートの原点基準)box.y-上ボーダー幅+box.height-window.scrollYrect.bottom

nsIDOMClientRectのwidthheightはどうもGecko 1.9.1以降でしか使えないっぽい。

例えば今使ってる環境のdocument.getElementById('reload-button')の場合はこんな感じ。(スクリーンショット) この時の値は以下の通り。

nsIBoxObjectnsIDOMClientRect
box.width==36rect.width==36
box.height==36rect.height==36
box.x==83rect.left==82
box.y==23rect.top==22
rect.right==118
rect.bottom==58

xleftytopの間にずれがあるのは、nsIBoxObjectのxyがいわゆるborder-box(border + padding + contentのボックス)ではなくpadding-box(padding + contentのボックス)基準であるからということらしい。nsIBoxObjectからborder辺の座標を取るには、getComputedStyle()でborderの幅を取って計算してやらないといけない。nsIBoxObjectもnsIDOMClientRectも、これら以外のプロパティはborder-box基準のようだ

で、上の表には書いてないけど、nsIBoxObjectにはscreenXscreenYというプロパティがあって、こちらで取れる画面上の座標もborder-box基準。そして、nsIDOMClientRectにはこれらプロパティが無いので、画面上の座標を取ることができない。

大まかに言って、nsIBoxObjectのxはnsIDOMClientRectのleft、nsIBoxObjectのyはnsIDOMClientRectのtopに読み替えて差し支えない。となると、残る問題は、screenXscreenYに相当する値をどう取るかだ。

で、試行錯誤の結果、以下のようなコードができあがった。

function getBoxObjectFor(aNode)
{
  // getBoxObjectFor() がある時はそれを使う。
  if ('getBoxObjectFor' in aNode.ownerDocument)
    return aNode.ownerDocument.getBoxObjectFor(aNode);

  var box = {
      x       : 0,
      y       : 0,
      width   : 0,
      height  : 0,
      screenX : 0,
      screenY : 0
    };
  try {
    var rect = aNode.getBoundingClientRect();
    var frame = aNode.ownerDocument.defaultView;
    box.x = rect.left + frame.scrollX;
    box.y = rect.top + frame.scrollY;
    box.width  = rect.right-rect.left;
    box.height = rect.bottom-rect.top;

    // 親フレームの要素を辿っていく。
    box.screenX = rect.left;
    box.screenY = rect.top;
    var owner = aNode;
    while (true)
    {
      frame = owner.ownerDocument.defaultView;
      owner = getFrameOwnerFromFrame(frame);
      if (!owner) {
        // 最上位のフレームまで来てしまったら、仕方ないのでwindowのプロパティを使う。
        // でもウィンドウの枠の外側の座標なので、激しくずれる。
        box.screenX += frame.screenX;
        box.screenY += frame.screenY;
        break;
      }
      if (owner.ownerDocument instanceof Ci.nsIDOMXULDocument) {
        // XULのドキュメント中の要素なら画面上の正確な位置を取れる。
        let ownerBox = owner.ownerDocument.getBoxObjectFor(owner);
        box.screenX += ownerBox.screenX;
        box.screenY += ownerBox.screenY;
        break;
      }
      let ownerRect = owner.getBoundingClientRect();
      box.screenX += ownerRect.left;
      box.screenY += ownerRect.top;
    }
  }
  catch(e) {
  }
  return box;
}

function getFrameOwnerFromFrame(aFrame)
{
  // window.parentでは、<browser type="content"/> の内容の
  // フレームからは親を辿れない。
  // nsIDocShellTreeItemを経由すれば可能。
  var parentItem = aFrame
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIWebNavigation)
        .QueryInterface(Ci.nsIDocShell)
        .QueryInterface(Ci.nsIDocShellTreeNode)
        .QueryInterface(Ci.nsIDocShellTreeItem)
        .parent;
  var isChrome = parentItem.itemType == parentItem.typeChrome;
  var parentDocument = parentItem
        .QueryInterface(Ci.nsIWebNavigation)
        .document;
  // フレームに結びついてるiframe要素を直接取る方法が分からないので、
  // 泥臭い方法を……
  var nodes = parentDocument.evaluate(
      '/descendant::*[contains(" frame FRAME iframe IFRAME browser tabbrowser ",'+
                              'concat(" ", local-name(), " "))]',
      parentDocument,
      null,
      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
      null
    );
  for (let i = 0, maxi = nodes.snapshotLength; i < maxi; i++)
  {
    let owner = nodes.snapshotItem(i);
    if (isChrome && owner.wrappedJSObject) owner = owner.wrappedJSObject;
    if (owner.localName == 'tabbrowser') {
      let tabs = owner.mTabContainer.childNodes;
      for (let i = 0, maxi = tabs.length; i < maxi; i++)
      {
        let browser = tabs[i].linkedBrowser;
        if (browser.contentWindow == aFrame)
          return browser;
      }
    }
    else if (owner.contentWindow == aFrame) {
      return owner;
    }
  }
  return null;
}

これにさらにborder幅によるズレとかposition:fixed;の場合への対応とかも盛り込んだ物を、ライブラリにしてみた。見ての通りchrome特権をバリバリに使ってるので、このコードはアドオンの中でしか動かない。画面上の絶対位置が必要になる場面なんてのはアドオンの場合くらいだろうから、別に問題ないと思うけど。

screenXscreenYに相当する値を取るためにけっこう面倒なことをしているので、オーバーヘッドがきっと半端ない。nsIDOMClientRectの持ってる情報だけで済む場合はそれだけ使った方がいいと思う。

ちなみに、安直な発想でXULDocument.getBoxObjectFor.call(HTMLDocument, HTMLElement)というのも考えてみたけど、これは実際には使えない。残念。

分類:Mozilla > XUL, , , , , 時刻:12:09 | Comments/Trackbacks (6) | Edit

Comments/Trackbacks

no title

> 誤差

nsIBoxObject.x|y の方は Border の幅を足したり引いたりしていますが、これの影響でしょうか?
http://mxr.mozilla.org/mozilla-central/source/layout/xul/base/src/nsBoxObject.cpp#162

Commented by taken at 2009/03/31 (Tue) 15:47:08

trunk

出だしからunkですか!
面白いんだけど、alice0775さんにちょっと失礼かなとも思うので一応ツッコミしておきます。

Commented by os0x at 2009/03/31 (Tue) 17:39:32

no title

> nsIBoxObject.x|y の方は Border の幅を足したり引いたりしていますが、これの影響でしょうか?

算出値と実効値とかそのへんの問題かも? よく分かんないですけど……


> 出だしからunkですか!

コピペミスorz

Commented by Piro at 2009/03/31 (Tue) 23:17:48

no title

テストケース作って実験してみたところ、nsIBoxObjectで取れるx/yは何故かpadding-box基準みたいですね。
という事で本文を訂正します。

Commented by Piro at 2009/04/01 (Wed) 01:26:42

内輪で好き勝手にしているような気がしないでもない

Bug 486200 - Need API to compute screen coordinates of DOM elements.
NoScriptの作者さんがscreenX/Y必要だとバグ立てたら, 速攻Robert O'Callahanという人が 私がやるだって。

Commented by すでに新しいバグが... at 2009/04/01 (Wed) 10:21:48

あらら

早速このエントリの内容(と、作ったライブラリ)も陳腐化してしまいましたかね……
まあnsIBoxObjectっぽいインターフェースで値を取れるようにするということの意味は失われるわけではないのかな。

Commented by Piro at 2009/04/01 (Wed) 12:58:59

TrackBack ping me at


の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2009-03-31_getBoundingClientRect.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。

Post a comment

writeback message: Ready to post a comment.

2020年11月30日時点の日本の首相のファミリーネーム(ひらがなで回答)

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のコメント

最近のつぶやき