Drag and Drop через события мыши
Почитал статью про реализацию Drag and Drop через события мыши (https://learn.javascript.ru/drag-and-drop-objects), Скопировал код js, но столкнулся с трудностью. Не срабатывает display:none при mouseup над droppable элементом. Поигравшись с консолью понял, что скорее всего дело в этих строчках (они идут не подряд, я их выбрал отдельно):
document.body.appendChild(avatar);
var elem = document.elementFromPoint(event.clientX, event.clientY);
return elem.closest('.droppable');
Происходит здесь следующее:
аватар добавляется в body, после чего находится самый вложеный элемент под мышью (с помощью консоли узнал, что он выдаёт draggable элемент)
далее с помощью closest ищется .droppable над которым сейчас мышка, но здесь всегда возвращается null (скорее всего из-за структуры HTML)
Если я вложу draggable не в body, а в #tour_5, то droppable ищется правильно, но тогда некорректо отображается перемещение draggable элемента из-за строчек, реализующих перемещение:
top: box.top + pageYOffset,
left: box.left + pageXOffset
Как я понял, это происходит потому что relative выступает #tour5 а position расчитывается относительно всей страницы.
Также если я уберу у #tour_5 relative, то droppable перестанет находиться (выдаёт null)
Я вижу здесь 2 варианта решения:
- Реализовать перемещение не относительно страницы, а относительно #tour_5
- реализовать правильный выбор droppable элемента даже когда перемещаю draggable в body
Помогите разобраться как это корректо реализовать, пожалуйста
var DragManager = new function() {
/**
* составной объект для хранения информации о переносе:
* {
* elem - элемент, на котором была зажата мышь
* avatar - аватар
* downX/downY - координаты, на которых был mousedown
* shiftX/shiftY - относительный сдвиг курсора от угла элемента
* }
*/
var dragObject = {};
var self = this;
function onMouseDown(e) {
if (e.which != 1) return;
var elem = e.target.closest('.draggable');
if (!elem) return;
dragObject.elem = elem;
// запомним, что элемент нажат на текущих координатах pageX/pageY
dragObject.downX = e.pageX;
dragObject.downY = e.pageY;
return false;
}
function onMouseMove(e) {
if (!dragObject.elem) return; // элемент не зажат
if (!dragObject.avatar) { // если перенос не начат...
var moveX = e.pageX - dragObject.downX;
var moveY = e.pageY - dragObject.downY;
// если мышь передвинулась в нажатом состоянии недостаточно далеко
if (Math.abs(moveX) < 3 && Math.abs(moveY) < 3) {
return;
}
// начинаем перенос
dragObject.avatar = createAvatar(e); // создать аватар
if (!dragObject.avatar) { // отмена переноса, нельзя "захватить" за эту часть элемента
dragObject = {};
return;
}
// аватар создан успешно
// создать вспомогательные свойства shiftX/shiftY
var coords = getCoords(dragObject.avatar);
dragObject.shiftX = dragObject.downX - coords.left;
dragObject.shiftY = dragObject.downY - coords.top;
startDrag(e); // отобразить начало переноса
}
// отобразить перенос объекта при каждом движении мыши
dragObject.avatar.style.left = e.pageX - dragObject.shiftX + 'px';
dragObject.avatar.style.top = e.pageY - dragObject.shiftY + 'px';
return false;
}
function onMouseUp(e) {
if (dragObject.avatar) { // если перенос идет
finishDrag(e);
}
// перенос либо не начинался, либо завершился
// в любом случае очистим "состояние переноса" dragObject
dragObject = {};
}
function finishDrag(e) {
var dropElem = findDroppable(e);
if (!dropElem) {
self.onDragCancel(dragObject);
} else {
self.onDragEnd(dragObject, dropElem);
}
}
function createAvatar(e) {
// запомнить старые свойства, чтобы вернуться к ним при отмене переноса
var avatar = dragObject.elem;
var old = {
parent: avatar.parentNode,
nextSibling: avatar.nextSibling,
position: avatar.position || '',
left: avatar.left || '',
top: avatar.top || '',
zIndex: avatar.zIndex || ''
};
// функция для отмены переноса
avatar.rollback = function() {
old.parent.insertBefore(avatar, old.nextSibling);
avatar.style.position = old.position;
avatar.style.left = old.left;
avatar.style.top = old.top;
avatar.style.zIndex = old.zIndex
};
return avatar;
}
function startDrag(e) {
var avatar = dragObject.avatar;
// инициировать начало переноса
document.getElementById('tour_5').appendChild(avatar);
avatar.style.zIndex = 9999;
avatar.style.position = 'absolute';
}
function findDroppable(event) {
// спрячем переносимый элемент
dragObject.avatar.hidden = true;
// получить самый вложенный элемент под курсором мыши
var elem = document.elementFromPoint(event.clientX, event.clientY);
// показать переносимый элемент обратно
dragObject.avatar.hidden = false;
if (elem == null) {
// такое возможно, если курсор мыши "вылетел" за границу окна
return null;
}
console.log(elem);
return elem.closest('.droppable');
}
document.onmousemove = onMouseMove;
document.onmouseup = onMouseUp;
document.onmousedown = onMouseDown;
this.onDragEnd = function(dragObject, dropElem) {};
this.onDragCancel = function(dragObject) {};
};
function getCoords(elem) { // кроме IE8-
var box = elem.getBoundingClientRect();
return {
top: box.top + pageYOffset,
left: box.left + pageXOffset
};
<div class="tour" id="tour_5">
<div class="blocks_t5">
<div class="block_t5">
<span class="block_t5_text">Гора, высшая точка хребта Герцена и Краснодарского края. Высота 3 345,9 м.</span>
<div class="ans_t5 droppable"></div>
</div>
<div class="block_t5">
<span class="block_t5_text">Озеро расположено в 8 км от Темрюка. Вода в нем<br>соленая, а знаменито оно запасами лечебной грязи.</span>
<div class="ans_t5 droppable"></div>
</div>
<div class="block_t5">
<span class="block_t5_text">Гора, высшая точка хребта Герцена и Краснодарского края. Высота 3 345,9 м.</span>
<div class="ans_t5 droppable"></div>
</div>
<div class="block_t5">
<span class="block_t5_text">Озеро расположено в 8 км от Темрюка. Вода в нем<br>соленая, а знаменито оно запасами лечебной грязи.</span>
<div class="ans_t5 droppable"></div>
</div>
<div class="block_t5">
<span class="block_t5_text">Гора, высшая точка хребта Герцена и Краснодарского края. Высота 3 345,9 м.</span>
<div class="ans_t5 droppable"></div>
</div>
<div class="block_t5">
<span class="block_t5_text">Озеро расположено в 8 км от Темрюка. Вода в нем<br>соленая, а знаменито оно запасами лечебной грязи.</span>
<div class="ans_t5 droppable"></div>
</div>
<div class="block_t5">
<span class="block_t5_text">Гора, высшая точка хребта Герцена и Краснодарского края. Высота 3 345,9 м.</span>
<div class="ans_t5 droppable"></div>
</div>
<div class="block_t5">
<span class="block_t5_text">Озеро расположено в 8 км от Темрюка. Вода в нем<br>соленая, а знаменито оно запасами лечебной грязи.</span>
<div class="ans_t5 droppable"></div>
</div>
</div>
<div class="static_t5">
<span class="var_t5 draggable" id="var_t5_1">В Черном море</span>
<span class="var_t5 draggable" id="var_t5_2">Всадник</span>
<span class="var_t5 draggable" id="var_t5_3"></span>
<span class="var_t5 draggable" id="var_t5_4"></span>
<span class="var_t5 draggable" id="var_t5_5"></span>
<span class="var_t5 draggable" id="var_t5_6"></span>
<span class="var_t5 draggable" id="var_t5_7"></span>
<span class="var_t5 draggable" id="var_t5_8"></span>
</div>
<button class="next_tour" onclick="go_to_tour_6()">
<h5>далее</h5>
</button>
</div>