Positionオブジェクト(5)

cloneメソッド(後で上書きされる)

【抜粋】
  clone: function(source, target) {
    source = $(source);
    target = $(target);
    target.style.position = 'absolute';
    var offsets = this.cumulativeOffset(source);
    target.style.top    = offsets[1] + 'px';
    target.style.left   = offsets[0] + 'px';
    target.style.width  = source.offsetWidth + 'px';
    target.style.height = source.offsetHeight + 'px';
  },

この後、同名のメソッドが定義されているため、ここの記述は無効です。処理としては、単純に要素sourceの位置および幅・高さを、要素targetに指定しているだけです。後述する別のcloneメソッドでは機能を拡張しています。

しかし、なんで残っているんだろ・・・。どうもPositionオブジェクトは全体的に投げやりな気がする。。。

pageメソッド

【抜粋】
  page: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent==document.body)
        if (Element.getStyle(element,'position')=='absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      valueT -= element.scrollTop  || 0;
      valueL -= element.scrollLeft || 0;
    } while (element = element.parentNode);

    return [valueL, valueT];
  },

ブラウザウィンドウ上で要素が見える位置を返却するメソッド。引数の要素(ID指定は不可^^;)のoffsetTopとoffsetLeftの累積値から、scrollTopとscrollLeftの累積値を減算しています。Safari対応のコードとして、「offsetParentがbody要素で、position:absolute;である場合はそこでoffsetTopとoffsetLeftの累積を中止する」というコードが入っています。環境がないので確認できませんが。。。。

しかし、なんでcumulativeOffsetメソッドとrealOffsetメソッドを使ってないのだろ? そしてcumulativeOffsetメソッドには、Safari対応のコードは要らないのだろか???→200/10/13修正。すみません、ボケました。最後にcumulativeOffsetメソッドをブラウザ判定して上書きしてますね。すっかり忘れてた・・・。ただ、cumulativeOffsetメソッドとrealOffsetメソッドを使っていないのは相変わらず謎です。

枠線幅問題

枠線幅があるとOpera以外で正しく値が取れません。「Positionオブジェクト(1) - Backstage of theater.js」の「枠線幅問題」を参照してください。

【例】
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 //EN">
<html>
<head>
<title></title>
<style>
<!--
div{
}
#mark{
  background-color:red;
  width:50px;
  height:50px;
  position:relative;
  top:50px;
  left:50px;
}
#scroll0{
  background-color:blue;
  overflow:scroll;
  position:relative;
  width:400px;
  height:300px;
}
#scroll1{
  background-color:yellow;
  overflow:scroll;
  position:relative;
  width:300px;
  height:200px;
  margin:100px;
}
#base0{
  width:500px;
  height:400px;
}
#base1{
  width:400px;
  height:300px;
}
-->
</style>
<script language="javascript" src="prototype.js" charset="utf-8"></script>
<script>
<!--
function test(){
  var str = "Position.page($('mark')) : "
          + Object.inspect(Position.page($('mark')));
  Element.update('view', str);
}
//-->
</script>
</head>
<body id="body">
<div id="scroll0">
<div id="base0">
<div id="scroll1">
<div id="base1">
<div id="mark">MARK</div>
</div>
</div>
</div>
</div>
<button onclick="test();">TEST</button>
<br>
<div id="view"></div>
</body>
</html>

TESTボタンを押すと、要素markのブラウザウィンドウ上の座標を表示します。スクロールしてウィンドウの上に外すとY座標がマイナスになります。

cloneメソッド

【抜粋】
  clone: function(source, target) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || {})

    // find page position of source
    source = $(source);
    var p = Position.page(source);

    // find coordinate system to use
    target = $(target);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(target,'position') == 'absolute') {
      parent = Position.offsetParent(target);
      delta = Position.page(parent);
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  },

引数sourceの要素(ここはID指定可^^;)の位置・サイズを、引数target(ID指定可)に設定するメソッド。通常、source要素をtarget要素で覆い隠す(または下敷きにする)用途で使用するものと思われます。ただし、そのためにはtarget要素はposition:absolute;を指定する必要があります。position:static;(または指定なし)の要素をtargetとして扱うことはできません。position:relative;の場合は少し特殊な動きをします。後述します。

第三引数には以下のプロパティを持つカスタムオブジェクトを設定できます。

プロパティ名 説明
setLeft true(デフォルト):横位置をコピーする
false:横位置をコピーしない
setTop true(デフォルト):縦位置をコピーする
false:縦位置をコピーしない
setWidth true(デフォルト):幅をコピーする
false:幅をコピーしない
setHeight true(デフォルト):高さをコピーする
false:高さをコピーしない
offsetTop 縦位置への加算値
offsetLeft 横位置への加算値

第三引数は設定しなくても機能します。その場合は位置・サイズはすべてコピー、位置への加算値は0です。

ちょっと分かりにくいですが、target要素がposition:absolute;でleft、topが0pxの状態から、target要素のoffsetParentの画面上の表示位置(pageメソッドで取得)を減算後(これでブラウザウィンドウ上で左上に位置する)、source要素の画面上の表示位置(pageメソッドで取得)を加算すると同じ位置になる、と考えれば理解しやすいかと思います。

枠線幅問題

targetおよびsource要素のノードツリー上方で、offsetParentとなる各要素(下の例ではscroll0、scroll1、sub)にborderがあるとその分ずれるという不具合があります(Opera以外)。その場合、第三引数で調整する等の対策が必要となります。「Positionオブジェクト(1) - Backstage of theater.js」の「枠線幅問題」を参照してください。

【例】
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 //EN">
<html>
<head>
<title></title>
<style>
<!--
div{
}
#mark{
  background-color:red;
  width:50px;
  height:50px;
  position:relative;
  top:50px;
  left:50px;
}
#scroll0{
  background-color:blue;
  overflow:scroll;
  position:relative;
  width:400px;
  height:300px;
}
#scroll1{
  background-color:yellow;
  overflow:scroll;
  position:relative;
  width:300px;
  height:200px;
  margin:100px;
}
#base0{
  width:500px;
  height:400px;
}
#base1{
  width:400px;
  height:300px;
}
#clone1{
  background-color:green;
  color:white;
  position:absolute;
  width:20px;
  height:20px;
}
#clone2{
  background-color:green;
  color:white;
  position:relative;
  width:20px;
  height:20px;
  left:22px;
}
#sub{
  background-color:yellow;
  width:400px;
  height:300px;
  position:absolute;
  top:15px;
  left:450px;
}
-->
</style>
<script language="javascript" src="prototype.js" charset="utf-8"></script>
<script>
<!--
function test(){
  var str = "Position.page($('mark')) : "
          + Object.inspect(Position.page($('mark'))) + "<br/>";
  Element.update('view', str);
  var option = {};
  if(!$('isSetLeft'  ).checked){ option['setLeft'  ] = false; }
  if(!$('isSetTop'   ).checked){ option['setTop'   ] = false; }
  if(!$('isSetWidth' ).checked){ option['setWidth' ] = false; }
  if(!$('isSetHeight').checked){ option['setHeight'] = false; }
  var offsetTop = $('offsetTop').value;
  try{option['offsetTop'] = parseInt(offsetTop||0);}catch(e){}
  var offsetLeft = $('offsetLeft').value;
  try{option['offsetLeft'] = parseInt(offsetLeft||0);}catch(e){}
  Position.clone('mark', 'clone1', option);
  Position.clone('mark', 'clone2', option);
}
function reset(){
  Element.setStyle('clone1', {position:'', height:'', width:'', top:'', left:''});
  Element.setStyle('clone2', {position:'', height:'', width:'', top:'', left:''});
} 
//-->
</script>
</head>
<body id="body">
<div id="scroll0">
<div id="base0">
<div id="scroll1">
<div id="base1">
<div id="mark">MARK</div>
</div>
</div>
</div>
</div>
<button onclick="test();">TEST</button>
<input type="checkbox" id="isSetLeft" checked/>isSetLeft&nbsp;
<input type="checkbox" id="isSetTop" checked/>isSetTop&nbsp;
<input type="checkbox" id="isSetWidth" checked/>isSetWidth&nbsp;
<input type="checkbox" id="isSetHeight" checked/>isSetHeight&nbsp;
<br/>
offsetTop:<input type="text" id="offsetTop"/>&nbsp;
offsetLeft:<input type="text" id="offsetLeft"/>
<br/>
<button onclick="reset();">RESET</button>
<br>
<div id="view"></div>
<div id="sub">
<div id="clone1" class="clone">C1</div>
<div id="clone2" class="clone">C2</div>
</div>
</body>
</html>

右の要素sub内に要素clone1と要素clone2があります。要素clone1はposition:absolute;、要素clone2はposition:relative;です。両者ともoffsetParentは要素subになります。

TESTボタンを押すと左の要素mark(要素scroll0>要素scroll1内)を第一引数(source)、要素clone1とclone2をそれぞれ第二引数にしてcloneメソッドを実行します。

要素clone1は要素markにピッタリ重なります。一方、clone2は要素sub内で位置が変わります。このときの要素clone2の位置は、要素markとブラウザウィンドウ左上端からの距離分、要素clone2のデフォルト位置(top、leftが0px)からずらしたものになります。

前述のcloneメソッド(無効)では、要素にposition:absolute;を設定していますが、それを上書きしているこのcloneメソッドではその処理がありません。よって、position:relative;の場合でも動作させることを想定していると思うのですが、どうにも使いどころが難しい気がします・・・。

チェックボックスを外すと、cloneメソッドの第三引数オブジェクトの各プロパティにfalseを設定します。これによりコピーされる情報が変わります。また、テキストボックスに数値を入れることでもプロパティが設定され、表示位置を調整することができます。