没有使用jQuery UI实现可拖动的div

64

我正在尝试使一个 div 具有可拖动性,但不使用 jQuery UI。

然而,我被下面的代码困住了。我知道应该使用鼠标相对于容器 div 的位置(在其中 div 将被拖动),并且我应该设置 div 的偏移量相对于这些值。

我只是想不出如何做到这一点。有什么提示吗?

这是当然不起作用的代码:

var X, Y;

$(this).mousedown(function() {
    $(this).offset({ 
        left: X, 
        top: Y
    });
});

$("#containerDiv").mousemove(function(event) {
    X = event.pageX;
    Y = event.pageY;
});
12个回答

75

这是一个非常简单的例子,可能会让你入门:

$(document).ready(function() {
    var $dragging = null;

    $(document.body).on("mousemove", function(e) {
        if ($dragging) {
            $dragging.offset({
                top: e.pageY,
                left: e.pageX
            });
        }
    });


    $(document.body).on("mousedown", "div", function (e) {
        $dragging = $(e.target);
    });

    $(document.body).on("mouseup", function (e) {
        $dragging = null;
    });
});

示例:http://jsfiddle.net/Jge9z/

我理解应该使用鼠标位置相对于容器 div(在其中拖动 div)并设置 div 的偏移量相对于那些值。

我不太确定。 在拖放中,似乎您总是希望使用元素相对于文档的偏移量。

如果您的意思是要将拖动限制在特定区域内,则这是一个更复杂的问题(但仍然可行)。


工作得很好,但在移动设备上拖动不起作用。如何修改此代码以使其在移动设备上也能正常工作? - Armin
不确定。你可能想要了解一下触摸事件 - Andrew Whitaker
2
我建议通过添加 mouseleave 事件来扩展代码,例如 $("body").mouseleave(function(e) { $dragging = null; }),以便在浏览器窗口外释放按下的按钮。 - rabudde
2
还有一个可以使用的拖放HTML5 API - Adam Szmyd
它能工作,但是在position fixed和absolute方面有一些小问题。 - khalid J-A

43

这是另一个更新后的代码:

$(document).ready(function() {
    var $dragging = null;
    $('body').on("mousedown", "div", function(e) {
        $(this).attr('unselectable', 'on').addClass('draggable');
        var el_w = $('.draggable').outerWidth(),
            el_h = $('.draggable').outerHeight();
        $('body').on("mousemove", function(e) {
            if ($dragging) {
                $dragging.offset({
                    top: e.pageY - el_h / 2,
                    left: e.pageX - el_w / 2
                });
            }
        });
        $dragging = $(e.target);
    }).on("mouseup", ".draggable", function(e) {
        $dragging = null;
        $(this).removeAttr('unselectable').removeClass('draggable');
    });
});​

演示:http://jsfiddle.net/tovic/Jge9z/31/


我为这个主题创建了一个简单的插件。

// Simple JQuery Draggable Plugin
// https://plus.google.com/108949996304093815163/about
// Usage: $(selector).drags();
// Options:
// handle            => your dragging handle.
//                      If not defined, then the whole body of the
//                      selected element will be draggable
// cursor            => define your draggable element cursor type
// draggableClass    => define the draggable class
// activeHandleClass => define the active handle class
//
// Update: 26 February 2013
// 1. Move the `z-index` manipulation from the plugin to CSS declaration
// 2. Fix the laggy effect, because at the first time I made this plugin,
//    I just use the `draggable` class that's added to the element
//    when the element is clicked to select the current draggable element. (Sorry about my bad English!)
// 3. Move the `draggable` and `active-handle` class as a part of the plugin option
// Next update?? NEVER!!! Should create a similar plugin that is not called `simple`!

(function($) {
    $.fn.drags = function(opt) {

        opt = $.extend({
            handle: "",
            cursor: "move",
            draggableClass: "draggable",
            activeHandleClass: "active-handle"
        }, opt);

        var $selected = null;
        var $elements = (opt.handle === "") ? this : this.find(opt.handle);

        $elements.css('cursor', opt.cursor).on("mousedown", function(e) {
            if(opt.handle === "") {
                $selected = $(this);
                $selected.addClass(opt.draggableClass);
            } else {
                $selected = $(this).parent();
                $selected.addClass(opt.draggableClass).find(opt.handle).addClass(opt.activeHandleClass);
            }
            var drg_h = $selected.outerHeight(),
                drg_w = $selected.outerWidth(),
                pos_y = $selected.offset().top + drg_h - e.pageY,
                pos_x = $selected.offset().left + drg_w - e.pageX;
            $(document).on("mousemove", function(e) {
                $selected.offset({
                    top: e.pageY + pos_y - drg_h,
                    left: e.pageX + pos_x - drg_w
                });
            }).on("mouseup", function() {
                $(this).off("mousemove"); // Unbind events from document
                if ($selected !== null) {
                    $selected.removeClass(opt.draggableClass);
                    $selected = null;
                }
            });
            e.preventDefault(); // disable selection
        }).on("mouseup", function() {
            if(opt.handle === "") {
                $selected.removeClass(opt.draggableClass);
            } else {
                $selected.removeClass(opt.draggableClass)
                    .find(opt.handle).removeClass(opt.activeHandleClass);
            }
            $selected = null;
        });

        return this;

    };
})(jQuery);

演示: http://tovic.github.io/dte-project/jquery-draggable/index.html


2
如果您使用代码 $selected = $(this).parent(); 的第 38:59 行,您将强制开发人员使用固定的 HTML 结构。您假设父元素是要移动的完整“结构”,但如果您分配一个类似于 class='draggableContainer' 的类,那么您可以执行类似 $selected = $(this).closest(".draggableContainer"); 的操作,这样您就可以使用任何 HTML 结构并且它会正常工作。总的来说, $hit 很酷,工作得非常好。 - ncubica
此外,如果您正在使用这个简单的“插件”,您必须意识到,如果您在可拖动容器内部有“处理程序事件”,它们将被激活,因此,您必须确保您在处理程序函数内执行的任何操作都来自预期的事件,而不是鼠标松开(拖动)事件。或者尝试在插件中使用event.stopPropagation(),我尝试过了,但无法停止它。我不确定应该把它放在哪里。 - ncubica
1
我正在使用这个插件,但是当你设置为可拖动的元素具有<select>元素时,它会出现一个重大错误。选择框停止工作(至少在Chrome上)。此外,我不喜欢用户在输入或按钮元素上“开始拖动”时,拖动会执行,然后鼠标单击会执行的UX问题。我更喜欢忽略一些元素来启动拖动事件,因此我分叉了这个插件,以具有类似于jquery-ui的取消选项:https://github.com/pjfsilva/dte-project/blob/master/jquery-draggable/draggable.js现在<select>可以正常工作,并且UX得到了改进。 - AlfaTeK

17

这是我的贡献:

http://jsfiddle.net/g6m5t8co/1/

<!doctype html>
<html>
    <head>
        <style>
            #container {
                position:absolute;
                background-color: blue;
                }
            #elem{
                position: absolute;
                background-color: green;
                -webkit-user-select: none;
                -moz-user-select: none;
                -o-user-select: none;
                -ms-user-select: none;
                -khtml-user-select: none;     
                user-select: none;
            }
        </style>
        <script>
            var mydragg = function(){
                return {
                    move : function(divid,xpos,ypos){
                        divid.style.left = xpos + 'px';
                        divid.style.top = ypos + 'px';
                    },
                    startMoving : function(divid,container,evt){
                        evt = evt || window.event;
                        var posX = evt.clientX,
                            posY = evt.clientY,
                        divTop = divid.style.top,
                        divLeft = divid.style.left,
                        eWi = parseInt(divid.style.width),
                        eHe = parseInt(divid.style.height),
                        cWi = parseInt(document.getElementById(container).style.width),
                        cHe = parseInt(document.getElementById(container).style.height);
                        document.getElementById(container).style.cursor='move';
                        divTop = divTop.replace('px','');
                        divLeft = divLeft.replace('px','');
                        var diffX = posX - divLeft,
                            diffY = posY - divTop;
                        document.onmousemove = function(evt){
                            evt = evt || window.event;
                            var posX = evt.clientX,
                                posY = evt.clientY,
                                aX = posX - diffX,
                                aY = posY - diffY;
                                if (aX < 0) aX = 0;
                                if (aY < 0) aY = 0;
                                if (aX + eWi > cWi) aX = cWi - eWi;
                                if (aY + eHe > cHe) aY = cHe -eHe;
                            mydragg.move(divid,aX,aY);
                        }
                    },
                    stopMoving : function(container){
                        var a = document.createElement('script');
                        document.getElementById(container).style.cursor='default';
                        document.onmousemove = function(){}
                    },
                }
            }();

        </script>
    </head>
    <body>
        <div id='container' style="width: 600px;height: 400px;top:50px;left:50px;">     
            <div id="elem" onmousedown='mydragg.startMoving(this,"container",event);' onmouseup='mydragg.stopMoving("container");' style="width: 200px;height: 100px;">
                <div style='width:100%;height:100%;padding:10px'>
                <select id=test>
                    <option value=1>first
                    <option value=2>second
                </select>
                <INPUT TYPE=text value="123">
                </div>
            </div>
        </div>  
    </body>
</html>

5
好工作... 只有用JavaScript很好 +1 - Utkarsh Dixit
2
太好了,唯一的问题是如果有一个绝对定位的元素,在mousedown事件中它会认为它在左上角位置0-这时是否有一种简单的方法让它在拖动之前保持当前位置呢? - StudioTime
@DarrenSweeney:调用 getBoundingClientRect() 函数获取 #elem 的矩形信息,然后使用该矩形的 top/left 属性初始化 divTop/divLeft 变量!额外加分:这样你就不再需要任何预设或 style 了。 - Sz.
这个比其他答案表现得更好。干得好。 - user736893
1
var a = document.createElement('script'); 是什么意思? - Bowi

16

无Jquery解决方案-基础

最基本的可拖动代码如下:

Element.prototype.drag = function(){

   var mousemove = function(e){ // document mousemove

       this.style.left = ( e.clientX - this.dragStartX ) + 'px';
       this.style.top  = ( e.clientY - this.dragStartY ) + 'px';

   }.bind(this);

   var mouseup = function(e){ // document mouseup

       document.removeEventListener('mousemove',mousemove);
       document.removeEventListener('mouseup',mouseup);

   }.bind(this);

   this.addEventListener('mousedown',function(e){ // element mousedown

       this.dragStartX = e.offsetX;
       this.dragStartY = e.offsetY;

       document.addEventListener('mousemove',mousemove);
       document.addEventListener('mouseup',mouseup);

   }.bind(this));

   this.style.position = 'absolute' // fixed might work as well

}

然后使用(非jQuery):

document.querySelector('.target').drag();

或使用jQuery:

$('.target')[0].drag();

注意:被拖动的元素应该应用position:absoluteposition:fixed以使左、上移动生效...

以下CodePen具有一些更高级的功能:dragStart、dragStop回调,追加CSS类来移除拖动时其他元素的文本选择,还具有放置特性...

请查看以下CodePen:http://codepen.io/anon/pen/VPPaEK

基本上是在需要拖动的元素上设置'mousedown'事件,然后绑定和解绑文档mousemove来处理移动。

可拖动手柄

为了为元素设置可拖动手柄

Element.prototype.drag = function( setup ){

   var setup = setup || {};

   var mousemove = function(e){ // document mousemove

       this.style.left = ( e.clientX - this.dragStartX ) + 'px';
       this.style.top  = ( e.clientY - this.dragStartY ) + 'px';

   }.bind(this);

   var mouseup = function(e){ // document mouseup

       document.removeEventListener('mousemove',mousemove);
       document.removeEventListener('mouseup',mouseup);

   }.bind(this);

   var handle = setup.handle || this;

   handle.addEventListener('mousedown',function(e){ // element mousedown

       this.dragStartX = e.offsetX;
       this.dragStartY = e.offsetY;

       document.addEventListener('mousemove',mousemove);
       document.addEventListener('mouseup',mouseup);

       handle.classList.add('dragging');

   }.bind(this)); 

   handle.classList.add('draggable');

   this.style.position = 'absolute' // fixed might work as well

}

以上代码的用法如下:

var setup = {
   handle : document.querySelector('.handle')
};

document.querySelector('.box').drag(setup);

添加CSS以消除可选文本

现在的问题是,在拖动时,可拖动元素内的文本令人恼火地被选择了,但却毫无用处...

这就是为什么我们已经向该元素添加了draggabledragging类,它们将取消这种不需要的行为,并添加一个移动光标,以显示此元素是可拖动的。

.draggable{
    cursor: move;
    position: fixed;
}

.draggable.dragging{
    user-select: none;
}

添加事件

现在我们有了可拖动的元素,有时候需要调用各种不同的事件。

setup.ondraginit  // this is called when setting up the draggable
setup.ondragstart // this is called when mouse is down on the element
setup.ondragend   // this is called when mouse is released (after dragging)
setup.ondrag      // this is called while the element is being dragged

每个处理程序都会将原始鼠标事件传递给特定的处理程序。
最终,这就是我们得到的...
Element.prototype.drag = function( setup ){

    var setup = setup || {};

    var mousemove = function(e){ // document mousemove

        this.style.left = ( e.clientX - this.dragStartX ) + 'px';
        this.style.top  = ( e.clientY - this.dragStartY ) + 'px';

        setup.ondrag && setup.ondrag(e); // ondrag event

    }.bind(this);

    var mouseup = function(e){ // document mouseup

        document.removeEventListener('mousemove',mousemove);
        document.removeEventListener('mouseup',mouseup);

        handle.classList.remove('dragging');

        setup.ondragend && setup.ondragend(e); // ondragend event

    }.bind(this);

    var handle = setup.handle || this;

    handle.addEventListener('mousedown',function(e){ // element mousedown

        this.dragStartX = e.offsetX;
        this.dragStartY = e.offsetY;

        document.addEventListener('mousemove',mousemove);
        document.addEventListener('mouseup',mouseup);

        handle.classList.add('dragging');

        setup.ondragstart && setup.ondragstart(e); // ondragstart event

    }.bind(this)); 

    handle.classList.add('draggable');

    setup.ondraginit && setup.ondraginit(e); // ondraginit event

}

而要使用它:

var setup = {
   handle      : document.querySelector('.handle'),
   ondragstart : e => { console.log('drag has started!'); },
   ondrag      : e => { console.log('drag!'); },
   ondragend   : e => { console.log('drag has ended!'); }
};

document.querySelector('.box').drag(setup);

请注意,e.target是指向我们可拖动元素的引用。


2
太干净了!我想再创建一个SO账户来投一次票。 ;) - Sz.

8
这里有另一种制作可拖动对象的方法,该对象居中于点击位置。 http://jsfiddle.net/pixelass/fDcZS/
function endMove() {
    $(this).removeClass('movable');
}

function startMove() {
    $('.movable').on('mousemove', function(event) {
        var thisX = event.pageX - $(this).width() / 2,
            thisY = event.pageY - $(this).height() / 2;

        $('.movable').offset({
            left: thisX,
            top: thisY
        });
    });
}
$(document).ready(function() {
    $("#containerDiv").on('mousedown', function() {
        $(this).addClass('movable');
        startMove();
    }).on('mouseup', function() {
        $(this).removeClass('movable');
        endMove();
    });

});

CSS

#containerDiv {
    background:#333;
    position:absolute;
    width:200px;
    height:100px;
}

6

像jQueryUI一样拖动: JsFiddle

你可以从任何点开始拖动元素而不会出现奇怪的居中问题。

$(document).ready(function() {

        var $body = $('body');
        var $target = null;
        var isDraggEnabled = false;

        $body.on("mousedown", "div", function(e) {

            $this = $(this);
            isDraggEnabled = $this.data("draggable");

            if (isDraggEnabled) {
                if(e.offsetX==undefined){
                    x = e.pageX-$(this).offset().left;
                    y = e.pageY-$(this).offset().top;
                }else{
                    x = e.offsetX;
                    y = e.offsetY;
                };

                $this.addClass('draggable');
                $body.addClass('noselect');
                $target = $(e.target);
            };

        });

         $body.on("mouseup", function(e) {
            $target = null;
            $body.find(".draggable").removeClass('draggable');
            $body.removeClass('noselect');
        });

         $body.on("mousemove", function(e) {
            if ($target) {
                $target.offset({
                    top: e.pageY  - y,
                    left: e.pageX - x
                });
            };     
         });

    });

有趣的结果。如果你将它添加到一个 div 中,你可以拖动所有子元素出来,但如果你点击并拖动实际的 div 而不是子元素,这会移动所有东西。 - Chris

5

这是我的。 http://jsfiddle.net/pd1vojsL/

一个 div 内有 3 个可拖动的按钮,拖动受该 div 的限制。

<div id="parent" class="parent">
    <button id="button1" class="button">Drag me</button>
    <button id="button2" class="button">Drag me</button>
    <button id="button3" class="button">Drag me</button>
</div>
<div id="log1"></div>
<div id="log2"></div>

需要 JQuery(仅限):

$(function() {
    $('.button').mousedown(function(e) {
        if(e.which===1) {
            var button = $(this);
            var parent_height = button.parent().innerHeight();
            var top = parseInt(button.css('top')); //current top position
            var original_ypos = button.css('top','').position().top; //original ypos (without top)
            button.css({top:top+'px'}); //restore top pos
            var drag_min_ypos = 0-original_ypos;
            var drag_max_ypos = parent_height-original_ypos-button.outerHeight();
            var drag_start_ypos = e.clientY;
            $('#log1').text('mousedown top: '+top+', original_ypos: '+original_ypos);
            $(window).on('mousemove',function(e) {
                //Drag started
                button.addClass('drag');
                var new_top = top+(e.clientY-drag_start_ypos);
                button.css({top:new_top+'px'});
                if(new_top<drag_min_ypos) { button.css({top:drag_min_ypos+'px'}); }
                if(new_top>drag_max_ypos) { button.css({top:drag_max_ypos+'px'}); }
                $('#log2').text('mousemove min: '+drag_min_ypos+', max: '+drag_max_ypos+', new_top: '+new_top);
                //Outdated code below (reason: drag contrained too early)
                /*if(new_top>=drag_min_ypos&&new_top<=drag_max_ypos) {
                    button.css({top:new_top+'px'});
                }*/
            });
            $(window).on('mouseup',function(e) {
                 if(e.which===1) {
                    //Drag finished
                    $('.button').removeClass('drag');
                    $(window).off('mouseup mousemove');
                    $('#log1').text('mouseup');
                    $('#log2').text('');
                 }
            });
        }
    });
});

非常有帮助,真的节省了我很多时间。 - shuvo sarker

2

我看到的以上内容非常复杂......

以下是一些可供参考的代码。

$("#box").on({
                mousedown:function(e)
                {
                  dragging = true;
                  dragX = e.clientX - $(this).position().left;
                  //To calculate the distance between the cursor pointer and box 
                  dragY = e.clientY - $(this).position().top;
                },
                mouseup:function(){dragging = false;},
                  //If not set this on/off,the move will continue forever
                mousemove:function(e)
                {
                  if(dragging)
                  $(this).offset({top:e.clientY-dragY,left:e.clientX-dragX});

                }
            })

可以将dragging、dragX和dragY作为全局变量。

这是一个关于此问题的简单演示,但该方法存在一些错误。

如果您现在需要,可以在此处查看示例


0

我有一个简单版本的代码,完全不需要使用任何 JQuery,而且实现起来非常容易!

// add 'draggable-object' as an attribute to your div element
var dragDiv = document.querySelector("div[draggable-object]");
// add the '-header' to the header element of the draggable div if you want the user to press a specific part of the element to drag it
var dragDivHeader = document.querySelector("div[draggable-object-header]");

// function for getting the mouse offset from the div position
function getMouseOffset()
{
    var e = window.event;
    
    var divX = parseInt(getComputedStyle(dragDiv).left);
    var divY = parseInt(getComputedStyle(dragDiv).top);
    
    return {
        X: e.clientX - divX,
        Y: e.clientY - divY,
    };
}

// variable for storing mouse offset from div element position
var mouseOffset = null;

// enable the mouse events, but on the document itself for easy dragging
function enableMouseDrag()
{
    document.onmouseup = disableMouseDrag;
    document.onmousemove = dragDivWithMouse;
    
    mouseOffset = getMouseOffset(); // get the mouse offset only when pressing down once
}

// drag div with mouse
function dragDivWithMouse()
{
    var e = window;
    
    // get the new x/y position of the draggable div element
    var newX = e.clientX - mouseOffset.X;
    var newY = e.clientY - mouseOffset.Y;
    
    // sets the new element position while also keeping it inside the document page without more lines of code
    dragDiv.style.left = Math.max(2, Math.min(newX, document.documentElement.clientWidth - dragDiv.offsetWidth - 2)) + "px";
    dragDiv.style.top = Math.max(2, Math.min(newY, document.documentElement.clientHeight - dragDiv.offsetHeight - 2)) + "px";
}

// disable the mouse events for the document itself
function disableMouseDrag()
{
    document.onmouseup = null;
    document.onmousemove = null;
}

// add the enableMouseDrag event callback to the div itself, or specify its header if you want
dragDiv.onmousedown = enableMouseDrag;
// dragDivHeader.onmousedown = enableMouseDrag;

我希望这能成为一个简单易用的解决方案。


0
$(document).ready(function() {
    var $startAt = null;

    $(document.body).live("mousemove", function(e) {
        if ($startAt) {
            $("#someDiv").offset({
                top: e.pageY,
                left: $("#someDiv").position().left-$startAt+e.pageX
            });
            $startAt = e.pageX; 
        }
    });

    $("#someDiv").live("mousedown", function (e) {$startAt = e.pageX;});
    $(document.body).live("mouseup", function (e) {$startAt = null;});
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接