Posted on 2006-12-09 14:47
errorfun 閱讀(1500)
評論(2) 編輯 收藏 所屬分類:
JavaScript
問題起因:
在原來產(chǎn)品中實現(xiàn)的 ajax tree上面添加拖拽效果,為了方便,使用了prototype來簡化開發(fā)。代碼中使用了Poistion.absolutize來改變拖動標簽時改變它的坐標為絕對坐標顯示,拖動結(jié)束后再使用Poistion.relativize變回相對坐標。
解決過程:
其實一開始測試時都挺好的,但后來在tree上面使用時就發(fā)生問題了,在拖動過程,標簽跟著鼠標的移動而改變,沒有問題,但在鼠標釋放后,標簽并沒有放置在鼠標釋放的位置,而是向左和向上偏移了,而這偏移的距離剛好就是tree顯示位置的left和top。在對拖動結(jié)束后的位置計算的代碼,拖動過程坐標計算的代碼debug了一天沒有收獲后,突然想到把樣式中的滾動條設(shè)置(overflow-x : "auto", overflow-y: "scroll",)刪掉試下,沒想到就可以了。
經(jīng)過反復(fù)驗證,終于證實是滾動條惹的禍,接著就跟蹤了prototype中的相關(guān)代碼,在實現(xiàn)Position.absolutize方法時是這樣寫的:
Position.absolutize?
=
?
function
(element)?
{
????element?
=
?$(element);
????
if
?(element.style.position?
==
?'absolute')?
return
;
????Position.prepare();

var
?offsets?
=
?Position.positionedOffset(element);

????
var
?top?????
=
?offsets[
1
];
????
var
?left????
=
?offsets[
0
];
????
var
?width???
=
?element.clientWidth;
????
var
?height??
=
?element.clientHeight;

????element._originalLeft???
=
?left?
-
?parseFloat(element.style.left??
||
?
0
);
????element._originalTop????
=
?top??
-
?parseFloat(element.style.top?
||
?
0
);
????element._originalWidth??
=
?element.style.width;
????element._originalHeight?
=
?element.style.height;

????element.style.position?
=
?'absolute';
????element.style.top????
=
?top?
+
?'px';
????element.style.left???
=
?left?
+
?'px';
????element.style.width??
=
?width?
+
?'px';
????element.style.height?
=
?height?
+
?'px';
}
;
其中Position.positionedOffset就是取當前標簽到body的偏移量,然后將信息存入_original*的相關(guān)屬性中,等到調(diào)用Position.relativize時,再從這些_original*屬性中從新計算出當前標簽的相對位置。
再看一下Position.relativize的實現(xiàn):


Position.relativize?=?function(element)?
{
????element?=?$(element);

????if?(element.style.position?==?'relative')?
{
????????return;????
????}
????
????Position.prepare();
????
????element.style.position?=?'relative';
????var?top??=?parseFloat(element.style.top??||?0)?-?(element._originalTop??||?0);
????var?left?=?parseFloat(element.style.left?||?0)?-?(element._originalLeft?||?0);
????element.style.left???=?left?+?'px';
????element.style.top????=?top??+?'px';
????element.style.height?=?element._originalHeight;
????element.style.width??=?element._originalWidth;
};
嗯,處理得非常漂亮,沒有存在什么問題,以下是用來測試有html,試下會有什么效果
<div?style="height:50px"></div>
<div?style="width:500px;overflow-y:auto;height:300px">
<div?style="height:200px"></div>
<div?style="height:300px">
<div?id="test"?style="height:20px">test</div>
????<input?type="button"?value="abs"?onclick="Position.absolutize('test');">
????<input?type="button"?value="rel"?onclick="Position.relativize('test');">
????</div>
</div>
沒錯,按下abs按鈕后,test向下移了50px左右(第二個div的offsetTop),也向右移了一點(第二個div的offsetLeft)。(如果把overflow-y:aut去掉,則沒有此情況出現(xiàn))而再按下rel按鈕后,test能回復(fù)正常的位置,這就表示它在算法上沒有什么問題,問題出在了absolutize后的位置上了,而與位置相關(guān)的信息有 _originalTop 和_originalLeft,而它們的值是與Position.positionedOffset直接相關(guān)的,再查看了Position.positionedOffset的代碼:


Position.positionedOffset?=?function?(element)?
{????
????var?node?=?element.parentElement;????
????var?valueT?=?0,?valueL?=?0;
????

????do?
{
????????valueT?+=?element.offsetTop?||?0;
????????valueL?+=?element.offsetLeft?||?0;
????????element?=?element.offsetParent;

????????if?(element)?
{????????
????????????p?=?Element.getStyle(element,?"position");

????????????if?(p?==?"relative"?||?p?==?"absolute"?)
{
????????????????break;
????????????}
????????}
????}?while?(element);????????
????
????return?[valueL,?valueT];
};看起來似乎也無法從中找出什么毛病來。可是,查了一下html的相關(guān)文檔后,發(fā)現(xiàn)這段代碼存在著相當嚴重的bug。html文檔里,當樣式position取絕對坐標"absolute "時,其內(nèi)容如下:
absolute :Object is positioned relative to parent element's position—or to the body object if its parent element is not positioned—using the top and left properties.
結(jié)合文檔內(nèi)容,經(jīng)過測試,如果標簽的所有祖先節(jié)點中,有任何一個是可滾動的(overflow,overflow-y,overflow-x其中一個屬性的值為auto或scroll),那標簽的絕對定位就是在此標簽中的坐標位置,而不是對于BODY的。
所以positionedOffset方法沒有考慮到這種情況而處理,當然在一般情況下行得通了,所以代碼更改如下:


Position.positionedOffset?=?function?(element)?
{????
????????

????/**//*
?????*?經(jīng)過測試,如果標簽的所有祖先節(jié)點中,有任何一個是可滾動的(overflow,overflow-y,overflow-x其中一個屬性的值為auto或scroll),
?????*?那標簽的絕對定位就是在此標簽中的坐標位置,而不是對于BODY的。所以在返回時應(yīng)該將此祖先節(jié)點對于body的偏移量減掉.
?????*/
????????
????var?valueT?=?0,?valueL?=?0;
????

????do?
{
????????valueT?+=?element.offsetTop?||?0;
????????valueL?+=?element.offsetLeft?||?0;
????????element?=?element.offsetParent;

????????if?(element)?
{
????????????var?scrollable?=?[element.style.overflow,?element.style.overflowX,?element.style.overflowY];
????????????p?=?Element.getStyle(element,?"position");

????????????if?(p?==?"relative"?||?p?==?"absolute"?||?scrollable.include(?"auto"?)?||?scrollable.include(?"scroll"?))?
{
????????????????break;
????????????}
????????}
????}?while?(element);????????
????
????return?[valueL,?valueT];
};
至此,拖動后的標簽定位問題終于解決,看來有時候人應(yīng)該相信自己多一點,多懷疑一下別人的代碼,正所謂,讀書要善疑,更何況讀別人的程序。