Reputation: 1388
This is my updated and modified script, it works completely, except I would like to universalize it... observe the **** how can I make it so that I don't have to do function(e){BOX.Draggable.elemen = e.target || e.srcElement; elementDraggable(e);
everytime I need to use the dragable function for a different element?
window.onload = addListeners;
var BOX = function(){
return{
Draggable: function(){}
};
}();
function addListeners(){
document.getElementById('div').addEventListener('contextmenu', menumove, false);
**document.getElementById('div').addEventListener('mousedown', function(e){BOX.Draggable.elemen = e.target || e.srcElement; elementDraggable(e);}, false);**
}
function elementDraggable(e){
var e = e || window.event;
var div = BOX.Draggable.elemen;
BOX.Draggable.innerX = e.clientX + window.pageXOffset - div.offsetLeft;
BOX.Draggable.innerY = e.clientY + window.pageYOffset - div.offsetTop;
window.addEventListener('mousemove', elementMove, false);
window.addEventListener('mouseup', function(){
window.removeEventListener('mousemove', elementMove, false);
}, true);
function elementMove(e){
div.style.position = 'absolute';
div.style.left = e.clientX + window.pageXOffset - BOX.Draggable.innerX + 'px';
div.style.top = e.clientY + window.pageYOffset - BOX.Draggable.innerY + 'px';
}
}
Upvotes: 66
Views: 226799
Reputation: 567
Another approach to touch.
function draggable(container, handle) {
let movable = handle ? handle : container;
['mousedown', 'touchstart'].forEach(event => {
movable.addEventListener(event, e => {
var c = e2c(e);
var offsetX = c.x - parseInt(getComputedStyle(container).left);
var offsetY = c.y - parseInt(getComputedStyle(container).top);
function e2c(e){
var client={x:0,y:0};
if(typeof e.touches != "undefined" && e.touches.length > 0){
client.x = e.touches[0].clientX;
client.y = e.touches[0].clientY;
}else{
client.x = e.clientX;
client.y = e.clientY;
}
return client;
}
function mouseMoveHandler(e) {
var c = e2c(e);
container.style.top = (c.y - offsetY) + 'px';
container.style.left = (c.x - offsetX) + 'px';
position[container.id].top=(c.y - offsetY);
position[container.id].left=(c.x - offsetX);
}
function reset() {
removeEventListener('mousemove', mouseMoveHandler);
removeEventListener('touchmove', mouseMoveHandler);
removeEventListener('mouseup', reset);
removeEventListener('touchend', reset);
}
addEventListener('mousemove', mouseMoveHandler);
addEventListener('touchmove', mouseMoveHandler);
addEventListener('mouseup', reset);
addEventListener('touchend', reset);
});
})
}
It's not that impressive, but it works.
makeDragable('#handle1', '#moveable1');
makeDragable('#handle2', '#moveable2');
makeDragable('#handle3', '#moveable3');
Upvotes: 0
Reputation: 2301
After trying jnoreiga's accepted answer, I found it very annoying that the dragged element abruptly snapped to the top left corner, rather than maintaining the same relative position.
This snippet prevents the awkward aforementioned behavior via an offset, and provides a simple interface to create draggable elements one at a time or en masse via a forEach
call or similar.
window.onload = function() {
draggable(document.getElementById('foo'));
}
function draggable(el) {
el.addEventListener('mousedown', function(e) {
var offsetX = e.clientX - parseInt(window.getComputedStyle(this).left);
var offsetY = e.clientY - parseInt(window.getComputedStyle(this).top);
function mouseMoveHandler(e) {
el.style.top = (e.clientY - offsetY) + 'px';
el.style.left = (e.clientX - offsetX) + 'px';
}
function reset() {
window.removeEventListener('mousemove', mouseMoveHandler);
window.removeEventListener('mouseup', reset);
}
window.addEventListener('mousemove', mouseMoveHandler);
window.addEventListener('mouseup', reset);
});
}
/* The only required styling is position: absolute */
#foo {
position: absolute;
border: 1px solid black;
overflow: hidden;
}
/* Prevents inconsistent highlighting of element while being dragged
Copied from https://stackoverflow.com/questions/826782 */
.noselect {
-webkit-user-select: none; /* Safari */
user-select: none; /* Every other browser */
}
<div id="foo" class="noselect">This is a draggable div!</div>
Upvotes: 16
Reputation: 6554
This is modified @evan answer, it add handle
function draggable(container, handle) {
let movable = handle ? handle : container;
['mousedown', 'touchstart'].forEach(event => {
movable.addEventListener(event, e => {
var offsetX = e.clientX - parseInt(getComputedStyle(container).left);
var offsetY = e.clientY - parseInt(getComputedStyle(container).top);
function mouseMoveHandler(e) {
container.style.top = (e.clientY - offsetY) + 'px';
container.style.left = (e.clientX - offsetX) + 'px';
}
function reset() {
removeEventListener('mousemove', mouseMoveHandler);
removeEventListener('mouseup', reset);
}
addEventListener('mousemove', mouseMoveHandler);
addEventListener('mouseup', reset);
});
})
}
// box only
let boxOnly = document.querySelector('#boxOnly');
draggable(boxOnly);
// box with handle
let container = document.querySelector('#foo');
let header = document.querySelector('#foo .header')
draggable(container, header);
/* The only required styling is position: absolute */
#foo, #boxOnly {
position: absolute;
border: 1px solid black;
user-select: none;
}
#boxOnly{left: 300px;}
.header { background: orange;margin: 0;text-align:center; padding: 6px;}
p.header:active, div#boxOnly:active {cursor:move;}
<div id="foo">
<p class="header">Header</p>
<p>Content cannot be dragged, <br>use header</p>
</div>
<div id="boxOnly">
<p>Content can be dragged</p>
</div>
Upvotes: 2
Reputation: 10391
I modified Shaedo's code a little bit, wraps it in a function and add a feature that you can drag an element by only parts of it or its children, say the title bar of a div. Note in this demo, you can only drag the red area to move the blue area.
function makeDragable(dragHandle, dragTarget) {
// used to prevent dragged object jumping to mouse location
let xOffset = 0;
let yOffset = 0;
let handle = document.querySelector(dragHandle);
handle.addEventListener("mousedown", startDrag, true);
handle.addEventListener("touchstart", startDrag, true);
/*sets offset parameters and starts listening for mouse-move*/
function startDrag(e) {
e.preventDefault();
e.stopPropagation();
let dragObj = document.querySelector(dragTarget);
// shadow element would take the original place of the dragged element, this is to make sure that every sibling will not reflow in the document
let shadow = dragObj.cloneNode();
shadow.id = ""
// You can change the style of the shadow here
shadow.style.opacity = 0.5
dragObj.parentNode.insertBefore(shadow, dragObj.nextSibling);
let rect = dragObj.getBoundingClientRect();
dragObj.style.left = rect.left;
dragObj.style.top = rect.top;
dragObj.style.position = "absolute";
dragObj.style.zIndex = 999999;
/*Drag object*/
function dragObject(e) {
e.preventDefault();
e.stopPropagation();
if(e.type=="mousemove") {
dragObj.style.left = e.clientX-xOffset + "px"; // adjust location of dragged object so doesn't jump to mouse position
dragObj.style.top = e.clientY-yOffset + "px";
} else if(e.type=="touchmove") {
dragObj.style.left = e.targetTouches[0].clientX-xOffset +"px"; // adjust location of dragged object so doesn't jump to mouse position
dragObj.style.top = e.targetTouches[0].clientY-yOffset +"px";
}
}
/*End dragging*/
document.addEventListener("mouseup", function() {
// hide the shadow element, but still let it keep the room, you can delete the shadow element to let the siblings reflow if that is what you want
shadow.style.opacity = 0
shadow.style.zIndex = -999999
window.removeEventListener('mousemove', dragObject, true);
window.removeEventListener('touchmove', dragObject, true);
}, true)
if (e.type=="mousedown") {
xOffset = e.clientX - rect.left; //clientX and getBoundingClientRect() both use viewable area adjusted when scrolling aka 'viewport'
yOffset = e.clientY - rect.top;
window.addEventListener('mousemove', dragObject, true);
} else if(e.type=="touchstart") {
xOffset = e.targetTouches[0].clientX - rect.left;
yOffset = e.targetTouches[0].clientY - rect.top;
window.addEventListener('touchmove', dragObject, true);
}
}
}
makeDragable('#handle1', '#moveable1')
makeDragable('#handle2', '#moveable2')
makeDragable('#handle3', '#moveable3')
.moveable {
width: 100px;
height: 100px;
background: blue;
display: inline-block
}
.handle {
width: 100px;
height: 20px;
cursor: move;
background: red;
}
#moveable2 { background: green }
#moveable3 { background: teal }
<div id="moveable1" class="moveable">
<div id="handle1" class="handle">
</div>
</div>
<div id="moveable2" class="moveable">
<div id="handle2" class="handle">
</div>
</div>
<div id="moveable3" class="moveable">
<div id="handle3" class="handle">
</div>
</div>
Upvotes: 14
Reputation: 841
This is a nice no-jQuery script to drag a div: http://jsfiddle.net/g6m5t8co/1/
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() {}
},
}
}();
#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;
}
<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>
Upvotes: 67
Reputation: 42
For all my friends:
function initDrag(div) {
div.addEventListener('mousedown', e => {
if (e.target === div){
startDrag(e, div);
};
});
};
function startDrag(e, div) {
const offsetX = e.offsetX;
const offsetY = e.offsetY;
const controller = new AbortController();
div.style.cursor = "grabbing";
document.addEventListener('mousemove', e => {
div.style.top = window.scrollY + e.clientY - offsetY + 'px';
div.style.left = window.scrollX + e.clientX - offsetX + 'px';
}, { signal: controller.signal }, true);
'mouseup,blur'.split(',').forEach(e => {
document.addEventListener(e, () => {
controller.abort();
div.style.cursor = "grab";
}, { once: true }, true);
});
};
Upvotes: 2
Reputation: 20951
Any solution that uses clientY
, clientX
, pageY
, or pageX
within the dragend
event will completely fail in Firefox. Source: Bugzilla: Bug #505521, Set screen coordinates during HTML5 drag event.
How do we get by this limitation? document
's drop
event also fires at the same exact time as the dragend
event of the dragged element. But, we can see things like clientY
and clientX
here in firefox. So, let's just use that.
Two working demos, 100% JavaScript-Only Solution: SO Code Snippet and JSBin.
var startx = 0;
var starty = 0;
dragStartHandler = function(e) {
startx = e.clientX;
starty = e.clientY;
}
dragOverHandler = function(e) {
e.preventDefault();
return false;
}
dragEndHandler = function(e) {
if(!startx || !starty) {
return false;
}
var diffx = e.clientX - startx;
var diffy = e.clientY - starty;
var rect = e.target.getBoundingClientRect();
var offset = {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX,
};
var newleft = offset.left + diffx;
var newtop = offset.top + diffy;
e.target.style.position = 'absolute';
e.target.style.left = newleft + 'px';
e.target.style.top = newtop + 'px';
startx = 0;
starty = 0;
}
document.getElementsByClassName("draggable")[0].addEventListener('dragstart', dragStartHandler);
document.addEventListener('dragover', dragOverHandler);
document.addEventListener('drop', dragEndHandler);
.draggable {
border: 1px solid black;
cursor: move;
width:250px;
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<BR><BR><BR>
<div id="draggable1" class="draggable" draggable="true">
Hey, try to drag this element!
</div>
</body>
</html>
Explanation:
dragStartHandler()
: This is bound to the draggable element. Here, all we do is record the current x/y coordinates at start.dragOverHandler()
: This is bound to the document, so that we can override the default dragover behavior. This is needed to do any type of drag & dropping.dragEndHandler()
: This is bound to the document
's drop
. Normally, we would want this to bind to the element
's dragend
, but since clientY
and clientX
are missing, we bind it to the document. This just does exactly what you'd want to happen when dragend is called, except you have x/y coordinates.The formula used is:
set style to: (current position) - (original position)
That is as complicated as it gets, but to calculate and apply the style, just for the x-dimension, the code is...
var diffx = e.clientX - startx;
var rect = e.target.getBoundingClientRect();
var offset = {
left: rect.left + window.scrollX,
};
var newleft = offset.left + diffx;
e.target.style.position = 'absolute';
e.target.style.left = newleft + 'px';
Upvotes: 1
Reputation: 1270
An additional method to "niente00" code.
init : function(className){
var elements = document.getElementsByClassName(className);
for (var i = 0; i < elements.length; i++){
elements[i].onmousedown = function(){mydragg.startMoving(this,'container',event);};
elements[i].onmouseup = function(){mydragg.stopMoving('container');};
}
}
Upvotes: 0
Reputation: 2174
Is jQuery an option for you? It makes what you are doing really simple since the code already exists.
http://jqueryui.com/demos/draggable/
JavaScript Code
window.onload = addListeners;
function addListeners(){
document.getElementById('dxy').addEventListener('mousedown', mouseDown, false);
window.addEventListener('mouseup', mouseUp, false);
}
function mouseUp()
{
window.removeEventListener('mousemove', divMove, true);
}
function mouseDown(e){
window.addEventListener('mousemove', divMove, true);
}
function divMove(e){
var div = document.getElementById('dxy');
div.style.position = 'absolute';
div.style.top = e.clientY + 'px';
div.style.left = e.clientX + 'px';
}
Upvotes: 76
Reputation: 324790
Well, your movement code simplifies to:
div.style.position = "absolute";
div.style.top = e.clientY - (e.clientY - div.offsetTop) + "px";
div.style.left = e.clientX - (e.clientX - div.offsetLeft) + "px";
Basic math here - the e.clientX
and e.clientY
have absolutely no effect on the position here, so you're just taking the offsetLeft
and reassigning it to the style.left
, and the same for the top. Thus no movement whatsoever.
What you need to do is save the clientX
and clientY
when the mousedown
happens, and do the subtraction based on that.
Oh and you're also setting the event listener wrong. The way it is now, you have it run divMove
once and the return value (undefined
here) is the function attached as the listener. Instead, use function(e) {divMove(dxy,e || window.event);}
.
Upvotes: 12