js实现拖拽|JS拖拽插件开发详细教程

更新时间:2019-12-10    来源:js教程    手机版     字体:

【www.bbyears.com--js教程】

一、js拖拽插件的原理

常见的拖拽操作是什么样的呢?整过过程大概有下面几个步骤:

  1、用鼠标点击被拖拽的元素

  2、按住鼠标不放,移动鼠标

  3、拖拽元素到一定位置,放开鼠标

这里的过程涉及到三个dom事件:onmousedown,onmousemove,onmouseup。所以拖拽的基本思路就是:

  1、用鼠标点击被拖拽的元素触发onmousedown

    (1)设置当前元素的可拖拽为true,表示可以拖拽

    (2)记录当前鼠标的坐标x,y

    (3)记录当前元素的坐标x,y

  2、移动鼠标触发onmousemove

    (1)判断元素是否可拖拽,如果是则进入步骤2,否则直接返回

    (2)如果元素可拖拽,则设置元素的坐标

      元素的x坐标 = 鼠标移动的横向距离+元素本来的x坐标 = 鼠标现在的x坐标 - 鼠标之前的x坐标 + 元素本来的x坐标

      元素的y坐标 = 鼠标移动的横向距离+元素本来的y坐标 = 鼠标现在的y坐标 - 鼠标之前的y坐标 + 元素本来的y坐标

  3、放开鼠标触发onmouseup

    (1)将鼠标的可拖拽状态设置成false

二、根据原理实现的最基本效果

在实现基本的效果之前,有几点需要说明的:

  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;
}


三、代码抽象与优化

上面的代码要做成插件,要将其抽象出来,基本结构如下:

; (function (window, undefined) {            
    function Drag(ele) {}
    window.Drag = Drag;
})(window, undefined);


用自执行匿名函数将代码包起来,内部定义Drag方法并暴露到全局中,直接调用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;
        },
        //获取设置元素的样式
        setCss: function (node, css) {
            for (var key in css) {
                node.style[key] = css[key];
            }
        }
    };
  
    window.Drag = Drag;
})(window, undefined);


在一个拖拽操作中,存在着两个对象:被拖拽的对象和鼠标对象,我们定义了下面的两个对象以及它们对应的操作:

首先的拖拽对象,它包含一个元素节点和拖拽之前的坐标x和y:

function DragElement(node) {
    this.node = node;//被拖拽的元素节点
    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);
    },
    //设置当前的坐标
    setXY: function (x, y) {
        this.x = parseInt(x) || 0;
        this.y = parseInt(y) || 0;
        return this;
    },
    //设置元素节点的样式
    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);
}



这是在拖拽操作中定义的两个对象。

 

如果一个页面可以有多个拖拽元素,那应该注意什么:

1、每个元素对应一个拖拽对象实例

2、每个页面只能有一个正在拖拽中的元素

为此,我们定义了唯一一个对象用来保存相关的配置:

var draggableConfig = {
    zIndex: 1,
    draggingObj: null,
    mouse: new Mouse()
};


这个对象中有三个属性:

(1)zIndex:用来赋值给拖拽对象的zIndex属性,有多个拖拽对象时,当两个拖拽对象重叠时,会造成当前拖拽对象有可能被挡住,通过设置zIndex使其显示在最顶层

(2)draggingObj:用来保存正在拖拽的对象,在这里去掉了前面的用来判断是否可拖拽的变量,通过draggingObj来判断当前是否可以拖拽以及获取相应的拖拽对象

(3)mouse:唯一的鼠标对象,用来保存当前鼠标的坐标等信息

 

最后是绑定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;
                },
                //获取设置元素的样式
                setCss: function (node, css) {
                    for (var key in css) {
                        node.style[key] = css[key];
                    }
                }
            };
            //#region 拖拽元素类
            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);
                },
                setXY: function (x, y) {
                    this.x = parseInt(x) || 0;
                    this.y = parseInt(y) || 0;
                    return this;
                },
                setEleCss: function (css) {
                    dom.setCss(this.node, css);
                    return this;
                }
            }
            //#endregion
            //#region 鼠标元素
            function Mouse() {
                this.x = 0;
                this.y = 0;
            }
            Mouse.prototype.setXY = function (x, y) {
                this.x = parseInt(x);
                this.y = parseInt(y);
            }
            //#endregion
            //拖拽配置
            var draggableConfig = {
                zIndex: 1,
                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 () {
                    //防止拖拽对象内的文字被选中
                    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);


调用方法:Drag(document.getElementById("obj"));

注意的一点,为了防止选中拖拽元素中的文字,通过onselectstart事件处理程序return false来处理这个问题。

四、扩展:有效的拖拽元素

我们常见的一些拖拽效果很有可能是这样的:

弹框的顶部是可以进行拖拽操作的,内容区域是不可拖拽的,怎么实现这样的效果呢:

首先优化拖拽元素对象如下,增加一个目标元素target,表示被拖拽对象,在上图的登录框中,就是整个登录窗口。

被记录和设置坐标的拖拽元素就是这个目标元素,但是它并不是整个部分都是拖拽的有效部分。我们在html结构中为拖拽的有效区域添加类draggable表示有效拖拽区域:

    
        拖拽的有效元素
    
    
        拖拽对象1
    


然后修改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的节点变成了包含draggable类的有效元素,如果不含有draggable,则整个元素都是有效元素。

五、性能优化和总结

由于onmousemove在一直调用,会造成一些性能问题,我们可以通过setTimout来延迟绑定onmousemove事件,改进move函数如下

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);
        }, 25);
    }
}


总结:

整个拖拽插件的实现其实很简单,主要是要注意几点

  1、实现思路:元素拖拽位置的改变就等于鼠标改变的距离,关键在于获取鼠标的变动和元素原本的坐标

      2、通过setTimeout来延迟加载onmousemove事件来提供性能

六、jquery插件化

简单地将其封装成jquery插件,主要是相关的dom方法替换成jquery方法来操作

; (function ($, window, undefined) {
    //#region 拖拽元素类
    function DragElement(node) {
        this.target = node;
        node.onselectstart = function () {
            //防止拖拽对象内的文字被选中
            return false;
        }
    }
    DragElement.prototype = {
        constructor: DragElement,
        setXY: function (x, y) {
            this.x = parseInt(x) || 0;
            this.y = parseInt(y) || 0;
            return this;
        },
        setTargetCss: function (css) {
            $(this.target).css(css);
            return this;
        }
    }
    //#endregion
    //#region 鼠标元素
    function Mouse() {
        this.x = 0;
        this.y = 0;
    }
    Mouse.prototype.setXY = function (x, y) {
        this.x = parseInt(x);
        this.y = parseInt(y);
    }
    //#endregion
    //拖拽配置
    var draggableConfig = {
        zIndex: 1,
        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 > 0 ? $dragNode : $ele;
        
        $dragNode.on({
            "mousedown": function (event) {
                var dragElement = draggableConfig.dragElement = new DragElement($ele.get(0));
                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);
            }, 25);
        }
    }
    $document.on({
        "mousemove": move,
        "mouseup": function () {
            draggableConfig.dragElement = null;
        }
    });
    $.fn.drag = function (options) {
        drag(this);
    }
})(jQuery, window, undefined)



JQuery拖拽插件


一直以来,都对JS获取元素的位置感到非常的困惑:一会client、一会offset、一会scroll。
再加上各大浏览器之间的不兼容,唉,搞得哥晕晕乎乎的。
而很多页面效果都要用到这些位置。不得已,得练练,得记记。

下面就来说说这个基于 JQuery的简易拖拽插件吧。   

按惯例,先说说拖拽的原理,以及搞这么一个东东的步骤:
那什么是拖拽呢? 看名字就知道了:就是把一个东东拖来拽去的。 放到我们的DOM上,就是改变它的位置。
它只有两个难点:1、如何知道是在拖? 2、如何知道从哪拖,拖到哪?
其实,这也算不上难点,毕竟两者都是基础的东西,关键在于熟练。

换到js 中,我们搞一个拖拽效果,大致有如下步骤:

1、让元素捕获事件(一般情况下,无非就是mousedown、mousemove、mouseup)
2、在mousedown时,标记开始拖拽,并获取元素及鼠标的位置。
3、在mousemove时,不断的获取鼠标的新位置,并通过相应的位置算法,来重新定位元素位置。
4、在mouseup时,结束拖拽。。。然后周而复始。

这中间,个需要注意的地方:被拖拽的元素,至少需要相对或绝对定位,否则拖拽不会有效果。

OK,不多说,无代码,无真相。相应的解释都在其中了:






Jeremy  -  DragDrop Test !
words" content="Javascript自由拖拽类" />
<script type="text/javascript" src="jquery-1.4.2.min.js"></script>
<script type="text/javascript">
(function($)
{
    $.extend({
        //获取鼠标当前坐标
            mouseCoords:function(ev){
        if(ev.pageX || ev.pageY){
            return {x:ev.pageX, y:ev.pageY};}
        return {
            x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
            y:ev.clientY + document.body.scrollTop  - document.body.clientTop
        };
        },
        //获取样式值
            getStyle:function(obj,styleName)
        {
        return obj.currentStyle ? obj.currentStyle[styleName] : document.defaultView.getComputedStyle(obj,null)[styleName];
//                return obj.currentStyle ? obj.currentStyle[styleName] : document.defaultView.getComputedStyle(obj,null).getPropertyValue(styleName);
            }
       });  
    // 元素拖拽插件
        $.fn.dragDrop = function(options)
    {
        var opts = $.extend({},$.fn.dragDrop.defaults,options);
        return this.each(function(){
            //是否正在拖动
                    var bDraging = false;   
            //移动的元素
                    var moveEle = $(this);
            //点击哪个元素,以触发移动。
                    //该元素需要是被移动元素的子元素(比如标题等)
                    var focuEle = opts.focuEle ? $(opts.focuEle,moveEle) : moveEle ;
            if(!focuEle || focuEle.length<=0)
            {
                alert('focuEle is not found! the element must be a child of '+this.id);
                return false;
            }                
            // initDiffX|Y : 初始时,鼠标与被移动元素原点的距离
                    // moveX|Y : 移动时,被移动元素定位位置 (新鼠标位置与initDiffX|Y的差值)
                    // 如果定义了移动中的回调函数,该对象将以参数传入回调函数。
                    var dragParams = {initDiffX:'',initDiffY:'',moveX:'',moveY:''}; 
            //被移动元素,需要设置定位样式,否则拖拽效果将无效。
                    moveEle.css({'position':'absolute','left':'0','top':'0'});
            //点击时,记录鼠标位置
                    //DOM写法: getElementById('***').onmousedown= function(event);
                    focuEle.bind('mousedown',function(e){                
                //标记开始移动
                        bDraging = true;
                    //改变鼠标形状
                        moveEle.css({'cursor':'move'});
                    //捕获事件。(该用法,还有个好处,就是防止移动太快导致鼠标跑出被移动元素之外)
                        if(moveEle.get(0).setCapture)
                {  
                moveEle.get(0).setCapture();  
                 }     
                //(实际上是鼠标当前位置相对于被移动元素原点的距离)
                            // DOM写法:(ev.clientX + document.body.scrollLeft - document.body.clientLeft) - document.getElementById('***').style.left;
                            dragParams.initDiffX = $.mouseCoords(e).x - moveEle.position().left;
                dragParams.initDiffY = $.mouseCoords(e).y - moveEle.position().top;
             });
            //移动过程
                    focuEle.bind('mousemove',function(e){
                if(bDraging)
                {    
                    //被移动元素的新位置,实际上鼠标当前位置与原位置之差
                                //实际上,被移动元素的新位置,也可以直接是鼠标位置,这也能体现拖拽,但是元素的位置就不会精确。
                                dragParams.moveX = $.mouseCoords(e).x - dragParams.initDiffX;
                    dragParams.moveY = $.mouseCoords(e).y - dragParams.initDiffY;
                    //是否限定在某个区域中移动.
                                //fixarea格式: [x轴最小值,x轴最大值,y轴最小值,y轴最大值]
                                if(opts.fixarea)
                    {    
                        if(dragParams.moveXopts.fixarea[1])
                        {
                            dragParams.moveX=opts.fixarea[1]
                        }
                        if(dragParams.moveYopts.fixarea[3])
                        {
                            dragParams.moveY=opts.fixarea[3]
                        }    
                    }
                    //移动方向:可以是不限定、垂直、水平。
                                if(opts.dragDirection=='all')
                    {
                        //DOM写法: document.getElementById('***').style.left = '***px'; 
                                        moveEle.css({'left':dragParams.moveX,'top':dragParams.moveY});
                    }
                    else if (opts.dragDirection=='vertical')
                    {
                        moveEle.css({'top':dragParams.moveY});
                    }
                    else if(opts.dragDirection=='horizontal')
                    {
                        moveEle.css({'left':dragParams.moveX});
                    }
                    //如果有回调
                                if(opts.callback)
                    {
                        //将dragParams作为参数传递
                                        opts.callback.call(opts.callback,dragParams);
                    }
                }
             });
            //鼠标弹起时,标记为取消移动
                        focuEle.bind('mouseup',function(e){
                bDraging=false;
                moveEle.css({'cursor':'default'});
                if(moveEle.get(0).releaseCapture)
                {
                    moveEle.get(0).releaseCapture();
                }
            });
        });
    };
    //默认配置
        $.fn.dragDrop.defaults = 
    {
        focuEle:null,            //点击哪个元素开始拖动,可为空。不为空时,需要为被拖动元素的子元素。
            callback:null,            //拖动时触发的回调。
                dragDirection:'all',    //拖动方向:['all','vertical','horizontal']
                fixarea:null            //限制在哪个区域拖动,以数组形式提供[minX,maxX,minY,maxY]
        };
})(jQuery);   
// test 
   $(function(){
//限定区域,有回调函数。
        $('#dragDiv').dragDrop({fixarea:[0,$('#dragContainer').width()-50,0,$('#dragContainer').height()-50],callback:function(params){
    $('#span1').text('X:'+params.moveX+' Y:'+params.moveY);
}});
//默认设置
        $('#dragDiv1').dragDrop();
});
</script>









   心静似高山流水不动,心清若巫峰雾气不沾。


本文来源:http://www.bbyears.com/wangyezhizuo/82705.html

热门标签

更多>>

本类排行