64

I'm trying to make a div draggable without using jQuery UI.

However, I'm stuck with the code below. I understand that I should use the mouse position relative to the container div (in which the div will be dragged) and that I should set the div's offset relative to those values.

I just can't figure out how. Any clues?

This is the code that doesn't (of course) work:

var X, Y;

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

$("#containerDiv").mousemove(function(event) {
    X = event.pageX;
    Y = event.pageY;
});
Community
  • 1
  • 1
holyredbeard
  • 19,619
  • 32
  • 105
  • 171

12 Answers12

76

Here's a really simple example that might get you started:

$(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;
    });
});

Example: http://jsfiddle.net/Jge9z/

I understand that I shall use the mouse position relative to the container div (in which the div shall be dragged) and that I shall set the divs offset relative to those values.

Not so sure about that. It seems to me that in drag and drop you'd always want to use the offset of the elements relative to the document.

If you mean you want to constrain the dragging to a particular area, that's a more complicated issue (but still doable).

Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
  • Works great, but the dragging does not work on mobile devices. How can this code be altered to work on mobile as well? – Armin Jan 09 '14 at 21:42
  • Not sure. You'd probably want to look into [touch events](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events) – Andrew Whitaker Jan 09 '14 at 21:46
  • 2
    I suggest to extend the code by adding `mouseleave` event, like `$("body").mouseleave(function(e) { $dragging = null; })` to catch releasing pressed button outside of browser window. – rabudde Apr 10 '14 at 06:46
  • 2
    There is als [Drag and Drop HTML5 API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API) that can be used – Adam Szmyd Oct 12 '17 at 05:01
  • its work but small problem with position fixed and absolute – khalid J-A Aug 27 '18 at 05:21
43

Here's another updated code:

$(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');
    });
});​

Demo: http://jsfiddle.net/tovic/Jge9z/31/


I've created a simple plugin to this thread.

// 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);

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

Taufik Nurrohman
  • 3,329
  • 24
  • 39
  • 2
    if you use this code `$selected = $(this).parent();` line `38:59`your are forcing the developer to use a fixed html structure. you are assuming the parent its gonna be the complete `structure` to move, but if instead of that you assign a class like say `class='draggableContainer'` you can do something like `$selected = $(this).closest(".draggableContainer");` you can now use any html structure and its going to work. But in general cool `$hit` working really cool – ncubica Nov 12 '14 at 08:21
  • Also if you are using this simple `plugin` you have to realize if you have `handler events` inside of the draggable container will activate them so, you have to be sure that whatever you execute inside of you function of that handler come from the expected event and not from the mouseup (drag) event. Or try to use `event.stopPropagation()` in the plugin, I tried but I couldnt stop it. I didnt know exactly where to put it. – ncubica Nov 12 '14 at 18:06
  • 1
    I'm using this plugin but it has a major bug when the element you set as draggable has works and the UX is improved – AlfaTeK Apr 22 '15 at 01:39
17

Here's my contribution:

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>
niente00
  • 821
  • 7
  • 3
  • 5
    Good Work... Only with javascript nice +1 – Utkarsh Dixit Feb 15 '15 at 08:39
  • 2
    Thats excellent, only issue with it is if you have an absolute positioned element, on mousedown it thinks it's at top & left 0 - is there an easy way for it to hold its current position first before dragging? – StudioTime Mar 08 '15 at 14:54
  • @DarrenSweeney: Call `getBoundingClientRect()` for `#elem`, and then use that rect's `top`/`left` to init `divTop`/`divLeft`! Bonus: you'll no longer need any presets, or `style` whatsoever then. – Sz. Mar 27 '18 at 22:58
  • This one behaves better than the other answers. Nice work. –  Jan 23 '21 at 22:28
  • 1
    What does `var a = document.createElement('script');` do? – Bowi Sep 24 '21 at 08:27
16

No Jquery Solution - Basic

The most basic draggable code would be :

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

}

and then usage (non-jquery) :

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

or in jquery :

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

Notice : the dragged element should have a position:absolute or position:fixed applied to it for the left,top movement to work...

the codepen below has some more "advanced" features : dragStart, dragStop callbacks, css classes appending to remove text selection of other elements while dragging, and a drop feature also...

checkout the following codepen :

http://codepen.io/anon/pen/VPPaEK

its basically setting a 'mousedown' event on the element which needs to be dragged, and then binding and unbinding the document mousemove to handle the movement.

Draggable Handle

in order to set a draggable handle for the element

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

}

The above code is used like so :

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

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

Adding CSS to eliminate selectable text

The problem now, is that when dragging, the text within the draggable element is annoyingly being selected with no use...

This is why we have added the draggable and dragging classes to the element. which will cancel out this unwanted behavior, and also add a move cursor, to display that this element is draggable

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

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

Adding Events

So now that we have our draggable element, we sometimes need to call various events.

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

Each will pass the original mouse event to the specific handler

Finally, this is what we get...

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

}

And to use this :

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

note that e.target is a reference back to our draggable element

  • 2
    Fantastic cleanliness! Thought about creating another SO account just to vote once more. ;) – Sz. Mar 27 '18 at 23:37
8

here's another way of making a draggable object that is centered to the click

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

Dragging like jQueryUI: JsFiddle

You can drag the element from any point without weird centering.

$(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
                });
            };     
         });

    });
Sams
  • 684
  • 1
  • 8
  • 14
  • Interesting result. If you add this to a div, you can drag all of its child elements out of it, but if you click and drag on the actual div and not a child, this moves everything. – Chris Sep 08 '16 at 02:40
5

This is mine. http://jsfiddle.net/pd1vojsL/

3 draggable buttons in a div, dragging constrained by 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>

Requires JQuery (only):

$(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('');
                 }
            });
        }
    });
});
Richard
  • 922
  • 11
  • 11
2

What I saw above is complicate.....

Here is some code can refer to.

$("#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 may place as the global variable.

It's a simple show about this issue,but there is some bug about this method.

If it's your need now,here's the Example here.

Hsinhsin Hung
  • 343
  • 1
  • 2
  • 12
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;});
});
Wasim A.
  • 9,660
  • 22
  • 90
  • 120
0

I have a simple version of this that doesn't use any JQuery whatsoever, and it's even easy to implement!

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

I hope this works as a simple usable solution.

-1

Here is my simple version.
The function draggable takes a jQuery object as argument.


/**
 * @param {jQuery} elem
 */
function draggable(elem){

    elem.mousedown(function(evt){
        var x = parseInt(this.style.left || 0) - evt.pageX;
        var y = parseInt(this.style.top || 0) - evt.pageY;

        elem.mousemove(function(evt){
            elem.css('left', x + evt.pageX);
            elem.css('top', y + evt.pageY);
        });
    });

    elem.mouseup(off);
    elem.mouseleave(off);

    function off(){
        elem.off("mousemove");
    }
}

John Berg
  • 105
  • 6
-2

Here is an implementation without using jQuery at all -
http://thezillion.wordpress.com/2012/09/27/javascript-draggable-2-no-jquery

Embed the JS file (http://zillionhost.xtreemhost.com/tzdragg/tzdragg.js) in your HTML code, and put the following code -

<script>
win.onload = function(){
 tzdragg.drag('elem1, elem2, ..... elemn');
 // ^ IDs of the draggable elements separated by a comma.
}
</script>

And the code is also easy to learn.
http://thezillion.wordpress.com/2012/08/29/javascript-draggable-no-jquery

thezillion
  • 43
  • 1