欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Draggable Elements 元素拖拽功能實現(xiàn)代碼

 更新時間:2011年03月30日 23:59:31   作者:  
雖說js框架到處都是, 都封裝了很多實用的功能,能快速的讓我們實現(xiàn)如動畫,元素拖拽等功能, 不過由于好奇心的驅(qū)使, 有時想一探究竟, 看看一些功能是如何實現(xiàn)的
當(dāng)然我們可以研究js庫的源碼, 也可以自己去發(fā)明輪子試試看, 其過程還是挺有趣的...下面我就來實現(xiàn)下頁面元素的拖拽功能
現(xiàn)在就開始著手實現(xiàn), 讓我們從最頂層的方法講起, 它用于初始化一個drag object, 方法的聲明如下
function DragObject(cfg)
這里的cfg我們用一個對象來傳入, 有點像Extjs里配置屬性
復(fù)制代碼 代碼如下:

var dragObj = new DragObject({
el: 'exampleB',
attachEl: 'exampleBHandle',
lowerBound: new Position(0, 0), //position代表一個點,有屬性x,y下面會詳細(xì)講到
upperBound: new Position(500, 500),
startCallback: ..., // 開始拖拽時觸發(fā)的回調(diào) 這里均省略了
moveCallback: ..., // 拖拽過程中觸發(fā)的回調(diào)
endCallback: ..., // 拖拽結(jié)束觸發(fā)的回調(diào)
attachLater: ... // 是否立刻啟動拖拽事件的監(jiān)聽
});

配置參數(shù)中el可以是具體元素的id, 也可以直接是個dom對象 attachEl就是例子中的handle元素, 通過拖拽它來拖拽元素, lowerBound和upperBound是用于限定拖拽范圍的, 都是Position對象, 關(guān)于這個對象的封裝和作用我們下面會分析到,不急哈: ), 如果沒有傳入的話, 拖拽的范圍就沒有限制. startCallback, moveCallback, endCallback都是些回調(diào)函數(shù), attachLater為true或者false. 如果不是很明白看了下面的分析, 我想你肯定很快會清楚的..
下面就來寫Position, 代碼如下:
復(fù)制代碼 代碼如下:

function Position(x, y) {
this.X = x;
thix.Y = y;
}
Position.prototype = {
constructor: Position,
add : function(val) {
var newPos = new Position(this.X, this.Y);
if (val) {
newPos.X += val.X;
newPos.Y += val.Y;
}
return newPos;
},
subtract : function(val) {
var newPos = new Position(this.X, this.Y);
if (val) {
newPos.X -= val.X;
newPos.Y -= val.Y;
}
return newPos;
},
min : function(val) {
var newPos = new Position(this.X, this.Y);
if (val) {
newPos.X = this.X > val.X ? val.X : this.X;
newPos.Y = this.Y > val.Y ? val.Y : this.Y;
return newPos;
}
return newPos;
},
max : function(val) {
var newPos = new Position(this.X, this.Y);
if (val) {
newPos.X = this.X < val.X ? val.X : this.X;
newPos.Y = this.Y < val.Y ? val.Y : this.Y;
return newPos;
}
return newPos;
},
bound : function(lower, upper) {
var newPos = this.max(lower);
return newPos.min(upper);
},
check : function() {
var newPos = new Position(this.X, this.Y);
if (isNaN(newPos.X))
newPos.X = 0;
if (isNaN(newPos.Y))
newPos.Y = 0;
return newPos;
},
apply : function(el) {
if(typeof el == 'string')
el = document.getElementById(el);
if(!el) return;
el.style.left = this.X + 'px';
el.style.top = this.Y + 'px';
}
};

一個坐標(biāo)點的簡單封裝, 它保存兩個值: x, y坐標(biāo). 我們能夠通過add和substract方法跟別的坐標(biāo)點進(jìn)行+運算和-運算, 返回一個計算過的新坐標(biāo)點. min和max函數(shù)顧名思義用于跟其他坐標(biāo)點進(jìn)行比較,并返回其中較小和教大的值.bound方法返回一個在限定范圍內(nèi)的坐標(biāo)點. check方法用于確保屬性x, y的值是數(shù)字類型的, 否則會置0. 最后apply方法就是把屬性x,y作用于元素style.left和top上. 接著我把剩下的大部分代碼拿出來, 再一點一點看:
復(fù)制代碼 代碼如下:

function DragObject(cfg) {
var el = cfg.el,
attachEl = cfg.attachEl,
lowerBound = cfg.lowerBound,
upperBound = cfg.upperBound,
startCallback = cfg.startCallback,
moveCallback = cfg.moveCallback,
endCallback = cfg.endCallback,
attachLater = cfg.attachLater;
if(typeof el == 'string')
el = document.getElementById(el);
if(!el) return;
if(lowerBound != undefined && upperBound != undefined) {
var tempPos = lowerBound.min(upperBound);
upperBound = lowerBound.max(upperBound);
lowerBound = tempPos;
}
var cursorStartPos,
elementStartPos,
dragging = false,
listening = false,
disposed = false;
function dragStart(eventObj) {
if(dragging || !listening || disposed) return;
dragging = true;
if(startCallback)
startCallback(eventObj, el);
cursorStartPos = absoluteCursorPosition(eventObj);
elementStartPos = new Position(parseInt(getStyle(el, 'left')), parseInt(getStyle(el, 'top')));
elementStartPos = elementStartPos.check();
hookEvent(document, 'mousemove', dragGo);
hookEvent(document, 'mouseup', dragStopHook);
return cancelEvent(eventObj);
}
function dragGo(e) {
if(!dragging || disposed) return;
var newPos = absoluteCursorPosition(e);
newPos = newPos.add(elementStartPos)
.subtract(cursorStartPos)
.bound(lowerBound, upperBound);
newPos.apply(el);
if(moveCallback)
moveCallback(newPos, el);
return cancelEvent(e);
}
function dragStopHook(e) {
dragStop();
return cancelEvent(e);
}
function dragStop() {
if(!dragging || disposed) return;
unhookEvent(document, 'mousemove', dragGo);
unhookEvent(document, 'mouseup', dragStopHook);
cursorStartPos = null;
elementStartPos = null;
if(endCallback)
endCallback(el);
dragging = false;
}
this.startListening = function() {
if(listening || disposed) return;
listening = true;
hookEvent(attachEl, 'mousedown', dragStart);
};
this.stopListening = function(stopCurrentDragging) {
if(!listening || disposed)
return;
unhookEvent(attachEl, 'mousedown', dragStart);
listening = false;
if(stopCurrentDragging && dragging)
dragStop();
};
this.dispose = function() {
if(disposed) return;
this.stopListening(true);
el = null;
attachEl = null;
lowerBound = null;
upperBound = null;
startCallback = null;
moveCallback = null;
endCallback = null;
disposed = true;
};
this.isDragging = function() {
return dragging;
};
this.isListening = function() {
return listening;
};
this.isDisposed = function() {
return disposed;
};
if(typeof attachEl == 'string')
attachEl = document.getElementById(attachEl);
// 如果沒有配置, 或者沒找到該Dom對象, 則用el
if(!attachEl) attachEl = el;
if(!attachLater)
this.startListening();
}

其中一些未給出方法, 在往下分析的過程中, 會一一給出....
我們先通過cfg來使el和attachEl指向?qū)嶋H的Dom對象, 如果attachEl沒配置或者沒找到對應(yīng)元素則用el替代. 我們同時設(shè)置了一些在拖拽中要用到的變量. cursorStartPos用于保存鼠標(biāo)按下開始拖拽時鼠標(biāo)的坐標(biāo)點. elementStartPos用于保存元素開始拖拽時的起始點. dragging, listening, disposed是一些狀態(tài)變量. listening: 指drag object是否正在監(jiān)聽拖拽開始事件. dragging: 元素是否正在被拖拽. disposed: drag object被清理, 不能再被拖拽了.
在代碼的最后, 我們看到如果attachLater不為true, 那么就調(diào)用startListening, 這是一個 public方法定義在drag object中, 讓我們看下它的實現(xiàn)
復(fù)制代碼 代碼如下:

this.startListening = function() {
if(listening || disposed) return;
listening = true;
hookEvent(attachEl, 'mousedown', dragStart);
};

前兩行就是做個判斷, 如果已經(jīng)開始對拖拽事件進(jìn)行監(jiān)聽或者清理過了, 就什么都不做直接return. 否則把listening狀態(tài)設(shè)為true, 表示我們開始監(jiān)聽啦, 把dragStart函數(shù)關(guān)聯(lián)到attachEl的mousedown事件上. 這里碰到個hookEvent函數(shù), 我們來看看它的樣子:
復(fù)制代碼 代碼如下:

function hookEvent(el, eventName, callback) {
if(typeof el == 'string')
el = document.getElementById(el);
if(!el) return;
if(el.addEventListener)
el.addEventListener(eventName, callback, false);
else if (el.attachEvent)
el.attachEvent('on' + eventName, callback);
}

其實也沒什么, 就是對元素事件的監(jiān)聽做了個跨瀏覽器的封裝, 同樣的unhookEvent方法如下
復(fù)制代碼 代碼如下:

function unhookEvent(el, eventName, callback) {
if(typeof el == 'string')
el = document.getElementById(el);
if(!el) return;
if(el.removeEventListener)
el.removeEventListener(eventName, callback, false);
else if(el.detachEvent)
el.detachEvent('on' + eventName, callback);
}

接著我們來看看dragStart函數(shù)的實現(xiàn), 它是drag object的一個私有函數(shù)
復(fù)制代碼 代碼如下:

function dragStart(eventObj) {
if(dragging || !listening || disposed) return;
dragging = true;
if(startCallback)
startCallback(eventObj, el);
cursorStartPos = absoluteCursorPosition(eventObj);
elementStartPos = new Position(parseInt(getStyle(el, 'left')), parseInt(getStyle(el, 'top')));
elementStartPos = elementStartPos.check();
hookEvent(document, 'mousemove', dragGo);
hookEvent(document, 'mouseup', dragStopHook);
return cancelEvent(eventObj);
}

attachEl所指的dom對象捕獲到mousedown事件后調(diào)用此函數(shù). 首先我們先確定drag object在一個適合拖拽的狀態(tài), 如果拖拽正在進(jìn)行, 或者沒有在監(jiān)聽拖拽事件, 再或者已經(jīng)處理完"后事"了, 那就什么都不做. 如果一切ok, 我們把 dragging狀態(tài)設(shè)為true, 然后"開工了", 如果startCallback定義了, 那我們就調(diào)用下它, 以mousedown event和el為參數(shù). 接著我們定位鼠標(biāo)的絕對位置, 保存到cursorStartPos中. 然后拿到拖拽元素當(dāng)前的top, left,封裝成Position對象保存到elementStartPos中. 保險起見我們檢查下elementStartPos中屬性是否合法. 再看兩個hookEvent的調(diào)用, 一個是mousemove事件, 表示正在dragging,調(diào)用dragGo函數(shù). 一個是mouseup事件, 代表拖拽的結(jié)束, 調(diào)用dragStopHook函數(shù).可能你會問,為什么事件綁定在document上, 而不是要拖拽的元素上,比如我們這里的el或者attachEl.因為考慮到直接將事件綁定到元素上,可能由于瀏覽器的一些延時會影響效果,所以直接把事件綁定到document上. 如果實在不是很理解, 或許影響也不大: P.... 看最后一句話中的cancelEvent(eventObj)
復(fù)制代碼 代碼如下:

function cancelEvent(e) {
e = e ? e : window.event;
if(e.stopPropagation)
e.stopPropagation();
if(e.preventDefault)
e.preventDefault();
e.cancelBubble = true;
e.returnValue = false;
return false;
}

用于停止冒泡, 阻止默認(rèn)事件, 可以理解為安全考慮....在dragStart中有些方法需要介紹下,先來 看看absoluteCursorPosition, 再看下getStyle
復(fù)制代碼 代碼如下:

function absoluteCursorPosition(e) {
e = e ? e : window.event;
var x = e.clientX + (document.documentElement || document.body).scrollLeft;
var y = e.clientY + (document.documentElement || document.body).scrollTop;
return new Position(x, y);
}

此方法就只是用于獲得鼠標(biāo)在瀏覽器中的絕對位置, 把滾動條考慮進(jìn)去就行了
復(fù)制代碼 代碼如下:

function getStyle(el, property) {
if(typeof el == 'string')
el = document.getElementById(el);
if(!el || !property) return;
var value = el.style[property];
if(!value) {
if(document.defaultView && document.defaultView.getComputedStyle) {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css.getPropertyValue(property) : null;
} else if (el.currentStyle) {
value = el.currentStyle[property];
}
}
return value == 'auto' ? '' : value;
}

getStyle方法用于獲取元素的css屬性值, 這樣不管你樣式是寫成內(nèi)聯(lián)形式還是定義在css中, 我們都能拿到正確的值, 當(dāng)然我們這里只要獲取元素的top, left屬性即可..下面真正處理拖拽工作的方法dragGo
復(fù)制代碼 代碼如下:

function dragGo(e) {
if(!dragging || disposed) return;
var newPos = absoluteCursorPosition(e);
newPos = newPos.add(elementStartPos)
.subtract(cursorStartPos)
.bound(lowerBound, upperBound);
newPos.apply(el);
if(moveCallback)
moveCallback(newPos, el);
return cancelEvent(e);
}

這個方法并不復(fù)雜, 像其他的方法一樣, 我們先查看下狀態(tài)如何, 如果沒有在拖拽中或者已經(jīng)清理了, 那么什么都不做. 如果一切順利, 我們利用鼠標(biāo)當(dāng)前位置, 元素初始位置, 鼠標(biāo)初始位置, 和限定范圍(如果配置upperBound, lowerBound的話)來計算出一個結(jié)果點, 通過apply方法我們把計算的坐標(biāo)賦給元素style.top和style.left, 讓拖拽元素確定其位置. 如果配置了moveCallback, 那么就調(diào)用下, 最后來個cancelEvent...這里的新坐標(biāo)運算,類似于jquery的操作, 因為Position對象的每個方法都返回了一個Position對像...dragStart里還有個方法dragStopHook
復(fù)制代碼 代碼如下:

function dragStopHook(e) {
dragStop();
return cancelEvent(e);
}
function dragStop() {
if(!dragging || disposed) return;
unhookEvent(document, 'mousemove', dragGo);
unhookEvent(document, 'mouseup', dragStopHook);
cursorStartPos = null;
elementStartPos = null;
if(endCallback)
endCallback(el);
dragging = false;
}

關(guān)鍵看下dragStop方法, 同樣先判斷下狀態(tài), 一切ok的話, 我們移除事件的綁定mousemove和mouseup, 并把 cursorStartPos和elementStartPos的值釋放掉, 一次拖拽結(jié)束啦..如果配置了endCallback那就調(diào)用下, 最后把dragging狀態(tài)設(shè)置為false......最后給出會用到的public方法
復(fù)制代碼 代碼如下:

this.stopListening = function(stopCurrentDragging) {
if(!listening || disposed)
return;
unhookEvent(attachEl, 'mousedown', dragStart);
listening = false;
if(stopCurrentDragging && dragging)
dragStop();
};
this.dispose = function() {
if(disposed) return;
this.stopListening(true);
el = null;
attachEl = null;
lowerBound = null;
upperBound = null;
startCallback = null;
moveCallback = null;
endCallback = null;
disposed = true;
};
this.isDragging = function() {
return dragging;
};
this.isListening = function() {
return listening;
};
this.isDisposed = function() {
return disposed;
};

stopListening移除監(jiān)聽拖拽的mousedown事件, 把監(jiān)聽狀態(tài)listening設(shè)置為false, 這里有個參數(shù)stopCurrentDragging見名知意. dispose方法用于些處理工作, 如果你不想讓drag object能被拖拽,那么調(diào)用一下dispose就可以了, 至于下面的三個小方法isDragging, isListening, isDisposed一看便知, 返回相關(guān)的狀態(tài). 最后給個源碼的下拉鏈接 下載點我 歡迎園友留言, 交流!

相關(guān)文章

最新評論