#javascript #html #dom
#javascript #HTML #dom
Вопрос:
Я написал этот код, чтобы сделать любой элемент с классом 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- Я хочу создать панель задач Windows, в которой элементы можно перемещать, а другие элементы перемещаться вправо или влево в зависимости от того, куда к нему приближается перетаскиваемый элемент.
Я хочу, чтобы перетаскиваемые элементы привязывались к сетке аналогично тому, что происходит, когда вы перетаскиваете значок на панели задач Windows или вкладку на другой вкладке в вашем браузере.
Ниже приведена моя попытка. Я убрал перемещение по вертикальной оси и поддержку касания, чтобы сделать код более читаемым. Привязка работает нормально, но элемент, на который наведен указатель мыши, не перемещается в другое пространство.
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>
Кроме того, я думаю, что проверка того, когда мышь пересекла половину ширины div
, во время перетаскивания элемента, div
должна переместить либо одну единицу влево или вправо в зависимости от того, находится ли элемент слева или справа от перетаскиваемого элемента. С проверкой проблем нет. Мы можем просто сравнить величины элементов offsetLeft
. Но как мне заставить элемент перемещаться?
Пожалуйста, попробуйте ответить на ванильном javascript.
Изменения: 1. Обновленный код 2. Обновленный заголовок 3. Обновлен tl; dr и изменен заголовок 3. Добавлено больше тегов
Комментарии:
1. На самом деле у вас есть расширение / фреймворк для этого, он называется sortableJS. Не такой уж большой (около 30 КБ). Почему бы вам не попробовать это? Вы даже можете перетаскивать из одного списка в другой, это действительно здорово. Дайте мне знать, если это поможет. Плюс это написано на чистом js (я думаю, не уверен)
2. Я видел расширение. Но, как уже упоминалось в вопросе, я хочу сделать это на чистом JavaScript. Хотя одна вещь, которую я узнал из расширения, заключается в том, что использование API перетаскивания HTML может быть более разумной идеей.
3. Зачем изобретать велосипед, человек? Конечно, вы можете перестроить javascript на C, но зачем? Если кто-то это сделал, почему бы не использовать это?
4. Как вы упомянули, библиотека превышает 30 КБ. Мне не нужны все его отдельные функции. Я уверен, что смогу написать код, который удовлетворяет вопросу размером менее 1-3 КБ. Я думаю, что я близок к завершению. В течение следующих 24 часов я опубликую ответ. Я надеюсь, что вы будете следить за развитием этого вопроса с большим интересом.
5. Я сделаю, потому что я хотел сделать что-то такое же, и у меня сдали нервы, хахаха, а затем я, наконец, просто остановился на расширении. Удачи!
Ответ №1:
Способ 1: Чистый JS-код, продолжающий вопрос
Следующий код соответствует требованию вопроса. Я добавил больше элементов в исходный код (больше элементов = больше удовольствия). Я также добавил комментарии в код. Я уверен, что вы поймете код, просто прочитав его. В любом случае, вот краткое объяснение.
Привязка target
к сетке
Сначала нам нужно привязать элементы к сетке. Сначала мы делаем привязку target
, смотрите следующий раздел для привязки других элементов. В строке указана ширина сетки в пикселях let grid = ...
. Для плавной анимации мы хотим, чтобы target
элемент щелкал при завершении перетаскивания, а не во время перетаскивания. Эта строка кода в функции endDrag
привязывает target
к сетке, когда перетаскивание завершено.
target.style.left = target.oldLeft Math.round(target.distX / grid) * grid "px";
Перемещение других элементов на основе target
Нам также нужно переместить элемент, положение которого target
занимает. В противном случае они бы перекрывались. Функция moveElementAt
выполняет эту работу. Это то, что происходит в moveElementAt
.
- Мы называем любой элемент, который сталкивается с
target
верхним левым угломelementAt
. Свойство JavaScript.elementFromPoint
выполняет проверку. - При проверке мы исключаем
target
сам элемент, установив для его CSSpointer-events
значениеnone
. Мы ничего не делаем, еслиelementAt
является родительским элементом. - Мы проверяем, приближается ли элемент
elementAt
слева или справа по некоторой математической логике.- Если
target
элемент поступает справа, онelementAt
перемещаетgrid
элементы вправо. - Если
target
элемент исходит слева, онelementAt
перемещаетgrid
элементы влево.
- Если
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>
Код немного сбивается при необычно быстром перетаскивании. Я исправлю сбой позже, хотя.
Способ 2: Использование API перетаскивания / библиотеки
Джон Незбит уведомил меня, что существует библиотека под названием SortableJs, специально предназначенная для этой цели. Вопрос задан для решения на чистом JS. Итак, я закодировал метод, который использовал API перетаскивания. Вот фрагмент.
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>
Библиотека использует API перетаскивания HTML, который не дает мне желаемого результата. Но вы обязательно должны это проверить. Кроме того, ознакомьтесь с этой превосходной статьей от автора библиотеки, в которой объясняется (с использованием чистого js), как они создали эту библиотеку. Хотя я этим не пользовался, я уверен, что кому-то это поможет.