原生JavaScript与jQuery(绝对、相对定位)实现拖拽效果

今天给大家谈谈这个拖拽功能的实现
一来是想复习一下这个小知识,二来是看到网上都是absolute绝对定位实现的,感觉有一定局限性
所以自己写了一下用relative相对定位来实现的拖拽效果

原生js绝对定位实现拖拽

首先我们来思考一下拖拽功能用到的事件
拖拽无非是鼠标按下点击物体(DOM节点)
鼠标移动,物体移动
鼠标抬起,物体停止拖拽
所以这里我们需要绑定三个事件
mousedown、mousemove、mouseup
不过通过刚才我们所思考的
mousemove和mouseup事件触发应该是由一个鼠标按下的前提
我们可以利用一个布尔变量储存状态表示当前是否鼠标按下
或者干脆在mousedown事件处理函数中去绑定mousemove、mouseup
那么现在我们大脑里有了这样一个实现拖拽功能函数的蓝图
(主要是逻辑,绑定事件等等兼容问题我就不具体实现了b( ̄▽ ̄)d)

//伪代码
function drag(ele){
    ele.onmousedown = function(){
        ...
        document.onmousemove = function(){
            ...
        }
        document.onmouseup = function(){
            ...
        }
    }
}

既然要让DOM节点被拖拽,当然不能少了作为参数的元素
下面我们来仔细分析一下拖拽的事件:

既然是绝对定位实现,那我们就加一个容错机制
让鼠标按下的时候就为它添加css样式绝对定位

我们在拖拽的过程中实际上就是改变元素的left和top
可是在拖拽过程中怎样知道它的left和top值应该是什么呢?
拖拽中,有一个量是不变的,那就是光标相对DOM节点左上角的坐标是不变的
而鼠标移动时我们可以获取到的是光标相对于视窗的坐标
也就是说
DOM节点相对于视窗坐标 = 光标相对于视窗坐标 - 光标相对于DOM节点坐标
这样逆向思考思路就清晰多了
不知道我这样描述大家能不能看懂
所以我们要在mousedown中处理的就是获取那个不变的量——光标相对于DOM节点的坐标
于是每次移动都根据公式获取DOM节点的相对于视窗的坐标(left与top值)
鼠标抬起清理事件
理清了这些,我们来封装这个函数

//待改进
function drag(ele){
    var posX, posY;
    ele.onmousedown = function(e){
        this.style.position = 'absolute';//元素绝对定位
        //获取光标相对于DOM节点的坐标
        posX = e.clientX - parseInt(window.getComputedStyle(this,false)['left']);
        posY = e.clientY - parseInt(window.getComputedStyle(this,false)['top']);
        ele.onmousemove = function(e){
            //改变DOM节点相对于视窗坐标
            this.style.left = e.clientX - posX + 'px';//加上像素不要忘了
            this.style.top = e.clientY - posY + 'px';
        }
        ele.onmouseup = function(){
            this.onmousemove = null;//鼠标抬起就应该解除事件
            this.onmouseup = null;
        }
    }
}

这里多说一句,其实posX = e.offsetX; posY = e.offfsetY; 和上面的那一长串是一样的
这是IE的非标准方法,虽然我现在使用的无比强大的chrome也好使……
然后我们在来使用一下我们刚刚封装的函数

<div class="demo"></div>
.demo { width: 100px; height: 100px; background-color: orangered; border-radius: 50%; }
var demo = document.getElementsByTagName('div');
drag(demo[0]);

很快我们发现了问题:当鼠标移动很快时,元素就不会跟随鼠标移动了
为什么会有这样的情况发生呢?
问题就出在了我们把鼠标移动的事件绑定到了元素身上
如果我们让光标移动的很快而移到了元素有效边界之外
那么mousemove的事件就不能触发了,也就不能修改它的left和top值了
正确的做法应该是这样的,把mousemove和mouseup绑定到document

function drag(ele){
    var posX, posY;
    ele.onmousedown = function(e){
        this.style.position = 'absolute';
        posX = e.clientX - parseInt(window.getComputedStyle(this,false)['left']);
        posY = e.clientY - parseInt(window.getComputedStyle(this,false)['top']);
        document.onmousemove = function(e){//绑定到document
            ele.style.left = e.clientX - posX + 'px';//在这里this就是document了,所以要用ele
            ele.style.top = e.clientY - posY + 'px';
        }
        document.onmouseup = function(){//绑定到document
            this.onmousemove = null;
            this.onmouseup = null;
        }
    }
}

这样无论我们拖拽的有多么的丧心病狂,都不会有问题了
(上面的绑定事件、解除事件、获取样式低版本IE都有兼容问题,以后我会总结的,这里就不予考虑了)

原生js相对定位实现拖拽

为什么我说绝对定位有一定局限性呢?
因为我感觉它有局限性 []~( ̄▽ ̄)~*
开个玩笑我举个例子

<div class="demo"></div>
<div class="demo"></div>
<div class="demo"></div>
.demo { width: 100px; height: 100px; background-color: orangered; border-radius: 50%; }

var demo = document.getElementsByTagName('div');
drag(demo[0]);
drag(demo[1]);
drag(demo[2]);

利用我们刚刚封装的绝对定位拖拽函数
我鼠标按下第一个球(DOM节点)的一瞬间
第二个球和第三个球跑到了上面
原生JavaScript与jQuery(绝对、相对定位)实现拖拽效果_第1张图片
这是因为点击节点的一瞬间,它多了样式position:absolute
绝对定位会使节点脱离当前的文本流
于是没有经过拖拽的节点2和节点3就看不到节点1了
(浏览器发生了reflow重排)

不仅仅这这样,如果这个元素原来是float元素,absolute定位后float也会失效
同样会产生这样的问题
那么怎样解决这种问题呢?
最开始我的想法是创建一个一模一样的透明节点占位
光是想一想都觉得很麻烦,(然而我不想麻烦)
反过来想一想relative或许更合适
相对定位不同于绝对定位的一点就是它不会脱离正常的文本流
而且它的left和top是相对于自己原来位置的,而不是相对于离自己最近的定位父级元素
有了这些想法,我们来尝试重构函数

function drag(ele){
    var oldX, oldY, newX, newY;
    ele.onmousedown = function(e){
        this.style.position = 'relative';//元素相对定位
        if(!this.style.left && !this.style.top){//第一次设置left、top为0
            this.style.left = 0;
            this.style.top = 0;
        }
        oldX = e.clientX;//记录初始光标相对于视窗坐标
        oldY = e.clientY;
        document.onmousemove = function(e){
            newX = e.clientX;//获取当前新光标相对于视窗坐标
            newY = e.clientY;
            ele.style.left = parseInt(ele.style.left) + newX - oldX + 'px';//更新
            ele.style.top = parseInt(ele.style.top) + newY - oldY + 'px';
            oldX = newX;//新坐标变为老坐标
            oldY = newY;
        }
        document.onmouseup = function(){
            document.onmousemove = null;
            document.onmouseup = null;
        }
    }
}

这段代码我就不细说了,也很容易看懂
如果你还想写的再丰满一些,可以添加z-index属性

jQuery绝对定位实现拖拽

上面解释了很多,这里我就不那么啰嗦了
直接上代码,道理是一样的

$.fn.extend({
    drag: function(){
        this.on('mousedown',function(e){
            $(this).css('position','absolute');
            var disX = e.clientX - $(this).position().left,
                disY = e.clientY - $(this).position().top,
                $self = $(this);
            $(document).on('mousemove',function(e){
                $self.css('left',e.clientX - disX);
                $self.css('top',e.clientY - disY);
            })
            $(document).on('mouseup',function(){
                $(document).off();
            })
        });
    }
});
$('.demo').drag();

还记得我 jQuery框架常用的性能优化里提到的扩展插件么
这里我利用了$.fn.extend()封装了对象方法的插件
提到这里多说一嘴,这里drag方法内部的this是一个jQuery对象,而不是元素,与on()绑定的事件触发函数内部不同,这里要特别注意
但是同样会产生上面所说的浏览器reflow重排的灾难问题

jQuery相对定位实现拖拽

$.fn.extend({
    drag: function(){
        var oldX, oldY, newX, newY;
        this.on('mousedown',function(e){
            $(this).css('position','relative');
            oldX = e.clientX;
            oldY = e.clientY;
            var $self = $(this);
            $(document).on('mousemove',function(e){
                newX = e.clientX,
                newY = e.clientY;
                $self.css('left','+=' + (newX - oldX));
                $self.css('top','+=' + (newY - oldY));
                oldX = newX;
                oldY = newY;
            })
            $(document).on('mouseup',function(){
                $(document).off();
            })
        });
    }
});
$('.demo').drag();

我也同样利用jQuery的相对定位实现了拖拽
话说jQuery这个off()方法倒是很方便,不填参数把document所有事件都解除了

以上就是我通过原生js还有jQuery分别用绝对定位和相对定位实现拖拽效果的方法
虽然不知道网上为什么没有相对定位的实现
或许它并不常用
不过通过这个小问题练习一下逻辑思维还是不错的
在此分享给大家..(呃码字好累)

==主页传送门==

你可能感兴趣的:(JavaScript,jquery,dom,移动,鼠标)