# Drag'n'Drop with mouse events Drag'n'Drop is a great interface solution. Taking something, dragging and dropping is a clear and simple way to do many things, from copying and moving (see file managers) to ordering (drop into cart). In the modern HTML standard there's a [section about Drag Events](https://html.spec.whatwg.org/multipage/interaction.html#dnd). They are interesting, because they allow to solve simple tasks easily, and also allow to handle drag'n'drop of "external" files into the browser. So we can take a file in the OS file-manager and drop it into the browser window. Then JavaScript gains access to its contents. But native Drag Events also have limitations. For instance, we can limit dragging by a certain area. Also we can't make it "horizontal" or "vertical" only. There are other drag'n'drop tasks that can't be implemented using that API. So here we'll see how to implement Drag'n'Drop using mouse events. Not that hard either. ## Drag'n'Drop algorithm The basic Drag'n'Drop algorithm looks like this: 1. Catch `mousedown` on a draggable element. 2. Prepare the element to moving (maybe create a copy of it or whatever). 3. Then on `mousemove` move it by changing `left/top` and `position:absolute`. 4. On `mouseup` (button release) -- perform all actions related to a finished Drag'n'Drop. These are the basics. We can extend it, for instance, by highlighting droppable (available for the drop) elements when hovering over them. Here's the algorithm for drag'n'drop of a ball: ```js ball.onmousedown = function(event) { // (1) start the process // (2) prepare to moving: make absolute and on top by z-index ball.style.position = 'absolute'; ball.style.zIndex = 1000; // move it out of any current parents directly into body // to make it positioned relative to the body document.body.append(ball); // ...and put that absolutely positioned ball under the cursor moveAt(event.pageX, event.pageY); // centers the ball at (pageX, pageY) coordinates function moveAt(pageX, pageY) { ball.style.left = pageX - ball.offsetWidth / 2 + 'px'; ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; } function onMouseMove(event) { moveAt(event.pageX, event.pageY); } // (3) move the ball on mousemove document.addEventListener('mousemove', onMouseMove); // (4) drop the ball, remove unneeded handlers ball.onmouseup = function() { document.removeEventListener('mousemove', onMouseMove); ball.onmouseup = null; }; }; ``` If we run the code, we can notice something strange. On the beginning of the drag'n'drop, the ball "forks": we start to dragging it's "clone". ```online Here's an example in action: [iframe src="ball" height=230] Try to drag'n'drop the mouse and you'll see the strange behavior. ``` That's because the browser has its own Drag'n'Drop for images and some other elements that runs automatically and conflicts with ours. To disable it: ```js ball.ondragstart = function() { return false; }; ``` Now everything will be all right. ```online In action: [iframe src="ball2" height=230] ``` Another important aspect -- we track `mousemove` on `document`, not on `ball`. From the first sight it may seem that the mouse is always over the ball, and we can put `mousemove` on it. But as we remember, `mousemove` triggers often, but not for every pixel. So after swift move the cursor can jump from the ball somewhere in the middle of document (or even outside of the window). So we should listen on `document` to catch it. ## Correct positioning In the examples above the ball is always centered under the pointer: ```js ball.style.left = pageX - ball.offsetWidth / 2 + 'px'; ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; ``` Not bad, but there's a side-effect. To initiate the drag'n'drop can we `mousedown` anywhere on the ball. If do it at the edge, then the ball suddenly "jumps" to become centered. It would be better if we keep the initial shift of the element relative to the pointer. For instance, if we start dragging by the edge of the ball, then the cursor should remain over the edge while dragging. ![](ball_shift.png) 1. When a visitor presses the button (`mousedown`) -- we can remember the distance from the cursor to the left-upper corner of the ball in variables `shiftX/shiftY`. We should keep that distance while dragging. To get these shifts we can substract the coordinates: ```js // onmousedown let shiftX = event.clientX - ball.getBoundingClientRect().left; let shiftY = event.clientY - ball.getBoundingClientRect().top; ``` Please note that there's no method to get document-relative coordinates in JavaScript, so we use window-relative coordinates here. 2. Then while dragging we position the ball on the same shift relative to the pointer, like this: ```js // onmousemove // ball has position:absoute ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` The final code with better positioning: ```js ball.onmousedown = function(event) { *!* let shiftX = event.clientX - ball.getBoundingClientRect().left; let shiftY = event.clientY - ball.getBoundingClientRect().top; */!* ball.style.position = 'absolute'; ball.style.zIndex = 1000; document.body.append(ball); moveAt(event.pageX, event.pageY); // centers the ball at (pageX, pageY) coordinates function moveAt(pageX, pageY) { ball.style.left = pageX - *!*shiftX*/!* + 'px'; ball.style.top = pageY - *!*shiftY*/!* + 'px'; } function onMouseMove(event) { moveAt(event.pageX, event.pageY); } // (3) move the ball on mousemove document.addEventListener('mousemove', onMouseMove); // (4) drop the ball, remove unneeded handlers ball.onmouseup = function() { document.removeEventListener('mousemove', onMouseMove); ball.onmouseup = null; }; }; ball.ondragstart = function() { return false; }; ``` ```online In action (inside `