国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

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

2019-11-20 23:53:07
字體:
供稿:網(wǎng)友
當(dāng)然我們可以研究js庫的源碼, 也可以自己去發(fā)明輪子試試看, 其過程還是挺有趣的...下面我就來實(shí)現(xiàn)下頁面元素的拖拽功能
現(xiàn)在就開始著手實(shí)現(xiàn), 讓我們從最頂層的方法講起, 它用于初始化一個(gè)drag object, 方法的聲明如下
function DragObject(cfg)
這里的cfg我們用一個(gè)對(duì)象來傳入, 有點(diǎn)像Extjs里配置屬性
復(fù)制代碼 代碼如下:

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

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

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

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

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

前兩行就是做個(gè)判斷, 如果已經(jīng)開始對(duì)拖拽事件進(jìn)行監(jiān)聽或者清理過了, 就什么都不做直接return. 否則把listening狀態(tài)設(shè)為true, 表示我們開始監(jiān)聽啦, 把dragStart函數(shù)關(guān)聯(lián)到attachEl的mousedown事件上. 這里碰到個(gè)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);
}

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

這個(gè)方法并不復(fù)雜, 像其他的方法一樣, 我們先查看下狀態(tài)如何, 如果沒有在拖拽中或者已經(jīng)清理了, 那么什么都不做. 如果一切順利, 我們利用鼠標(biāo)當(dāng)前位置, 元素初始位置, 鼠標(biāo)初始位置, 和限定范圍(如果配置upperBound, lowerBound的話)來計(jì)算出一個(gè)結(jié)果點(diǎn), 通過apply方法我們把計(jì)算的坐標(biāo)賦給元素style.top和style.left, 讓拖拽元素確定其位置. 如果配置了moveCallback, 那么就調(diào)用下, 最后來個(gè)cancelEvent...這里的新坐標(biāo)運(yùn)算,類似于jquery的操作, 因?yàn)镻osition對(duì)象的每個(gè)方法都返回了一個(gè)Position對(duì)像...dragStart里還有個(gè)方法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......最后給出會(huì)用到的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, 這里有個(gè)參數(shù)stopCurrentDragging見名知意. dispose方法用于些處理工作, 如果你不想讓drag object能被拖拽,那么調(diào)用一下dispose就可以了, 至于下面的三個(gè)小方法isDragging, isListening, isDisposed一看便知, 返回相關(guān)的狀態(tài). 最后給個(gè)源碼的下拉鏈接 下載點(diǎn)我 歡迎園友留言, 交流!
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 天气| 武乡县| 德安县| 繁昌县| 临沂市| 崇仁县| 都兰县| 孝感市| 庆安县| 伊宁县| 上栗县| 荔波县| 蒲城县| 江口县| 宁南县| 冀州市| 新巴尔虎右旗| 东方市| 久治县| 新闻| 谢通门县| 同德县| 阳东县| 泽州县| 长白| 柘城县| 新干县| 理塘县| 西丰县| 阳新县| 洛扎县| 镇江市| 保山市| 澄城县| 黔西| 博罗县| 卫辉市| 彰武县| 宾川县| 黄石市| 芷江|