I wrote this code to make any element with class draggable draggable.
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;
e.clientX ?
(target.oldX = e.clientX,
target.oldY = e.clientY) :
(target.oldX = e.touches[0].clientX,
target.oldY = e.touches[0].clientY)
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;
document.addEventListener('touchmove', dr, {passive: false})
function dr(event) {
event.preventDefault();
if (!target.moving) {
return;
}
event.clientX ?
(target.distX = event.clientX - target.oldX,
target.distY = event.clientY - target.oldY) :
(target.distX = event.touches[0].clientX - target.oldX,
target.distY = event.touches[0].clientY - target.oldY)
target.style.left = target.oldLeft + target.distX + "px";
target.style.top = target.oldTop + target.distY + "px";
}
function endDrag() {
target.moving = false;
}
target.onmouseup = endDrag;
target.ontouchend = endDrag;
}
document.onmousedown = filter;
document.ontouchstart = filter;
div {
width: 100px;
height: 100px;
background: red;
}
<div class="draggable"></div>
tl;dr- I want to make a Windows taskbar thing where elements can be moved and the other elements move to the right or left based on where the dragged element is approaching it.
I want the draggable elements to snap to the grid similar to what happens when you drag an icon on the Windows taskbar or a tab on another tab in your browser.
Following is my attempt. I removed movement along the verticle axis and touch support to make the code more readable. The snapping is working fine but the element being hovered is not moving to the other space.
const d = document.getElementsByClassName("draggable");
let grid = 50;
for (let i = 0; i < d.length; i++) {
d[i].style.position = "relative";
d[i].onmousedown = filter;
}
function filter(e) {
let target = e.target;
target.moving = true;
target.oldX = e.clientX;
target.oldLeft = window.getComputedStyle(target).getPropertyValue('left').split('px')[0] * 1;
document.onmousemove = dr;
function dr(event) {
event.preventDefault();
if (!target.moving) {
return;
}
target.distX = event.clientX - target.oldX;
target.style.left = target.oldLeft + Math.round(target.distX / grid) * grid + 'px'
}
function endDrag() {
target.moving = false;
}
document.onmouseup = endDrag;
}
.parent {
width: 100%;
height: 100%;
background: lime;
display: flex;
align-items: center;
}
.child {
width: 50px;
height: 50px;
position: relative;
}
.one {
background: red;
}
.two {
background: blue;
}
<div class="parent">
<div class="child one draggable"></div>
<div class="child two draggable"></div>
</div>
Further, I think checking for when the mouse has crossed half of the width of a div, while an element is being dragged, the div should move either one unit left or right depending whether the element is left or right to the element being dragged. The checking part is no trouble. We can just compare the magnitudes of the elements' offsetLeft. But how do I make the element move?
Please try to answer in vanilla javascript.
Edits: 1. Updated code 2. Updated title 3. Updated tl;dr and changed title 3.Added more tagsThe following code fulfills the question's demand. I added more elements to the original code (more elements = more fun). I have also added comments in the code. I am sure you will understand the code just by reading it. Here is a brief explanation anyway.
target in a gridWe first need to snap elements in a grid. We first snap target, see the next section for snapping other elements. The grid's width in pixels is specified in the line let grid = .... For smooth animation, we want the target to snap when we end dragging, not while we are dragging. This line of code in the function endDrag snaps the target into grid when drag is over.
target.style.left = target.oldLeft + Math.round(target.distX / grid) * grid + "px";
target
We also need to move the element whose position target takes. Otherwise, they would overlap. The function moveElementAt does this job. This is what happens in moveElementAt.
target's top-left corner elementAt. The JavaScript property .elementFromPoint does the check.target itself by setting its CSS pointer-events to none. We do nothing if elementAt is the parent element.elementAt from left or right by some mathematical logic.
target is coming from the right, elementAt moves grid units towards the right.target is coming from left elementAt moves grid units left.const d = document.getElementsByClassName("draggable");
let grid = 50; //Width of one grid box
for (let i = 0; i < d.length; i++) {
d[i].style.position = "relative";
}
function filter(e) {
let target = e.target;
target.moving = true;
target.oldX = e.clientX;
target.oldLeft =
window
.getComputedStyle(target)
.getPropertyValue("left")
.split("px")[0] * 1; //Get left style as a number
document.onmousemove = dr;
function dr(event) {
event.preventDefault();
if (!target.moving) {
return;
}
target.distX = event.clientX - target.oldX;
target.style.left = target.oldLeft + target.distX + "px";
target.style.pointerEvents = "none"; //Stops target from being elementAt
moveElementAt();
}
function endDrag() {
target.moving = false;
target.style.left =
target.oldLeft + Math.round(target.distX / grid) * grid + "px";
moveElementAt(); //Do it at endDrag() also to stop elements from overlapping
target.style.pointerEvents = "auto";
}
function moveElementAt() {
let rootEl = target.parentNode;
let elementAt = document.elementFromPoint(
target.offsetLeft,
target.offsetTop //Get element at target's coordinates
);
if (elementAt === rootEl) {
return
} //Stop rootEl from moving
//Move elementAt either grid units left or right depending on which way target is approaching it from
if (target.offsetLeft - elementAt.offsetLeft * 1 <= grid / 2) //Can also compare to 0, comparing to grid/2 stops elements' position from breaking when moving very fast to some extent
{
elementAt.style.left =
window
.getComputedStyle(elementAt)
.getPropertyValue("left")
.split("px")[0] * 1 - grid + "px";
} else {
elementAt.style.left =
window
.getComputedStyle(elementAt)
.getPropertyValue("left")
.split("px")[0] * 1 + grid + "px";
}
}
document.onmouseup = endDrag;
}
document.onmousedown = filter;
.parent {
width: 100%;
height: 100%;
background: lime;
display: flex;
align-items: center;
}
.child {
width: 50px;
height: 50px;
position: relative;
}
.one {
background: red;
}
.two {
background: blue;
}
.three {
background: brown;
}
.four {
background: pink;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hello!</title>
<link rel="stylesheet" href="/style.css" />
<script src="/scriptmain.js"></script>
</head>
<body>
<div class="parent" id="parent">
<div class="child one draggable"></div>
<div class="child two draggable"></div>
<div class="child three draggable"></div>
<div class="child four draggable"></div>
</div>
</body>
</html>
The code is a bit glitchy when dragging unusually fast. I will fix the glitch later though.
Jon Nezbit notified me that there is a library called SortableJs specifically meant for this purpose. The question stated for a pure JS solution. So I coded a method that used the drag and drop API. Here is a snippet.
function sortable(rootEl) {
let dragEl;
for (let i = 0; i < rootEl.children.length; i++) {
rootEl.children[i].draggable = true;
}
rootEl.ondragstart = evt => {
dragEl = evt.target;
rootEl.addEventListener("dragover", onDragOver);
rootEl.addEventListener("dragend", onDragEnd);
};
function onDragOver(evt) {
let target = evt.target;
rootEl.insertBefore(
dragEl,
rootEl.children[0] === target ?
rootEl.children[0] :
target.nextSibling || target
);
}
function onDragEnd(evt) {
evt.preventDefault();
rootEl.removeEventListener("dragover", onDragOver);
rootEl.removeEventListener("dragend", onDragEnd);
}
}
sortable(document.getElementById("parent"))
.parent {
width: 100%;
height: 100%;
background: lime;
display: flex;
align-items: center;
}
.child {
width: 50px;
height: 50px;
position: relative;
}
.one {
background: red;
}
.two {
background: blue;
}
.three {
background: brown;
}
.four {
background: pink;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hello!</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<div class="parent" id="parent">
<div class="child one draggable"></div>
<div class="child two draggable"></div>
<div class="child three draggable"></div>
<div class="child four draggable"></div>
</div>
<script src="/script-dndmain.js"></script>
</body>
</html>
The library uses the HTML Drag and Drop API which does not give me the result as I wanted. But you should definitely check that out. Also, check out this excellent article from the author of the library which explains (with pure js) how they made that library. Although I did not use it, I am sure someone will be helped out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With