JS拖拽插件實(shí)現(xiàn)步驟
這篇文章詳細(xì)介紹了JS拖拽插件的實(shí)現(xiàn)步驟,主要從以下六步做詳細(xì)分析,具體內(nèi)容如下:
一、js拖拽插件的原理
二、根據(jù)原理實(shí)現(xiàn)的最基本效果
三、代碼抽象與優(yōu)化
四、擴(kuò)展:有效的拖拽元素
五、性能優(yōu)化和總結(jié)
六、jquery插件化
js拖拽是常見的網(wǎng)頁(yè)效果,本文將從零開始實(shí)現(xiàn)一個(gè)簡(jiǎn)單的js插件。
一、js拖拽插件的原理
常見的拖拽操作是什么樣的呢?整過過程大概有下面幾個(gè)步驟:
1、用鼠標(biāo)點(diǎn)擊被拖拽的元素
2、按住鼠標(biāo)不放,移動(dòng)鼠標(biāo)
3、拖拽元素到一定位置,放開鼠標(biāo)
這里的過程涉及到三個(gè)dom事件:onmousedown,onmousemove,onmouseup。所以拖拽的基本思路就是:
1、用鼠標(biāo)點(diǎn)擊被拖拽的元素觸發(fā)onmousedown
(1)設(shè)置當(dāng)前元素的可拖拽為true,表示可以拖拽
(2)記錄當(dāng)前鼠標(biāo)的坐標(biāo)x,y
?。?)記錄當(dāng)前元素的坐標(biāo)x,y
2、移動(dòng)鼠標(biāo)觸發(fā)onmousemove
?。?)判斷元素是否可拖拽,如果是則進(jìn)入步驟2,否則直接返回
?。?)如果元素可拖拽,則設(shè)置元素的坐標(biāo)
元素的x坐標(biāo) = 鼠標(biāo)移動(dòng)的橫向距離+元素本來的x坐標(biāo) = 鼠標(biāo)現(xiàn)在的x坐標(biāo) - 鼠標(biāo)之前的x坐標(biāo) + 元素本來的x坐標(biāo)
元素的y坐標(biāo) = 鼠標(biāo)移動(dòng)的橫向距離+元素本來的y坐標(biāo) = 鼠標(biāo)現(xiàn)在的y坐標(biāo) - 鼠標(biāo)之前的y坐標(biāo) + 元素本來的y坐標(biāo)
3、放開鼠標(biāo)觸發(fā)onmouseup
?。?)將鼠標(biāo)的可拖拽狀態(tài)設(shè)置成false
回到頂部
二、根據(jù)原理實(shí)現(xiàn)的最基本效果
在實(shí)現(xiàn)基本的效果之前,有幾點(diǎn)需要說明的:
1、元素想要被拖動(dòng),它的postion屬性一定要是relative或absolute
2、通過event.clientX和event.clientY獲取鼠標(biāo)的坐標(biāo)
3、onmousemove是綁定在document元素上而不是拖拽元素本身,這樣能解決快速拖動(dòng)造成的延遲或停止移動(dòng)的問題
代碼如下:
var dragObj = document.getElementById("test");
dragObj.style.left = "px";
dragObj.style.top = "px";
var mouseX, mouseY, objX, objY;
var dragging = false;
dragObj.onmousedown = function (event) {
event = event || window.event;
dragging = true;
dragObj.style.position = "relative";
mouseX = event.clientX;
mouseY = event.clientY;
objX = parseInt(dragObj.style.left);
objY = parseInt(dragObj.style.top);
}
document.onmousemove = function (event) {
event = event || window.event;
if (dragging) {
dragObj.style.left = parseInt(event.clientX - mouseX + objX) + "px";
dragObj.style.top = parseInt(event.clientY - mouseY + objY) + "px";
}
}
document.onmouseup = function () {
dragging = false;
}
三、代碼抽象與優(yōu)化
上面的代碼要做成插件,要將其抽象出來,基本結(jié)構(gòu)如下:
; (function (window, undefined) {
function Drag(ele) {}
window.Drag = Drag;
})(window, undefined);
用自執(zhí)行匿名函數(shù)將代碼包起來,內(nèi)部定義Drag方法并暴露到全局中,直接調(diào)用Drag,傳入被拖拽的元素。
首先對(duì)一些常用的方法進(jìn)行簡(jiǎn)單的封裝:
; (function (window, undefined) {
var dom = {
//綁定事件
on: function (node, eventName, handler) {
if (node.addEventListener) {
node.addEventListener(eventName, handler);
}
else {
node.attachEvent("on" + eventName, handler);
}
},
//獲取元素的樣式
getStyle: function (node, styleName) {
var realStyle = null;
if (window.getComputedStyle) {
realStyle = window.getComputedStyle(node, null)[styleName];
}
else if (node.currentStyle) {
realStyle = node.currentStyle[styleName];
}
return realStyle;
},
//獲取設(shè)置元素的樣式
setCss: function (node, css) {
for (var key in css) {
node.style[key] = css[key];
}
}
};
window.Drag = Drag;
})(window, undefined);
在一個(gè)拖拽操作中,存在著兩個(gè)對(duì)象:被拖拽的對(duì)象和鼠標(biāo)對(duì)象,我們定義了下面的兩個(gè)對(duì)象以及它們對(duì)應(yīng)的操作:
首先的拖拽對(duì)象,它包含一個(gè)元素節(jié)點(diǎn)和拖拽之前的坐標(biāo)x和y:
function DragElement(node) {
this.node = node;//被拖拽的元素節(jié)點(diǎn)
this.x = ;//拖拽之前的x坐標(biāo)
this.y = ;//拖拽之前的y坐標(biāo)
}
DragElement.prototype = {
constructor: DragElement,
init: function () {
this.setEleCss({
"left": dom.getStyle(node, "left"),
"top": dom.getStyle(node, "top")
})
.setXY(node.style.left, node.style.top);
},
//設(shè)置當(dāng)前的坐標(biāo)
setXY: function (x, y) {
this.x = parseInt(x) || ;
this.y = parseInt(y) || ;
return this;
},
//設(shè)置元素節(jié)點(diǎn)的樣式
setEleCss: function (css) {
dom.setCss(this.node, css);
return this;
}
}
還有一個(gè)對(duì)象是鼠標(biāo),它主要包含x坐標(biāo)和y坐標(biāo):
function Mouse() {
this.x = ;
this.y = ;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
這是在拖拽操作中定義的兩個(gè)對(duì)象。
如果一個(gè)頁(yè)面可以有多個(gè)拖拽元素,那應(yīng)該注意什么:
1、每個(gè)元素對(duì)應(yīng)一個(gè)拖拽對(duì)象實(shí)例
2、每個(gè)頁(yè)面只能有一個(gè)正在拖拽中的元素
為此,我們定義了唯一一個(gè)對(duì)象用來保存相關(guān)的配置:
var draggableConfig = {
zIndex: ,
draggingObj: null,
mouse: new Mouse()
};
這個(gè)對(duì)象中有三個(gè)屬性:
(1)zIndex:用來賦值給拖拽對(duì)象的zIndex屬性,有多個(gè)拖拽對(duì)象時(shí),當(dāng)兩個(gè)拖拽對(duì)象重疊時(shí),會(huì)造成當(dāng)前拖拽對(duì)象有可能被擋住,通過設(shè)置zIndex使其顯示在最頂層
(2)draggingObj:用來保存正在拖拽的對(duì)象,在這里去掉了前面的用來判斷是否可拖拽的變量,通過draggingObj來判斷當(dāng)前是否可以拖拽以及獲取相應(yīng)的拖拽對(duì)象
(3)mouse:唯一的鼠標(biāo)對(duì)象,用來保存當(dāng)前鼠標(biāo)的坐標(biāo)等信息
最后是綁定onmousedown,onmouseover,onmouseout事件,整合上面的代碼如下:
; (function (window, undefined) {
var dom = {
//綁定事件
on: function (node, eventName, handler) {
if (node.addEventListener) {
node.addEventListener(eventName, handler);
}
else {
node.attachEvent("on" + eventName, handler);
}
},
//獲取元素的樣式
getStyle: function (node, styleName) {
var realStyle = null;
if (window.getComputedStyle) {
realStyle = window.getComputedStyle(node, null)[styleName];
}
else if (node.currentStyle) {
realStyle = node.currentStyle[styleName];
}
return realStyle;
},
//獲取設(shè)置元素的樣式
setCss: function (node, css) {
for (var key in css) {
node.style[key] = css[key];
}
}
};
//#region 拖拽元素類
function DragElement(node) {
this.node = node;
this.x = ;
this.y = ;
}
DragElement.prototype = {
constructor: DragElement,
init: function () {
this.setEleCss({
"left": dom.getStyle(node, "left"),
"top": dom.getStyle(node, "top")
})
.setXY(node.style.left, node.style.top);
},
setXY: function (x, y) {
this.x = parseInt(x) || ;
this.y = parseInt(y) || ;
return this;
},
setEleCss: function (css) {
dom.setCss(this.node, css);
return this;
}
}
//#endregion
//#region 鼠標(biāo)元素
function Mouse() {
this.x = ;
this.y = ;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
//#endregion
//拖拽配置
var draggableConfig = {
zIndex: ,
draggingObj: null,
mouse: new Mouse()
};
function Drag(ele) {
this.ele = ele;
function mouseDown(event) {
var ele = event.target || event.srcElement;
draggableConfig.mouse.setXY(event.clientX, event.clientY);
draggableConfig.draggingObj = new DragElement(ele);
draggableConfig.draggingObj
.setXY(ele.style.left, ele.style.top)
.setEleCss({
"zIndex": draggableConfig.zIndex++,
"position": "relative"
});
}
ele.onselectstart = function () {
//防止拖拽對(duì)象內(nèi)的文字被選中
return false;
}
dom.on(ele, "mousedown", mouseDown);
}
dom.on(document, "mousemove", function (event) {
if (draggableConfig.draggingObj) {
var mouse = draggableConfig.mouse,
draggingObj = draggableConfig.draggingObj;
draggingObj.setEleCss({
"left": parseInt(event.clientX - mouse.x + draggingObj.x) + "px",
"top": parseInt(event.clientY - mouse.y + draggingObj.y) + "px"
});
}
})
dom.on(document, "mouseup", function (event) {
draggableConfig.draggingObj = null;
})
window.Drag = Drag;
})(window, undefined);
調(diào)用方法:Drag(document.getElementById("obj"));
注意的一點(diǎn),為了防止選中拖拽元素中的文字,通過onselectstart事件處理程序return false來處理這個(gè)問題。
四、擴(kuò)展:有效的拖拽元素
我們常見的一些拖拽效果很有可能是這樣的:

彈框的頂部是可以進(jìn)行拖拽操作的,內(nèi)容區(qū)域是不可拖拽的,怎么實(shí)現(xiàn)這樣的效果呢:
首先優(yōu)化拖拽元素對(duì)象如下,增加一個(gè)目標(biāo)元素target,表示被拖拽對(duì)象,在上圖的登錄框中,就是整個(gè)登錄窗口。
被記錄和設(shè)置坐標(biāo)的拖拽元素就是這個(gè)目標(biāo)元素,但是它并不是整個(gè)部分都是拖拽的有效部分。我們?cè)趆tml結(jié)構(gòu)中為拖拽的有效區(qū)域添加類draggable表示有效拖拽區(qū)域:
<div id="obj" class="dialog" style="position:relative;left:px">
<div class="header draggable">
拖拽的有效元素
</div>
<div class="content">
拖拽對(duì)象
</div>
</div>
然后修改Drag方法如下:
function drag(ele) {
var dragNode = (ele.querySelector(".draggable") || ele);
dom.on(dragNode, "mousedown", function (event) {
var dragElement = draggableConfig.dragElement = new DragElement(ele);
draggableConfig.mouse.setXY(event.clientX, event.clientY);
draggableConfig.dragElement
.setXY(dragElement.target.style.left, dragElement.target.style.top)
.setTargetCss({
"zIndex": draggableConfig.zIndex++,
"position": "relative"
});
}).on(dragNode, "mouseover", function () {
dom.setCss(this, draggableStyle.dragging);
}).on(dragNode, "mouseout", function () {
dom.setCss(this, draggableStyle.defaults);
});
}
主要修改的是綁定mousedown的節(jié)點(diǎn)變成了包含draggable類的有效元素,如果不含有draggable,則整個(gè)元素都是有效元素。
五、性能優(yōu)化和總結(jié)
由于onmousemove在一直調(diào)用,會(huì)造成一些性能問題,我們可以通過setTimout來延遲綁定onmousemove事件,改進(jìn)move函數(shù)如下
function move(event) {
if (draggableConfig.dragElement) {
var mouse = draggableConfig.mouse,
dragElement = draggableConfig.dragElement;
dragElement.setTargetCss({
"left": parseInt(event.clientX - mouse.x + dragElement.x) + "px",
"top": parseInt(event.clientY - mouse.y + dragElement.y) + "px"
});
dom.off(document, "mousemove", move);
setTimeout(function () {
dom.on(document, "mousemove", move);
}, );
}
}
總結(jié):
整個(gè)拖拽插件的實(shí)現(xiàn)其實(shí)很簡(jiǎn)單,主要是要注意幾點(diǎn)
1、實(shí)現(xiàn)思路:元素拖拽位置的改變就等于鼠標(biāo)改變的距離,關(guān)鍵在于獲取鼠標(biāo)的變動(dòng)和元素原本的坐標(biāo)
2、通過setTimeout來延遲加載onmousemove事件來提供性能
六、jquery插件化
簡(jiǎn)單地將其封裝成jquery插件,主要是相關(guān)的dom方法替換成jquery方法來操作
; (function ($, window, undefined) {
//#region 拖拽元素類
function DragElement(node) {
this.target = node;
node.onselectstart = function () {
//防止拖拽對(duì)象內(nèi)的文字被選中
return false;
}
}
DragElement.prototype = {
constructor: DragElement,
setXY: function (x, y) {
this.x = parseInt(x) || ;
this.y = parseInt(y) || ;
return this;
},
setTargetCss: function (css) {
$(this.target).css(css);
return this;
}
}
//#endregion
//#region 鼠標(biāo)元素
function Mouse() {
this.x = ;
this.y = ;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
//#endregion
//拖拽配置
var draggableConfig = {
zIndex: ,
dragElement: null,
mouse: new Mouse()
};
var draggableStyle = {
dragging: {
cursor: "move"
},
defaults: {
cursor: "default"
}
}
var $document = $(document);
function drag($ele) {
var $dragNode = $ele.find(".draggable");
$dragNode = $dragNode.length > ? $dragNode : $ele;
$dragNode.on({
"mousedown": function (event) {
var dragElement = draggableConfig.dragElement = new DragElement($ele.get());
draggableConfig.mouse.setXY(event.clientX, event.clientY);
draggableConfig.dragElement
.setXY(dragElement.target.style.left, dragElement.target.style.top)
.setTargetCss({
"zIndex": draggableConfig.zIndex++,
"position": "relative"
});
},
"mouseover": function () {
$(this).css(draggableStyle.dragging);
},
"mouseout": function () {
$(this).css(draggableStyle.defaults);
}
})
}
function move(event) {
if (draggableConfig.dragElement) {
var mouse = draggableConfig.mouse,
dragElement = draggableConfig.dragElement;
dragElement.setTargetCss({
"left": parseInt(event.clientX - mouse.x + dragElement.x) + "px",
"top": parseInt(event.clientY - mouse.y + dragElement.y) + "px"
});
$document.off("mousemove", move);
setTimeout(function () {
$document.on("mousemove", move);
}, );
}
}
$document.on({
"mousemove": move,
"mouseup": function () {
draggableConfig.dragElement = null;
}
});
$.fn.drag = function (options) {
drag(this);
}
})(jQuery, window, undefined)
以上就是本文對(duì)JS拖拽插件實(shí)現(xiàn)步驟的詳細(xì)介紹,希望對(duì)大家有所幫助。
相關(guān)文章
簡(jiǎn)單實(shí)現(xiàn)節(jié)流函數(shù)和防抖函數(shù)過程解析
這篇文章主要介紹了簡(jiǎn)單實(shí)現(xiàn)節(jié)流函數(shù)和防抖函數(shù)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
JavaScript canvas繪制圓形加載進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了JavaScript canvas繪制圓形加載進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
javascript在子頁(yè)面中函數(shù)無(wú)法調(diào)試問題解決方法
遇到在子頁(yè)面中提交的時(shí)候會(huì)無(wú)法能夠調(diào)試javascript代碼的情況出現(xiàn),下面有個(gè)不錯(cuò)的解決方法,希望對(duì)大家有所幫助2014-01-01
Bootstrap富文本組件wysiwyg數(shù)據(jù)保存到mysql的方法
這篇文章主要為大家詳細(xì)介紹了Bootstrap富文本組件wysiwyg數(shù)據(jù)保存到mysql的方法,感興趣的小伙伴們可以參考一下2016-05-05

