原生js实现拖拽效果,js拖拽原理

之前用原生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);
})()   

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值