前端學習筆記の拖拽(一)#
源碼地址:https://github.com/WZOnePiece/study-draggable
引言:9月開始的半個月陶衅,由于項目和一些朋友的詢問,我接觸了關(guān)于前端拖拽的一些問題膨俐。不過本人由于只是一個前端菜鳥焚刺,接觸前端時間滿打滿算门烂,從0到現(xiàn)在也就一年左右,對于一些知識了解的還不是很全面和深刻蔓姚。所以赂乐,我就打算好好學習下前端拖拽得一些相關(guān)知識咖气。這就是這篇“史詩級的錦繡文章”的誕生記O(∩_∩)O挖滤。
借鑒:http://www.cnblogs.com/lrzw32/p/4696655.html
js拖拽插件的原理##
常見的拖拽操作是什么樣的呢斩松?整過過程大概有下面幾個步驟:
1觉既、用鼠標點擊被拖拽的元素
2、按住鼠標不放钧椰,移動鼠標
3嫡霞、拖拽元素到一定位置,放開鼠標
這里的過程涉及到三個dom事件:onmousedown,onmousemove,onmouseup诊沪。所以拖拽的基本思路就是:
1曾撤、用鼠標點擊被拖拽的元素觸發(fā)onmousedown
(1)設(shè)置當前元素的可拖拽為true挤悉,表示可以拖拽
(2)記錄當前鼠標的坐標x,y
(3)記錄當前元素的坐標x,y
2、移動鼠標觸發(fā)onmousemove
(1)判斷元素是否可拖拽昏鹃,如果是則進入步驟2,否則直接返回
(2)如果元素可拖拽衅斩,則設(shè)置元素的坐標
元素的x坐標 = 鼠標移動的橫向距離+元素本來的x坐標 = 鼠標現(xiàn)在的x坐標 - 鼠標之前的x坐標 + 元素本來的x坐標
元素的y坐標 = 鼠標移動的橫向距離+元素本來的y坐標 = 鼠標現(xiàn)在的y坐標 - 鼠標之前的y坐標 + 元素本來的y坐標
3盆顾、放開鼠標觸發(fā)onmouseup
(1)將鼠標的可拖拽狀態(tài)設(shè)置成false
基本效果##
在實現(xiàn)基本的效果之前怠褐,有幾點需要說明的:
1畏梆、元素想要被拖動,它的postion屬性一定要是relative或absolute
2奈懒、通過event.clientX和event.clientY獲取鼠標的坐標
3奠涌、onmousemove是綁定在document元素上而不是拖拽元素本身,這樣能解決快速拖動造成的延遲或停止移動的問題
代碼如下:
var dragObj = document.getElementById("test");
dragObj.style.left = "0px";
dragObj.style.top = "0px";
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)化##
上面的代碼要做成插件磷杏,要將其抽象出來溜畅,基本思路如下:
; (function (window, undefined) {
function Drag(ele) {}
window.Drag = Drag;
})(window, undefined);
用自執(zhí)行匿名函數(shù)將代碼包起來,內(nèi)部定義Drag方法并暴露到全局中慈格,直接調(diào)用Drag蒜田,傳入被拖拽的元素冲粤。
前面加入梯捕; 避免一些不必要錯誤傀顾。
首先對一些常用的方法進行簡單的封裝:
; (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);
在一個拖拽操作中,存在著兩個對象:被拖拽的對象和鼠標對象错英,我們定義了下面的兩個對象以及它們對應(yīng)的操作:
首先的拖拽對象椭岩,它包含一個元素節(jié)點和拖拽之前的坐標x和y:
function DragElement(node) {
this.node = node;//被拖拽的元素節(jié)點
this.x = 0;//拖拽之前的x坐標
this.y = 0;//拖拽之前的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);
},
//設(shè)置當前的坐標
setXY: function (x, y) {
this.x = parseInt(x) || 0;
this.y = parseInt(y) || 0;
return this;
},
//設(shè)置元素節(jié)點的樣式
setEleCss: function (css) {
dom.setCss(this.node, css);
return this;
}
}
還有一個對象是鼠標,它主要包含x坐標和y坐標:
function Mouse() {
this.x = 0;
this.y = 0;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
這是在拖拽操作中定義的兩個對象塌计。
如果一個頁面可以有多個拖拽元素,那應(yīng)該注意什么:
1热芹、每個元素對應(yīng)一個拖拽對象實例
2伊脓、每個頁面只能有一個正在拖拽中的元素
為此报腔,我們定義了唯一一個對象用來保存相關(guān)的配置:
var draggableConfig = {
zIndex: 1,
draggingObj: null,
mouse: new Mouse()
};
這個對象中有三個屬性:
(1)zIndex:用來賦值給拖拽對象的zIndex屬性邪狞,有多個拖拽對象時帆卓,當兩個拖拽對象重疊時剑令,會造成當前拖拽對象有可能被擋住吁津,通過設(shè)置zIndex使其顯示在最頂層
(2)draggingObj:用來保存正在拖拽的對象碍脏,在這里去掉了前面的用來判斷是否可拖拽的變量,通過draggingObj來判斷當前是否可以拖拽以及獲取相應(yīng)的拖拽對象
(3)mouse:唯一的鼠標對象糊探,用來保存當前鼠標的坐標等信息
最后是綁定onmousedown褥紫,onmouseover髓考,onmouseout事件氨菇,整合上面的代碼如下:
/*
author: NARUTOne,wz;
creatTime: 2016/9/20;
developing: 可以擴展:添加父元素限制门驾;多個同級元素拖拽順序排列等。
*/
; (function(window, undefined) {
// 拖拽對象的事件綁定竣灌;樣式獲取初嘹、設(shè)置
var dom = {
on: function(node, eventName, handler) {
if(node.addEventListener) {
console.log(node);
node.addEventListener(eventName, handler, false);
}
else {//IE
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) {//IE
realStyle = node.currentStyle[styleName];
}
else if(node.style) {
realStyle = node.style[styleName];
}
return realStyle;
},
setCss: function(node, css) {
for(var key in css) {
node.style[key] = css[key];
}
}
};
// 拖拽對象定義
function DragElement(node) {
this.node = node;
this.x = 0;
this.y = 0;
}
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è)置當前坐標
setXY: function(x, y) {
this.x = parseInt(x) || 0;
this.y = parseInt(y) || 0;
return this;
},
// 設(shè)置元素節(jié)點的樣式
setEleCss: function(css) {
dom.setCss(this.node, css);
return this;
}
}
// 鼠標對象
function Mouse() {
this.x = 0;
this.y = 0;
}
Mouse.prototype.setXY = function(x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
// 拖拽配置
var draggableConfig = {
zIndex: 1,
draggingObj: null,
mouse: new Mouse(),
parent:document,
dragEle:null
}
// 拖拽實現(xiàn)
function Drag(ele,parent) {
this.ele = ele;
function mouseDown(event) {
var elem = this;
draggableConfig.mouse.setXY(event.clientX, event.clientY);
draggableConfig.draggingObj = new DragElement(elem);
draggableConfig.draggingObj
.setXY(elem.style.left, elem.style.top)
.setEleCss({
"zIndex": draggableConfig.zIndex ++,
"position": "absolute"
});
}
draggableConfig.parent = parent;
draggableConfig.dragEle = ele;
ele.onselectstart = function() {
// 防止拖拽對象內(nèi)文字被選中
return false;
}
dom.on(ele, "mousedown", mouseDown);
}
dom.on(draggableConfig.parent, "mousemove", function(e) {
if(draggableConfig.draggingObj) {
var mouse = draggableConfig.mouse,
draggingObj = draggableConfig.draggingObj;
// 這里可以添加限制驻龟,拖拽不脫離父元素
var p_height = draggableConfig.parent.offsetHeight;
var p_width = draggableConfig.parent.offsetWidth;
var c_height = draggableConfig.dragEle.offsetHeight;
var c_width = draggableConfig.dragEle.offsetWidth;
var left, top = 0;
if(event.clientX - mouse.x + draggingObj.x < 0) {
left = 0;
}
else if((event.clientX - mouse.x + draggingObj.x) > (p_width - c_width)) {
left = p_width - c_width;
}
else {
left = event.clientX - mouse.x + draggingObj.x;
}
if(event.clientY - mouse.y + draggingObj.y < 0) {
top = 0;
}
else if((event.clientY - mouse.y + draggingObj.y) > (p_height - c_height)) {
top = p_height - c_height;
}
else {
top = event.clientY - mouse.y + draggingObj.y;
}
draggingObj.setEleCss({
"left": parseInt(left) + "px",
"top": parseInt(top) + "px"
})
}
})
dom.on(draggableConfig.parent, "mouseup", function(event) {
draggableConfig.draggingObj = null;
})
window.Drag = Drag;
})(window,undefined);
調(diào)用方法:Drag(document.getElementById("obj"));
注意點:為了防止選中拖拽元素中的文字,通過onselectstart事件處理程序return false來處理這個問題闯冷∩咭或者css3中的user-select:none;
srcElement是IE下的屬性,target是Firefox下的屬性荞怒,Chrome瀏覽器同時有這兩個屬性衰抑。
當然這個也是可以按照需要進行定制化擴展等呛踊。
擴展:有效拖拽##
有時谭网,會遇到一些拖拽需求愉择,只能拖拽需要拖拽模塊的部分區(qū)域衷戈,這時怎樣實現(xiàn)呢殖妇?
思路:首先優(yōu)化拖拽元素對象如下,增加一個目標元素target座每,表示被拖拽對象嫡纠,在上圖的登錄框中除盏,就是整個登錄窗口者蠕。
被記錄和設(shè)置坐標的拖拽元素就是這個目標元素,但是它并不是整個部分都是拖拽的有效部分大磺。我們在html結(jié)構(gòu)中為拖拽的有效區(qū)域添加類draggable表示有效拖拽區(qū)域:
<div id="drag">
<h3 class='drag-title'>拖拽主體</h3>
<div class="drag-body">
內(nèi)容主體
</div>
</div>
js部分:
function Drag(ele,parent) {
this.ele = ele;
var drag = ele.getElementsByClassName('drag-title')[0] || ele
function mouseDown(event) {
var elem = this ;
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": "absolute"
});
}
draggableConfig.parent = parent;
draggableConfig.dragEle = ele;
ele.onselectstart = function() {
// 防止拖拽對象內(nèi)文字被選中
return false;
}
dom.on(drag, "mousedown", mouseDown);
}
性能優(yōu)化##
由于onmousemove在一直調(diào)用杠愧,會造成一些性能問題锐锣,我們可以通過setTimout來延遲綁定onmousemove事件雕憔,改進move函數(shù)如下:
setTimeout(function () {
dom.on(document, "mousemove", function(e){
});
}, 25);
接下來的拖拽(二)將會去學習下H5中拖拽分瘦,敬請下回分解哈,O(∩_∩)O哈哈~