57

Okay it would seem like it should be simple. I need to take an already existing div and move it according to mouse position within the window. I have searched everywhere and it has led me to over-complicated ways of doing the same thing and involves the use of j-query. I need to strictly use javascript for what I am trying to do.

Method :

var mousePosition;
var div;

(function createDiv(){

    div = document.createElement("div");
    div.style.position = "absolute";
    div.style.left = "0px";
    div.style.top = "0px";
    div.style.width = "100px";
    div.style.height = "100px";
    div.style.background = "red";
    div.style.color = "blue";

    div.addEventListener('mousedown', handleKeyPressed, true);

    document.body.appendChild(div);


})();

function handleKeyPressed(event) {

    event.preventDefault();

    mousePosition = {

        x : event.clientX,
        y : event.clientY

    };

    div.style.left = mousePosition.x;
    div.style.top = mousePosition.y;

    //alert("whoa!");

}
StoneAgeCoder
  • 923
  • 1
  • 7
  • 13
  • 1
    jQuery can do this, it's great for all things. Add jQuery UI and you have draggable out of the box. – adeneo Jun 05 '14 at 02:37
  • 11
    @adeneo jQuery is a javascript library want to learn about javascript not jquery although I know it is conventional to use it, not interested in learning it as of now. Just looking into pure javascript. – StoneAgeCoder Jun 05 '14 at 02:40

9 Answers9

101

I think you're looking for something more like this

var mousePosition;
var offset = [0,0];
var div;
var isDown = false;

div = document.createElement("div");
div.style.position = "absolute";
div.style.left = "0px";
div.style.top = "0px";
div.style.width = "100px";
div.style.height = "100px";
div.style.background = "red";
div.style.color = "blue";

document.body.appendChild(div);

div.addEventListener('mousedown', function(e) {
    isDown = true;
    offset = [
        div.offsetLeft - e.clientX,
        div.offsetTop - e.clientY
    ];
}, true);

document.addEventListener('mouseup', function() {
    isDown = false;
}, true);

document.addEventListener('mousemove', function(event) {
    event.preventDefault();
    if (isDown) {
        mousePosition = {

            x : event.clientX,
            y : event.clientY

        };
        div.style.left = (mousePosition.x + offset[0]) + 'px';
        div.style.top  = (mousePosition.y + offset[1]) + 'px';
    }
}, true);

FIDDLE

adeneo
  • 312,895
  • 29
  • 395
  • 388
  • 1
    It's a bit jittery though. – Ali Gajani Jun 05 '14 at 02:44
  • 1
    It's because it's always at the top left corner of the mouse pointer, you have to calculate the offset of the element in relation to where you clicked, but that's almost a question in itself. – adeneo Jun 05 '14 at 02:45
  • 1
    @adeneo thanks. :) although you could have simply called me an idiot for forgetting to add px to the end of div.style.left = e.clientX;. But this helped me even more much appreciated. – StoneAgeCoder Jun 05 '14 at 02:50
  • 2
    @StoneAgeCoder - Yes, in plain JS units are required. Lets see if we can't fix that jitter, one sec. – adeneo Jun 05 '14 at 02:53
  • @adeneo one word. perfect. – StoneAgeCoder Jun 05 '14 at 03:00
  • @StoneAgeCoder - thank you, hope it works for what you're trying to do! – adeneo Jun 05 '14 at 03:10
  • 3
    This is by far the best solution I have ever seen. The script is small and easy to understand and the results work better than anything else I've seen on various browsers. I am compelled to comment and vote, if only to find this again in the future. – sanepete Aug 17 '18 at 10:57
  • 1
    Great solution but you could use `div.addEventListener` everywhere instead `document.addEventListener` if you wish to release mouse for other actions on the document. Thanks – Konstantin Feb 27 '20 at 13:24
  • Put `event.preventDefault();` inside `if (isDown)`, otherwise you can't interact with the page. – Paul Oct 12 '20 at 10:26
  • my element is not moving properly in x direction but moving fine in y direction what could be worng? @adeneo I am moving an existing element instead of creating new element. – AbhimanuSharma Dec 08 '20 at 05:20
  • When moving quickly this can cause some rendering glitches. I solved this by wrapping the element position change in a call to `window.requestAnimationFrame()`. – Ken Wayne VanderLinde Dec 16 '20 at 06:36
  • Thank you so much for this demo of draggable divs! I made an improved version here: https://jsfiddle.net/gcakuw50/2/ – Ryan Apr 14 '21 at 14:01
  • https://stackoverflow.com/a/25933582/470749 is nice too. – Ryan Apr 14 '21 at 15:34
  • @adeneo You are a savior sir! Thank you so much, I was trying to solve this problem for a few days on my own. – M. Çağlar TUFAN Apr 27 '21 at 23:13
  • Is a boolean really necessary? Shouldn’t it be enough to simply remove the mousemove event listener on mouseup and add it onclick? – LuckyLuke Skywalker Jun 07 '23 at 11:58
23

Support for touch inputs

All other answers (including the accepted one) do not work with touch inputs. Touch inputs have events different than that of mouse inputs. See Using Touch Events on MDN.

The following code snippet works even with touch inputs. I have highlighted all lines of code that need to be added to support touch devices.
The basic idea here is that every element containing draggable in the class list should be draggable. This concept is easier to follow when you have a big number of elements that need to be dragged.

See this Glitch page and following for a demo.

const d = document.getElementsByClassName("draggable");

for (let i = 0; i < d.length; i++) {
  d[i].style.position = "relative";
}

function filter(e) {
  let target = e.target;

  if (!target.classList.contains("draggable")) {
    return;
  }

  target.moving = true;

  //NOTICE THIS  Check if Mouse events exist on users' device
  if (e.clientX) {
    target.oldX = e.clientX; // If they exist then use Mouse input
    target.oldY = e.clientY;
  } else {
    target.oldX = e.touches[0].clientX; // Otherwise use touch input
    target.oldY = e.touches[0].clientY;
  }
  //NOTICE THIS  Since there can be multiple touches, you need to mention which touch to look for, we are using the first touch only in this case

  target.oldLeft = window.getComputedStyle(target).getPropertyValue('left').split('px')[0] * 1;
  target.oldTop = window.getComputedStyle(target).getPropertyValue('top').split('px')[0] * 1;

  document.onmousemove = dr;
  //NOTICE THIS 
  document.ontouchmove = dr;
  //NOTICE THIS 

  function dr(event) {
    event.preventDefault();

    if (!target.moving) {
      return;
    }
    //NOTICE THIS 
    if (event.clientX) {
      target.distX = event.clientX - target.oldX;
      target.distY = event.clientY - target.oldY;
    } else {
      target.distX = event.touches[0].clientX - target.oldX;
      target.distY = event.touches[0].clientY - target.oldY;
    }
    //NOTICE THIS 

    target.style.left = target.oldLeft + target.distX + "px";
    target.style.top = target.oldTop + target.distY + "px";
  }

  function endDrag() {
    target.moving = false;
  }
  target.onmouseup = endDrag;
  //NOTICE THIS 
  target.ontouchend = endDrag;
  //NOTICE THIS 
}
document.onmousedown = filter;
//NOTICE THIS 
document.ontouchstart = filter;
//NOTICE THIS 
.draggable {
  width: 100px;
  height: 100px;
  background: red;
}
<div class="draggable"></div>
mmaismma
  • 763
  • 6
  • 19
9

Check if this is smoother than adeneo: FIDDLE

var m = document.getElementById('move');
m.addEventListener('mousedown', mouseDown, false);
window.addEventListener('mouseup', mouseUp, false);

function mouseUp() {
    window.removeEventListener('mousemove', move, true);
}

function mouseDown(e) {
    window.addEventListener('mousemove', move, true);
}

function move(e) {
    m.style.top = e.clientY + 'px';
    m.style.left = e.clientX + 'px';
};
jhyap
  • 3,779
  • 6
  • 27
  • 47
  • 1
    I didn't find this smoother than the accepted answer. It was actually a bit jumpy. Am I doing something wrong? – sanepete Aug 17 '18 at 10:59
  • 1
    Yeah. the accepted answer is now smoother than mine. Please use the accepted answer. – jhyap Aug 20 '18 at 03:09
  • hello, could you help me with this problem? I can't get mouse wheel dragging to work https://stackoverflow.com/questions/70444738/disabled-move-a-dragdrop-element-when-scrolling-wheel – Lenin Zapata Dec 23 '21 at 22:13
7

I just made a small change to the @adeneo very well working answer. If everything is enclosed in a function, and every event is attached to the div, you can use it as part of a library.

Call the following function passing an id. If the div does not exist it is created.

function drag_div(div_id){
var div;

div = document.getElementById(div_id);

if(div == null){
   div = document.createElement("div");
   div.id = div_id;
   div.style.position = "absolute";
   div.style.left = "0px";
   div.style.top = "0px";
   div.style.width = "100px";
   div.style.height = "100px";
   div.style.background = "red";
   div.style.color = "blue";
   document.body.appendChild(div);
}

div.addEventListener('mousedown', function(e) {
    div.isDown = true;
    div.offset = [
        div.offsetLeft - e.clientX,
        div.offsetTop - e.clientY
    ];
}, true);

div.addEventListener('mouseup', function() {
    div.isDown = false;
}, true);

div.addEventListener('mousemove', function(event) {
    event.preventDefault();
    if (div.isDown) {
        div.mousePosition = {

            x : event.clientX,
            y : event.clientY

        };
        div.style.left = (div.mousePosition.x + div.offset[0]) + 'px';
        div.style.top  = (div.mousePosition.y + div.offset[1]) + 'px';
    }
}, true);
}
minivip
  • 195
  • 1
  • 6
  • Thanks for the idea with the library. But the divs are stuttering when you move your mouse fast. The mousemove event should be added the document. – J. Doe Nov 12 '19 at 20:56
  • hello, could you help me with this problem? I can't get mouse wheel dragging to work https://stackoverflow.com/questions/70444738/disabled-move-a-dragdrop-element-when-scrolling-wheel – Lenin Zapata Dec 23 '21 at 22:19
2

You can use this one as a library. Works perfectly. I found it on github but it was getting stuck sometimes because the sharer put "mouseup" to element. I changed it to document and it fixed the problem. This is fixed version

'use strict';

/**
 * Makes an element draggable.
 *
 * @param {HTMLElement} element - The element.
 */
function draggable(element) {
    var isMouseDown = false;

    // initial mouse X and Y for `mousedown`
    var mouseX;
    var mouseY;

    // element X and Y before and after move
    var elementX = 0;
    var elementY = 0;

    // mouse button down over the element
    element.addEventListener('mousedown', onMouseDown);

    /**
     * Listens to `mousedown` event.
     *
     * @param {Object} event - The event.
     */
    function onMouseDown(event) {
        mouseX = event.clientX;
        mouseY = event.clientY;
        isMouseDown = true;
    }

    // mouse button released
    document.addEventListener('mouseup', onMouseUp);

    /**
     * Listens to `mouseup` event.
     *
     * @param {Object} event - The event.
     */
    function onMouseUp(event) {
        isMouseDown = false;
        elementX = parseInt(element.style.left) || 0;
        elementY = parseInt(element.style.top) || 0;
    }

    // need to attach to the entire document
    // in order to take full width and height
    // this ensures the element keeps up with the mouse
    document.addEventListener('mousemove', onMouseMove);

    /**
     * Listens to `mousemove` event.
     *
     * @param {Object} event - The event.
     */
    function onMouseMove(event) {
        if (!isMouseDown) return;
        var deltaX = event.clientX - mouseX;
        var deltaY = event.clientY - mouseY;
        element.style.left = elementX + deltaX + 'px';
        element.style.top = elementY + deltaY + 'px';
    }
}
Kyoko Sasagava
  • 113
  • 1
  • 8
2

Here's another approach that includes touch input.

dragElement(document.getElementById('mydiv'));

function dragElement(element) {
    var startX = 0, startY = 0, endX = 0, endY = 0;
    element.onmousedown = dragStart;
    element.ontouchstart = dragStart;

    function dragStart(e) {
        e = e || window.event;
        e.preventDefault();
        // mouse cursor position at start  
        if (e.clientX) {  // mousemove
            startX = e.clientX;
            startY = e.clientY;
        } else { // touchmove - assuming a single touchpoint
            startX = e.touches[0].clientX
            startY = e.touches[0].clientY
        }
        document.onmouseup = dragStop;
        document.ontouchend = dragStop;
        document.onmousemove = elementDrag;  // call whenever the cursor moves
        document.ontouchmove = elementDrag;
    }

    function elementDrag(e) {
        e = e || window.event;
        e.preventDefault();
        // calculate new cursor position
        if (e.clientX) {
            endX = startX - e.clientX;
            endY = startY - e.clientY;
            startX = e.clientX;
            startY = e.clientY;
        } else {
            endX = startX - e.touches[0].clientX;
            endY = startY - e.touches[0].clientY;
            startX = e.touches[0].clientX;
            startY = e.touches[0].clientY;
        }
        // set the new position
        element.style.left = (element.offsetLeft - endX) + "px";
        element.style.top = (element.offsetTop - endY) + "px";
    }

    function dragStop() {
        // stop moving on touch end / mouse btn is released 
        document.onmouseup = null;
        document.onmousemove = null;
        document.ontouchend = null;
        document.ontouchmove = null;
    }
}
Maxi
  • 421
  • 4
  • 4
1

jquery is much easier to deploy. I am surprised you say you don't want to learn it.

You can save the jquery file in your local computer so you do not need internet to use jquery features.

In my case i have saved it in tools folder. So i do not need to be on internet.

For all the js many lines of js code answered above you only need one small line.

 <script src="/common/tools/jquery-1.10.2.js"></script>
 <script src="/common/tools/jquery-ui.js"></script>

 <script>
   $(function() {
   $( "#mydiv_to_make_draggable" ).draggable();
   });
</script>
webzy
  • 338
  • 3
  • 12
  • 13
    JQuery is easy to use, however, anyone can use it. I'm also a programmer that likes coding everything from scratch. If you include JQuery library in your code and your only using one or two functions from it, it wastes a lot of space. – Vince Aug 17 '15 at 19:33
  • 3
    +1 - too many people jump to jQuery now and they don't (1) understand or recognize why jQuery existed in the first place and (2) evaluate the current environment for the need for such a large library of code. More times than not - using pure ES6 with an ES5 transpilation will result in future proof'd code, less bloat, and better understanding of programming and how it works. Not "just getting it to work". – Markus Aug 05 '16 at 22:19
1

Accepted answer with touch added

The accepted answer from adeneo is really elegant and works well. However it only works for mouse clicks, so here is an amended version which includes touch input:

var position;
var offset = [0,0];
var isDown = false;

function makeDraggable(el){

    ['mousedown', 'touchstart'].forEach( evt => 
        el.addEventListener(evt, pickup, true)
    );
    
    ['mousemove', 'touchmove'].forEach( evt => 
        el.addEventListener(evt, move, true)
    );

    ['mouseup', 'touchend'].forEach( evt => 
        el.addEventListener(evt, drop, true)
    );      
        
    function pickup(e) {
        isDown = true;
        if (e.clientX) {
            offset = [el.offsetLeft - e.clientX, el.offsetTop - e.clientY];
        }
        else if (e.touches) {  
            // for touch devices, use 1st touch only
            offset = [el.offsetLeft - e.touches[0].pageX, el.offsetTop - e.touches[0].pageY];
        }       
    }
    function move(e) {
        if (isDown) {
            if (e.clientX) {
                position = {x : e.clientX, y : e.clientY};
            }
            else if (e.touches) {
                position = {x : e.touches[0].pageX, y : e.touches[0].pageY};            
            }           
            el.style.left = (position.x + offset[0]) + 'px';
            el.style.top  = (position.y + offset[1]) + 'px';
        }
    }
    function drop(e) {
        // seems not to be needed for Android Chrome
        // and modern browsers on Mac & PC
        // but is required for iPad & iPhone
        isDown = false;     
        el.style.left = (position.x + offset[0]) + 'px';
        el.style.top  = (position.y + offset[1]) + 'px';
    }
}
Bob Osola
  • 59
  • 5
1

Late to the party but, here's what I find is a simpler and touch-friendly implementation:

  • Use the "pointerdown/move/up" Events.
    Also, there's no need to constantly listen for mousemove events and use booleans like isMove etc. Instead, attach the move and up events on pointerdown, and use Element.removeEventListener on pointerup to detach the listeners from window.
  • Use CSS touch-action: none; to make the pointer events work on a touch device
  • Instead of storing drag start coordinates, use Event.movementX,Y on the element's current Element.offsetLeft/Top

const drag = (evt) => {
  
  const el = evt.currentTarget;
  el.style.touchAction = "none";
  
  const move = (evt) => {
    el.style.left = `${el.offsetLeft + evt.movementX}px`;
    el.style.top = `${el.offsetTop + evt.movementY}px`;
  };
  
  const up = () => {
    removeEventListener("pointermove", move);
    removeEventListener("pointerup", up);
  };
  
  addEventListener("pointermove", move);
  addEventListener("pointerup", up);
};


// Use like:
document.querySelector("#box").addEventListener("pointerdown", drag);
#box { position: absolute; width: 50px; height: 50px; background: red; }
<div id="box"></div>
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313