宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
trunkでとうとうgetBoxObjectForがエラーを吐くようになってしまった - alice0775のファイル置き場 - Yahoo!ジオシティーズ
これを見て焦って今頃になってやっと調べた。
パッチによると、nsIDOMNSDocumentからgetBoxObjectFor()
が消えて、nsIXULDocument専用のメソッドになった。ということなので、HTMLDocumentでgetBoxObjectFor()
を使っているコードは全滅だ。何とかして代わりの方法を見つけないといけない。
document.getBoxObjectFor(element)
で取れるのはnsIBoxObject、element.getBoundingClientRect()
で取れるのはnsIDOMClientRectで、インターフェースが違う。
取りたい値 | nsIBoxObject | nsIDOMClientRect |
---|---|---|
ボックスの幅 | box.width | rect.right-rect.leftまたはrect.width |
ボックスの高さ | box.height | rect.bottom-rect.topまたはrect.height |
ボックスの左上の点のX座標(ドキュメントの原点基準) | box.x+左ボーダー幅 | rect.left+window.scrollX |
ボックスの左上の点のY座標(ドキュメントの原点基準) | box.y+上ボーダー幅 | rect.top+window.scrollY |
ボックスの右下の点のX座標(ドキュメントの原点基準) | box.x-左ボーダー幅+box.width | rect.right+window.scrollX |
ボックスの右下の点のY座標(ドキュメントの原点基準) | box.y-上ボーダー幅+box.height | rect.bottom+window.scrollY |
ボックスの左上の点のX座標(ビューポートの原点基準) | box.x+左ボーダー幅-window.scrollX | rect.left |
ボックスの左上の点のY座標(ビューポートの原点基準) | box.y+上ボーダー幅-window.scrollY | rect.top |
ボックスの右下の点のX座標(ビューポートの原点基準) | box.x-左ボーダー幅+box.width-window.scrollX | rect.top |
ボックスの右下の点のY座標(ビューポートの原点基準) | box.y-上ボーダー幅+box.height-window.scrollY | rect.bottom |
nsIDOMClientRectのwidth
とheight
はどうもGecko 1.9.1以降でしか使えないっぽい。
例えば今使ってる環境のdocument.getElementById('reload-button')
の場合はこんな感じ。
この時の値は以下の通り。
nsIBoxObject | nsIDOMClientRect |
---|---|
box.width==36 | rect.width==36 |
box.height==36 | rect.height==36 |
box.x==83 | rect.left==82 |
box.y==23 | rect.top==22 |
rect.right==118 | |
rect.bottom==58 |
x
とleft
、y
とtop
の間にずれがあるのは、nsIBoxObjectのx
とy
がいわゆるborder-box(border + padding + contentのボックス)ではなくpadding-box(padding + contentのボックス)基準であるからということらしい。nsIBoxObjectからborder辺の座標を取るには、getComputedStyle()
でborderの幅を取って計算してやらないといけない。nsIBoxObjectもnsIDOMClientRectも、これら以外のプロパティはborder-box基準のようだ。
で、上の表には書いてないけど、nsIBoxObjectにはscreenX
とscreenY
というプロパティがあって、こちらで取れる画面上の座標もborder-box基準。そして、nsIDOMClientRectにはこれらプロパティが無いので、画面上の座標を取ることができない。
大まかに言って、nsIBoxObjectのx
はnsIDOMClientRectのleft
、nsIBoxObjectのy
はnsIDOMClientRectのtop
に読み替えて差し支えない。となると、残る問題は、screenX
とscreenY
に相当する値をどう取るかだ。
で、試行錯誤の結果、以下のようなコードができあがった。
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特権をバリバリに使ってるので、このコードはアドオンの中でしか動かない。画面上の絶対位置が必要になる場面なんてのはアドオンの場合くらいだろうから、別に問題ないと思うけど。
screenX
とscreenY
に相当する値を取るためにけっこう面倒なことをしているので、オーバーヘッドがきっと半端ない。nsIDOMClientRectの持ってる情報だけで済む場合はそれだけ使った方がいいと思う。
ちなみに、安直な発想でXULDocument.getBoxObjectFor.call(HTMLDocument, HTMLElement)
というのも考えてみたけど、これは実際には使えない。残念。
> 誤差
nsIBoxObject.x|y の方は Border の幅を足したり引いたりしていますが、これの影響でしょうか?
http://mxr.mozilla.org/mozilla-central/source/layout/xul/base/src/nsBoxObject.cpp#162
出だしからunkですか!
面白いんだけど、alice0775さんにちょっと失礼かなとも思うので一応ツッコミしておきます。
> nsIBoxObject.x|y の方は Border の幅を足したり引いたりしていますが、これの影響でしょうか?
算出値と実効値とかそのへんの問題かも? よく分かんないですけど……
> 出だしからunkですか!
コピペミスorz
テストケース作って実験してみたところ、nsIBoxObjectで取れるx/yは何故かpadding-box基準みたいですね。
という事で本文を訂正します。
Bug 486200 - Need API to compute screen coordinates of DOM elements.
NoScriptの作者さんがscreenX/Y必要だとバグ立てたら, 速攻Robert O'Callahanという人が 私がやるだって。
早速このエントリの内容(と、作ったライブラリ)も陳腐化してしまいましたかね……
まあnsIBoxObjectっぽいインターフェースで値を取れるようにするということの意味は失われるわけではないのかな。
の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2009-03-31_getBoundingClientRect.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。
writeback message: Ready to post a comment.