2012年12月1日
要素の絶対座標を求める、要素を移動する
ブラウザー上で、要素の移動を行う場合、要素の絶対座標を求める必要がある。画面がスクロールしていない場合、表示しているウインドウ座標と表示されているドキュメント座標(ドキュメント全体を示す座標系で、ここで言う絶対座標を示す)は等しく、それぞれの座標は絶対座標を示す。しかし、スクロールしている場合、それぞれの座標は異なる。
ブラウザー中に座標の原点は2つあり、1つはドキュメントの左上隅にあり、もう1つはブラウザーの左上隅(可視領域)にある。画面スクロールがない場合、この原点は一致する。
マウスで要素をドラッグし、マウスを移動させる。要素にマウス移動に追従するコントロールを付加すると、マウス移動量を要素の位置情報に加えれと、マウス移動に合わせ要素も移動する。
このような制御ができるJavaScriptの実装を試みる。
1.マウスの座標を求める
マウスの位置は、event.clientX、event.clientYで、ウインドウ座標の中の位置を求める。マウスカーソルをブラウザーの左上隅に移動させると、IEやFireFoxでは(0、0)を示し、GoogleChrome(確認したバージョンはバージョン 23.0.1271.64 m)では(0、35)を示す(Chromeでは原点がY軸に35pxずれているが、理由は不明)。なお、ブラウザー右下隅は、ウインドウの大きさを示す。
2.要素の座標を求める
getClientRectメソッドは、対象となるオブジェクトの矩形情報をTextRectangleオブジェクトのコレクション(left、top、right、bottom、width、height 但しIE8にはwidth、heightがない)として返すため、これを利用すると簡単に要素のウインドウ座標を得ることができる。次のコードで、id="hoge"を有する要素の開始座標を得ることができる。
var node = document.getElementById("hoge");
var coord = node.getBoundingClientRect();
var x = coord.left;
var y = coord.top;
GoogleChromeでは、先程と同様に(0、35)だけ座標がずれていたため、GoogleChromeの座標についてはずれている分の補正を行うこととした。
getClientRectメソッドで得られる座標はウインドウ座標のため、絶対座標を得るにはスクロール情報を補正する必要がある。スクロール量は、scrollLeft、scrollTopで求めることができ、スクロール量をx、yに加算すれば、絶対座標が得られる(下図参照)。
この方法と別に、画面を構成するDOMを走査し、絶対座標を求める方法がある(下図参照)。この方法に従ったコードは次の通りで、その動作は、対象要素とその親要素の位置を加算し、offsetParentがBODY要素となるまで遡り続け、求めたx、yを戻す。このコードに従うと、GoogleChromeで見られた“座標のずれ”は生じず、またスクロール補正も必要ない。IE、GoogleChrome、FireFoxのそれぞれのブラウザーで、同じ座標を得ることができた。
サンプルコード
function getCoodinate(element) {
var x = 0, y = 0;
do {
x += element.offsetLeft;
y += element.offsetTop;
} while (element = element.offsetParent);
return {"x":x, "y":y};
}
マウスを動かすと画面左上に、その位置の要素の座標を表示する。要素のスタイル情報で、position:absoluteが指定されている場合、どのブラウザーでも同じ座標が示される。一方、position指定がない要素では、ブラウザーによって、デフォルトのフォントサイズなどが異なるため、それぞれで座標が異なる。
3.マウスの移動量を求め、要素を動かす
要素を動かす場合、要素のスタイル情報で、position:absoluteが指定されていることが必要である。
マウスの移動量は、マウスを押下したときに発生するmousedownイベントで開始し、その時点でのウインドウ座標中の座標を、x0=event.clientX、y0=event.clientYと、要素targetのドキュメント座標xp、ypで求める。次にマウスが移動したときに発生するmousemoveイベントで、xn=clientX、yn=clientYを求める。マウスの移動量はx= xn- x0、y= yn- y0となる。
要素のスタイル情報をtarget.style.left=(xp+x)+”px”、target.style.top=(yp+y)+”px”と操作すると、要素がマウスの移動量x、y分、移動する。必ず単位を付けること。
要素の移動中止は、マウスをアップしたときに発生するmouseupイベントで、中止する。
サンプルコード
document.onmousemove=mmove;
var marginChrome=0;
window.onload=function(){
var userAgent=window.navigator.userAgent.toLowerCase();
if(userAgent.indexOf("chrome")>0)marginChrome=35;
}
var targetNode=null;
var offsetX,offsetY;//マウスのダウン座標
var positionX,positionY;//オブジェクトの座標
var scrlX,scrlY;//マウスダウン時のスクロール量
//要素をクリックしたとき
function mdown(ev){
var evt=window.event||ev;
var e=evt.target||evt.srcElement;
if(targetNode)return false;
if(e.style.position=="relative")return false;
targetNode=e;
//ウインドウ座標からmousedown位置を取得
offsetX=evt.clientX;
offsetY=evt.clientY;
//ドキュメント座標の取得
var cord=getCoodinate(targetNode);
positionX=cord.x;
positionY=cord.y;
//現時点のスクロールを求める
scrlX=(document.documentElement.scrollLeft||document.body.scrollLeft);
scrlY=(document.documentElement.scrollTop||document.body.scrollTop);
return
false;
}
//マウスが移動したとき
function mmove(ev){
var evt=window.event||ev;
if(targetNode){
//移動前のスクロールと現時点のスクロールの差を求める
var
dX=(document.documentElement.scrollLeft||document.body.scrollLeft)-scrlX;
var
dY=(document.documentElement.scrollTop||document.body.scrollTop)-scrlY;
//移動前の位置と現時点の位置の差と、スクロール補正を加える
var movetoX=evt.clientX-offsetX+dX;
var movetoY=evt.clientY-offsetY+dY;
//移動前の絶対座標に移動量を加える
if(movetoX!=0)targetNode.style.left=(positionX+movetoX)+"px";//単位の付加は必須
if(movetoY!=0)targetNode.style.top=(positionY+movetoY)+"px";
return
false;
} }
//マウスがアップしたとき
function mup(){
if(targetNode){
targetNode=null;
scrlX=scrlY=0;
return
false;
}
}
<img src="duke.gif" style="position:absolute; left:450px; top:100px; width:100px; height:100px; border-style:solid; border-width:1px; border-color:red; cursor:pointer;" onmousedown="mdown(event);return false;" onmouseup="mup(event);"/>
4.余談
上図で、太枠が描かれている。HTMLでは次のように記述している。
<div style="width:300px; height:30px; border-style:solid; border-width:1; border-color:black;"></div>
一方、border-width:1pxと単位を付加すると、“1px”の枠が描かれる。
<div style="width:300px; height:30px; border-style:solid; border-width:1px; border-color:black;"></div>
このように単位がないと、希望する描画とならないことに注意せよ。
5.参考サイト
エレメントの座標取得 ~スクロール要素~
http://n-yagi.0r2.net/script/2009/06/post_14.html
JavaScript
Tutorial Coordinates(これに、きっちり書いてある)
http://javascript.info/tutorial/coordinates
heatmap.js(ヒートマップ表示、とても面白いので見てください)
http://www.patrick-wied.at/static/heatmapjs/
作成 大坂哲司