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