之前用原生js中的H5中的api:gragstart,graggover,drop写的拖拽发现不能微调位置,于是改为用原生js来写拖拽效果。
我们一共会用到三个事件:mosedown,mosemove以及moseup。
第一种理解方式:(更易于理解)
拖拽:
1. 鼠标摁下可以开始拖拽
2. 记录摁下时的鼠标位置 和 元素位置
3. 移动后获取到鼠标的新位置
4. 用鼠标新位置 - 摁下时的鼠标位置 = 鼠标移动距离
5. 元素当前位置 = 鼠标移动距离 + 摁下时元素位置
实现方式:
class Drag {
// 构造函数
constructor(el){
this.el = el;
// 鼠标摁下时的元素的位置
this.startOffset = {};
// 鼠标摁下时鼠标的坐标
this.startPoint = {};
let move = (e)=>{
this.move(e);
};
let end = (ev)=>{
ev.preventDefault();
document.removeEventListener("mousemove",move);
document.removeEventListener("mouseup",end);
};
el.addEventListener("mousedown",(e)=>{
this.start(e);
document.addEventListener("mousemove",move);
document.addEventListener("mouseup",end);
});
}
// 摁下时的处理函数
start(e){
let {el} = this;
this.startOffset = {
x: el.offsetLeft,
y: el.offsetTop
};
this.startPoint = {
x: e.clientX,
y:e.clientY
};
}
//移动时的处理函数
move(e){
e.preventDefault();
let {el,startOffset,startPoint} = this;
let nowPoint = {
x: e.clientX,
y: e.clientY
};
let dis = {
x: nowPoint.x - startPoint.x,
y: nowPoint.y - startPoint.y
}
el.style.left = dis.x + startOffset.x + "px";
el.style.top = dis.y + startOffset.y + "px";
}
}
//调用
(function(){
let box = document.querySelector("#box");
let dragBox = new Drag(box);
})()
第二种理解方式:
原理:在目标元素上按下鼠标(mousedown事件触发),拖拽(mousemove事件)过程中实时改变目标元素的位置,鼠标松开后(mouseup事件)元素停留在最后位置,并向后台传递最后位置信息。为了记住是在拖拽中,所以我们需要一个flag(isOnTarget)来判断是否处于拖拽状态,在按下鼠标时为true,当松开时为false,移动过程中判断为true时移动目标元素,否则不移动。
拖拽过程中的最重要的就是随着鼠标移动改变元素位置,那么这个移动位置怎么更新就有点讲究了:
如上图,外层红框代表我们的文档,content-area是我们定义position:relative的拖拽元素所在的盒子,drag-item就是我们拖动的目标元素,mouse-point是我们鼠标点击下后的点所在位置。
我们每次的鼠标mousemove需要得到的是06号线段的距离,即目标元素(grag-item)相对于父元素(content-area)所在的位置。
当我们鼠标移动的时候可以实时获取的位置是event.pageX,event.pageY。而这两个数据是相对于document的距离。还可以得到dragItem的初始位置offsetLeft。
所以我们可以使用 06号 = event.pageX - 02号 - 01号=>06号 = event.pageX - (02号+01号)。
02号 + 01号这个距离在鼠标移动过程中始终是固定的,所以我们只要得到这个值那么我们就可以根据pageX的变值减去这个固定值得到实时位置。02号+01号 = 初始位置的pageX - 初始位置的offsetLeft。
所以在鼠标按下的时候我们便可以设定这个值为disX:disX = event.pageX - dragItem.offsetLeft。
在鼠标移动过程中的最新left = event.pageX - disX。
限定边界:左边界就是 left<0时设置left = 0。有边界则是:left+dragItem.offsetWidth<contentArea.offsetWidth
所有top和上下边界同理。
最后可能有文字选中的问题,则使用在mousedown时使用return false;来解决。关于ie低版本用setCapture什么的算了吧,抛弃ie从我做起。
最后总结:最关键的就是这个disX = event.pageX - dragItem.offsetLeft。知道了这个值时固定不变的就行。
其实在最终的代码实现计算left,top上第二种和第一种的本质是一样的,只是理解和记忆的角度不一样
最后放上代码:
moveItem();
function moveItem(){
let dragged = null;//拖拽元素
let isOnTarget = false;//拖拽状态
let left = 0,top=0,disX=0,disY=0;
//拖拽元素的父元素尺寸,用于边界限定
let boxWidth = document.querySelector('.content-area').offsetWidth;
let boxHeight = document.querySelector('.content-area').offsetHeight;
//鼠标按下
document.addEventListener('mousedown',function(event){
let target = event.target;
//是否是目标拖拽元素,不是则不做任何事情
if(!target.classList.contains('dragItem')){
return false;
}
//设置拖拽状态
isOnTarget = true;
dragged = event.target;
//获取固定值,用于后面的计算left,top值
disX = event.pageX - dragged.offsetLeft;
disY = event.pageY - dragged.offsetTop;
return false;
})
//鼠标移动过程中
document.addEventListener('mousemove',function(event){
//如果不是拖拽中则不做任何事情
if(!isOnTarget) return false;
//计算位置
left = event.pageX - disX;
top = event.pageY - disY;
if(left<10){//左边界限定
left = 10;
}else if(left+dragged.offsetWidth+10>boxWidth){//有边界限定
left = boxWidth-dragged.offsetWidth-10;
}
if(top<16){//上边界限定
top = 16;
}else if(top+dragged.offsetHeight+10>boxHeight){//下边界限定
top= boxHeight-dragged.offsetHeight-10;
}
//设置位置
dragged.style.left = left + 'px';
dragged.style.top = top + 'px';
})
//鼠标抬起
document.addEventListener('mouseup',function(event){
//由于我们要向后端发送数据,所以这里要判断是否是在目标元素上的点击
let target = event.target;
if(!target.classList.contains('dragItem')){
return false;
}
//拖拽完成,更新拖拽状态
isOnTarget = false;
//发送数据
updateGrid(scope,nodeData.id,left,top);
用于解决选中文字的问题
return false;
})
}
为了和第一种方式实现方式保持一致,这里写了个简单的es6类实现的方式,场景较简单,即body内移动
class Drags{
constructor(el){
this.el = el;
this.isDrag = false;
this.dis = {};
this.el.addEventListener('mousedown',this.start);
document.addEventListener('mousemove',this.move);
document.addEventListener('mouseup',this.end);
}
start=(ev)=>{
let {el} = this;
this.isDrag = true;
//这个disx每次都是一个固定值,包括拖动元素父元素的左、上边距离以及鼠标距离拖动元素的左、 // 上边的距离
this.dis = {
x:ev.pageX - el.offsetLeft,
y:ev.pageY - el.offsetTop
}
console.log(this.dis);
}
move=(ev)=>{
ev.preventDefault();
if(!this.isDrag) return false;
let {el} = this;
let left = ev.pageX - this.dis.x;
let top = ev.pageY - this.dis.y;
//边界
left= left <= 0 ? 0 : left;
left = left+el.offsetWidth>window.innerWidth ? window.innerWidth-el.offsetWidth : left;
top = top<=0 ? 0 :top;
top = top+el.offsetHeight>window.innerHeight ? window.innerHeight - el.offsetHeight : top;
el.style.left = left + 'px';
el.style.top = top + 'px';
}
end=()=>{
if(!this.isDrag) return false;
this.isDrag = false;
}
}
(function(){
let box = document.querySelector("#box");
let dragBox = new Drags(box);
})()