# Drag'n'Drop with mouse events Drag'n'Drop is a great interface solution. Taking something and dragging and dropping it is a clear and simple way to do many things, from copying and moving documents (as in file managers) to ordering (dropping items into a cart). In the modern HTML standard there's a [section about Drag and Drop](https://html.spec.whatwg.org/multipage/interaction.html#dnd) with special events such as `dragstart`, `dragend`, and so on. These events allow us to support special kinds of drag'n'drop, such as handling dragging a file from OS file-manager and dropping it into the browser window. Then JavaScript can access the contents of such files. But native Drag Events also have limitations. For instance, we can't prevent dragging from a certain area. Also we can't make the dragging "horizontal" or "vertical" only. And there are many other drag'n'drop tasks that can't be done using them. Also, mobile device support for such events is very weak. So here we'll see how to implement Drag'n'Drop using mouse events. ## Drag'n'Drop algorithm The basic Drag'n'Drop algorithm looks like this: 1. On `mousedown` - prepare the element for moving, if needed (maybe create a clone of it, add a class to it or whatever). 2. Then on `mousemove` move it by changing `left/top` with `position:absolute`. 3. On `mouseup` - perform all actions related to finishing the drag'n'drop. These are the basics. Later we'll see how to add other features, such as highlighting current underlying elements while we drag over them. Here's the implementation of dragging a ball: ```js ball.onmousedown = function(event) { // (1) 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); // 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'; } // move our absolutely positioned ball under the pointer moveAt(event.pageX, event.pageY); function onMouseMove(event) { moveAt(event.pageX, event.pageY); } // (2) move the ball on mousemove document.addEventListener('mousemove', onMouseMove); // (3) 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 dragging its "clone". ```online Here's an example in action: [iframe src="ball" height=230] Try to drag'n'drop with the mouse and you'll see such behavior. ``` That's because the browser has its own drag'n'drop support for images and some other elements. It 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 pointer 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 moved so that its center is 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, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. 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 pointer should remain over the edge while dragging. ![](ball_shift.svg) Let's update our algorithm: 1. When a visitor presses the button (`mousedown`) - remember the distance from the pointer to the left-upper corner of the ball in variables `shiftX/shiftY`. We'll 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; ``` 2. Then while dragging we position the ball on the same shift relative to the pointer, like this: ```js // onmousemove // ball has position:absolute 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); // moves the ball at (pageX, pageY) coordinates // taking initial shifts into account function moveAt(pageX, pageY) { ball.style.left = pageX - *!*shiftX*/!* + 'px'; ball.style.top = pageY - *!*shiftY*/!* + 'px'; } function onMouseMove(event) { moveAt(event.pageX, event.pageY); } // move the ball on mousemove document.addEventListener('mousemove', onMouseMove); // 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 `