ホーム >> 左脳Script >> Java Script >> エレメントの座標取得 ~スクロール要素~

エレメントの座標取得 ~スクロール要素~


さて、バラバラ遁走リンクのページを、Internet Explorer 又は、Opera などで閲覧されてしまった方には判ると思いますが、ページがスクロールされていると、文字の位置が激しくずれます。Opera に至っては、スクロールの値に関連した理不尽なずれ方をします。

実は、今までのエレメント座標取得ルーチンは、(エレメントを含む)要素のスクロールを考慮していない物でした。

スクロールを考慮しない、今までのコード
  • getPosition
    
    function    getPosition(el)
    {
        var ex  =   0;
        var ey  =   0;
        do
        { 
            ex  +=  el.offsetLeft;
            ey  +=  el.offsetTop;
        }
        while(  el  =   el.offsetParent );
        //
        return  [ex,ey];
    };
    
    Internet Explorer でoffset系プロパティが遅いとかいう愚痴は今回は置いておきます。


要素のスクロール対応する

今までのコードでは、overflowスタイルでスクロールを許しているブロック要素の中に含まれているエレメントの座標が、正しく取得できませんでした。

それに対応するコードは以下。

function    getPosition(target)
{
    var ex  =   0;
    var ey  =   0;
    //
    var el  =   target;
    do
    { 
        ex  +=  el.offsetLeft   ||  0;
        ey  +=  el.offsetTop    ||  0;
    }
    while(  el  =   el.offsetParent );
    //
    var el  =   target;
    do
    {
        ex  -=  el.scrollLeft   ||  0;
        ey  -=  el.scrollTop    ||  0;
    }
    while(  el  =   el.parentNode   );
    //
    return  {x:ex,y:ey};
}
scrollLeft、scrollTop分を差し引き、地道に辿るシンプルなものです。しかし、これで更に重くなりそうですね・・・

上記コードは、<body>のスクロール(ページそのもののスクロール)も引いてしまいます。すると、ブラウザとして表示されている画面でのエレメント座標になってしまうのです。

今回、欲しいのはページ内絶対座標なので、スクロールの差し引きを<body>の手前までにするよう修正。
  • getPosition2
    
    function    getPosition2(target)
    {
        var ex  =   0;
        var ey  =   0;
        //
        var el  =   target;
        do
        { 
            ex  +=  el.offsetLeft   ||  0;
            ey  +=  el.offsetTop    ||  0;
        }
        while(  el  =   el.offsetParent );
        //  要素内スクロール対応
        var el  =   target;
        var bd  =   document.body;
        do
        {
            ex  -=  el.scrollLeft   ||  0;
            ey  -=  el.scrollTop    ||  0;
            el  =   el.parentNode;
        }
        while(  el!=bd  );
        //
        return  [ex,ey];
    };
    
    これで、ページ内のエレメントの正しい絶対座標を取得できるようになりました。


getBoundingClientRect

どうやら、getBoundingClientRectというメソッドがあるようです。

同じ値を返すコード。ループもなく Internet Explorer にも対応している事から、Internet Explorer でのアノ遅さを克服できるかもしれません。
  • getPosition3
    
    function    getPosition3(el)
    {
        var pos =   el.getBoundingClientRect();
        var html    =   document.documentElement;
        var body    =   document.body;
        return  [   pos.left    +   (body.scrollLeft||html.scrollLeft)  -   html.clientLeft
                ,   pos.top     +   (body.scrollTop||html.scrollTop)    -   html.clientTop  ];
    };
    
    Chrome1では、未実装のようで動作しませんでした。

諸事情により Chrome 1 を使っているので、コレに対応するべく、ブラウザによってルーチンを振り分ける事にしました。なにより Chrome は、offsetを辿るようなコードでも、十二分な速さを確保できているという理由がある為でもあります。

getPosition3を Internet Explorer 6(6.0.2900.5512.xpsp_sp3_gdr.060206-1234)で、試した所、動作しました。また、Opera 9.6 も問題なく動作するようです。

getPosition2は、割とどのブラウザでも問題のない値を出すのですが、Opera 9.6 だけは変な値がでます。スクロール差し引きの余計な小細工のない、getPositionの方が正確な値を出力できるようです。
getBoundingClientRect未対応の古い Opera は getPosition に振り分けた方が安全かもしれません。

振り分け:
  • getPosition3
    Internet Explorer、Opera、safari4、FireFox3

  • getPosition2
    上記以外の getBoundingClientRect未対応のもの。 未対応の Opera は getPosition へ。

  • getPosition
    上記以外。古い Opera など。


判別コード

getBoundingClientRectの実装判定に、Element.prototype.getBoundingClientRectの有無をチェックする方法にした。かったのですが・・・

Internet Explorer で、Elementが無いと怒られます。

実は、Internet Explorer は基本 DOM に対応し切れていない所が多く、Element オブジェクトや、prototype にアクセスできません。バージョンが8になって、DOM に対応した、という謳い文句があったかどうかは定かでは有りませんが、現時点で互換モードで動かす事が非常に多いので、あまり意味が無かったりする場合もあるのです。

今回まさにそうなりました。

これを真っ向から解決するには根が深すぎる問題なので、面倒になってダミーエレメントを作る方法で逃げます。

function    is_getBoundingClientRect()
{
    //return    (typeof Element.prototype.getBoundingClientRect ==  'function')
    var dummy   =   document.createElement("div");
    return  (   typeof(dummy.getBoundingClientRect)=="function" );
}

ところが、Internet Explorer で、この関数がを返しません。
なんでだ?と思い「alert(typeof dummy.getBoundingClientRect);」を追加すると・・・

typeof_ie.PNG
なんだってー!?
function じゃないの?と言うわけで、Internet Explorer では、関数は「object」らしいです。
typeof_safari4.PNG
safari 4
typeof_firefox3.PNG
FireFox 3
typeof_opera96.PNG Opera 9.6
Safari 4、FireFox 3、Opera 9.6 では、関数オブジェクトは「function」と返ってきます。

さすが Web界きっての やんちゃブラウザ Internet Explorer である(笑)。


function    is_getBoundingClientRect()
{
    var dummy   =   document.createElement("div");
    return  (dummy.getBoundingClientRect)!=undefined;
}
結局、「対象の関数がある」かどうか、ではなく、「対象の定義が無くない(笑)」かどうかで判別するしかないようです。


エレメント座標取得関数

このような経緯を踏まえ、できたのが以下のコード。

//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
//  エレメントの絶対座標を得たい
//------------------------------------------------------------------------------
//  座標取得
var getPosition;

function    is_getBoundingClientRect()
{   //return    (typeof Element.prototype.getBoundingClientRect ==  'function')
    var dummy   =   document.createElement("div");
    return  (dummy.getBoundingClientRect)!=undefined;
}

if( is_getBoundingClientRect()  )
{
    var __doc__ =   document;
    //  getBoundingClientRectでの座標取得
    getPosition =   function(el)
    {
        var pos =   el.getBoundingClientRect();
        var html    =   __doc__.documentElement;
        var body    =   __doc__.body;
        return  {   x:(pos.left +   (body.scrollLeft||html.scrollLeft)  -   html.clientLeft)
                ,   y:(pos.top  +   (body.scrollTop||html.scrollTop)    -   html.clientTop) };
    };
}
else
{   //  getBoundingClientRectが無い
    if( window.opera    )
    {
        getPosition =   function(el)
        {
            var ex  =   0;
            var ey  =   0;
            do
            { 
                ex  +=  el.offsetLeft;
                ey  +=  el.offsetTop;
            }
            while(  el  =   el.offsetParent );
            //
            return  {x:ex,y:ey};
        };
    }
    else
    {
        var __doc__ =   document;
        //
        getPosition =   function(target)
        {
            var ex  =   0;
            var ey  =   0;
            //
            var el  =   target;
            do
            { 
                ex  +=  el.offsetLeft   ||  0;
                ey  +=  el.offsetTop    ||  0;
            }
            while(  el  =   el.offsetParent );
            //  要素内スクロール対応
            var el  =   target;
            var bd  =   __doc__.body;
            do
            {
                ex  -=  el.scrollLeft   ||  0;
                ey  -=  el.scrollTop    ||  0;
                el  =   el.parentNode;
            }
            while(  el!=bd  );
            //
            return  {x:ex,y:ey};
        };
    }
}
//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

実行すれば、それなりに環境に則した「エレメント座標取得関数 getPosition」が使えるようになります。


以前やったベンチマークコードに、この関数を適応させてみました。→pos_bench3.html
注意)この値は私の環境での値なので、あくまで目安です。

getPosition 1000回ベンチマーク
Internet Explorer 8265.6ms
Chrome113.2ms
safari11.2ms
ForeFox336.8ms
Opera 971.8ms
依然として Internet Explorer は重いです。リアルタイムで何かアクションのある事をしようとすれば Internet Explorer は不利ということになります。
offsetLeft 、offsetTop だけでの座標取得と比べれば6割程度なので、相対的にはマシですが。 マシですが、他ブラウザと比べると、重い事には変わりなく。


バラバラ逃げるテキスト

スクロールに対応した、バラバラ逃げるテキスト。








のわわわわわわわわわわわっわわ。




のわわわわわわわわわわわっわわ。


のわわわわわわわわわわわっわわ。

のわわわわわわわわわわわっわわ。

大変申し訳ないのですが、実は、これでも Internet Explorer では正常に動作しません。
何故だらう・・・上記のコードでも、正しく座標を取れないから動かないのですが、イマイチその「正しく座標が取れないモード」が明確でないのがツライところです。

Opera は、もっと別の理由で動作不良です。もー、ヤになってきぞ(ぉ


それでも良いと言う方へ、ひとまずソースとなるファイルを置いておきますので、煮るなり焼くなり燻すなり好きにして下さい。出来れば、万能座標取得ルーチン作って教えて下さい(ぉ

今回の修正を加えたソース→escAway2.5src.zip



トラックバック(1)

トラックバックURL: http://n-yagi.0r2.net/sanoupulurun/mt-tb.cgi/170

マウス座標の件から、なんとなしに、過去の記事のエレメント座標取得のコードを見ると、 この対策がなされたコードがありました。 getPosition... 続きを読む

コメントする

ホーム >> 左脳Script >> Java Script >> エレメントの座標取得 ~スクロール要素~

アーカイブ

このブログ記事について

このページは、n-yagiが2009年6月19日 18:05に書いたブログ記事です。

ひとつ前のブログ記事は「エレメントの座標取得 ~高速化への挑戦~」です。

次のブログ記事は「プレビュー状態かどうかを知るには」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Creative Commons License
このブログはクリエイティブ・コモンズでライセンスされています。