Основываясь на предложениях здесь, если для перемещения требуется только один элемент, возможно, ниже приведен простой способ сделать это:
(function () {
let offset = [0, 0];
let target = document.querySelector('.target');
let isDown = false;
target.addEventListener('mousedown', function(e) {
isDown = true;
target.style.position = 'relative';
offset = [
target.offsetLeft - e.clientX,
target.offsetTop - e.clientY
];
}, true);
document.addEventListener('mouseup', function() {
isDown = false;
}, true);
document.addEventListener('mousemove', function(e) {
event.preventDefault();
if (isDown) {
target.style.left = (e.clientX + offset[0]) + 'px';
target.style.top = (e.clientY + offset[1]) + 'px';
}
}, true);
})();
.target {
width: 100px;
height: 100px;
background-color: #0000FF;
}
<div class = "target"></div>
Что делать, если есть 2 div? а если 1000? тогда подход, описанный выше, будет не таким удобным, потому что, если у нас есть 1000 элементов div, нам нужно еще 1000 прослушивателей событий, отслеживающих 1000 offset
, а также isDown
переменных. Во время более ранней попытки я попытался избавиться от логики offset
и isDown
с помощью $('.target').offset()
и сделать расчет внутри обработчика mousemove
, но мне это не удалось. Вот попытка сделать то, что я описал ранее, и это то, что я пытаюсь улучшить:
(function() {
let targetsData = {};
let targets = document.querySelectorAll('.target');
Array.prototype.map.call(targets, (target) => {
targetsData[target] = { 'mousedown': false, 'offset': [0, 0] };
target.addEventListener('mousedown', (e) => {
target.style.position = 'relative';
targetsData[target]['mousedown'] = true;
targetsData[target]['offset'] = [
target.offsetLeft - e.clientX,
target.offsetTop - e.clientY
];
});
target.addEventListener('mouseup', (e) => {
targetsData[target]['mousedown'] = false;
});
target.addEventListener('mousemove', (e) => {
e.preventDefault();
if (targetsData[target]['mousedown']) {
let offset = targetsData[target]['offset']
target.style.left = (e.clientX + offset[0]) + 'px';
target.style.top = (e.clientY + offset[1]) + 'px';
}
});
});
})();
.target {
width: 100px;
height: 100px;
background-color: #0000FF;
margin-bottom: 5px
}
<div class = "target"></div>
<div class = "target"></div>
<div class = "target"></div>
Работает только первый квадрат сверху, и если его быстро перетащить, начинают происходить какие-то странные эффекты, а побочные эффекты других кажутся хуже:
Что тут происходит? Как можно эффективно заставить все квадраты двигаться правильно?
Исходя из фона python, это работает в python, поэтому я предположил, что это будет с использованием простых объектов. Означает ли это, что при преобразовании в строку 3 будут иметь одну и ту же строку и, следовательно, на самом деле будет существовать только один ключ поиска? Что касается странных прыжков, почему они происходят только или сильнее в последних 2 клетках?
Объект Map
представляет собой более общий поиск в стиле dict
Python, тогда как ключи объекта JS представляют собой строки, числа и символы, хотя, если вы используете что-либо еще в качестве ключа, он будет преобразован в строку; в этом случае строковое представление может зависеть от браузера, но похоже на [object HTMLDivElement]
во всех трех случаях. Скачки случаются только сильнее с двумя последними квадратами, потому что начальные offsetTop
и offsetLeft
первого квадрата равны 0, поскольку он начинается в верхнем левом углу страницы.
Есть много возможностей — на самом деле не так уж сложно иметь много обработчиков событий, хотя управление ими может быть проблематичным, если вы не используете фреймворк.
Довольно простой вариант — использовать один обработчик mousedown
, зарегистрированный в документе, и проверить в обработчике событий, является ли цель события (или любой из его предков) одним из перетаскиваемых элементов:
Примечание: первоначальная версия не обрабатывала повторное перетаскивание корректно.
(function() {
let offset = [0, 0];
let target = null;
function targetIfMatches(e, selector) {
let maybeTarget = e.target;
while (maybeTarget && maybeTarget.matches) {
if (maybeTarget.matches(selector))
return maybeTarget;
maybeTarget = maybeTarget.parentNode;
}
return null;
}
function handleMouseDown(e) {
// Check event target and ancestors to see if any of them are a target
target = targetIfMatches(e, ".target");
if (target) {
target.style.position = 'relative';
offset = [
[e.pageX - parseFloat(target.style.left || 0)],
[e.pageY - parseFloat(target.style.top || 0)]
];
}
}
function handleMouseMove(e) {
e.preventDefault();
if (target) {
target.style.left = (e.pageX - offset[0]) + 'px';
target.style.top = (e.pageY - offset[1]) + 'px';
}
}
function handleMouseUp(e) {
target = null;
}
document.addEventListener('mousedown', handleMouseDown, true);
document.addEventListener('mouseup', handleMouseUp, true);
document.addEventListener('mousemove', handleMouseMove, true);
})();
.target {
width: 100px;
height: 100px;
background-color: #0000FF;
text-align: center;
font: 20px/100px sans-serif;
color: white;
cursor: move;
margin: 5px;
}
<div class = "target">1</div>
<div class = "target">2</div>
<div class = "target">3</div>
<div class = "target">4</div>
<div class = "target">5</div>
<div class = "target">6</div>
Это решение ломается после нескольких попыток переместить один div
После редактирования все квадраты двигаются, как и ожидалось, но таким образом 2 прослушивателя событий возрождаются при любом нажатии мыши. Хотя это не повлияет на производительность, это расточительно, менее читабельно и немного запутанно. Как вы думаете, есть ли более чистый способ добиться того же?
Все три обработчика событий подключены при начальной настройке. Обработчики mouseup
и mousemove
не возрождаются на mousedown
.
Ага, только что заметил, что их нет, наверное форматирование меня смутило.
Прошу прощения, я использовал код из вашего фрагмента и чрезмерно усердную tidy
функциональность, предоставляемую интерфейсом фрагмента.
Также есть необработанное исключение, возникающее при mousdowns, где нет цели. Uncaught TypeError: maybeTarget.matches is not a function at HTMLDocument.<anonymous> (...)
Переформатирован и настроен для обработки всех случаев, которые я могу придумать. Пожалуйста, не стесняйтесь изменять его в соответствии с вашими потребностями.
Вам не нужен массив смещений. Глобальный будет работать нормально. В любом случае вы можете хранить отдельные значения внутри самого элемента. Это сэкономит немного памяти и времени. Мне также не нравится идея добавления смещения. Вместо этого вычтите смещение мыши из текущей позиции мыши. Это смещение будет равно смещению div от момента первой загрузки DOM плюс смещение позиции мыши от текущего положения div.
(function() {
let targets = document.querySelectorAll('.target');
let offsetX;
let offsetY;
Array.prototype.map.call(targets, (target) => {
target.isMouseDown = false;
target.initialOffsetLeft = target.offsetLeft;
target.initialOffsetTop = target.offsetTop;
target.addEventListener('mousedown', (e) => {
target.style.position = 'relative';
target.isMouseDown = true;
offsetX = target.initialOffsetLeft + e.offsetX;
offsetY = target.initialOffsetTop + e.offsetY;
});
document.addEventListener('mouseup', (e) => {
target.isMouseDown = false;
});
document.addEventListener('mousemove', (e) => {
e.preventDefault();
if (target.isMouseDown) {
target.style.left = e.pageX - offsetX + 'px';
target.style.top = e.pageY - offsetY + 'px';
}
});
});
})();
.target {
width: 300px;
height: 300px;
background-color: #0000FF;
margin-bottom: 5px;
}
<div class = "target"></div>
<div class = "target"></div>
<div class = "target"></div>
Одно из основных различий между нашими кодами заключается в том, где мы размещаем обработчики событий. Я размещаю свои mouseup
и mousemove
события на document
, а не на самом элементе. Таким образом, не имеет значения, где в документе находится мышь. Ваш код требует, чтобы мышь находилась над div
, чтобы обрабатывать движения мыши или mouseup
. И тут разница в вылете. Сначала легкая часть, я использую event.pageXY
вместо event.clientXY
, чтобы учесть прокрутку. Затем я сохранил начальное смещение каждого div
внутри самого div
. Я добавляю начальное смещение к смещению мыши относительно div
для каждого mousedown
, чтобы установить offsetXY
. Наконец, я вычитаю смещение мыши из положения мыши, чтобы установить положение div
.
Примечание. Первоначально я обозначил offsetXY
как initialXY
. Я понял, что термин initial
неверно представляет переменную. Просто изменение имен переменных.
третий квадрат имеет такие же странные побочные эффекты
@ nlblack323 Да, это происходит при прокрутке. Будут ли ваши пользователи прокручивать? Я могу добавить поддержку прокрутки
Я собираюсь использовать его для настройки эффекта перетаскивания, потому что все существующие известные решения выглядят ужасно или обременительны для использования или импорта. Так что да, это должно работать при перетаскивании из любого места в любое место. И что не так с тем, что я сделал? почему он не работает должным образом?
@ nlblack323 Я добавил поддержку прокрутки, используя pageX и pageY вместо clientXY. И, честно говоря, я не совсем уверен, что именно делает ваш код. Я посмотрю немного больше.
да, ваш фрагмент в настоящее время работает, как и ожидалось.
@ nlblack323 Я добавил огромное объяснение. По сути, вам нужно сохранить смещение div
при загрузке DOM и никогда не обновлять его. Ваша самая большая ошибка — обновлять его каждый раз.
Итак, здесь падает пара вещей. Вы не можете использовать свой объект
target
в качестве ключа поиска в простом объекте — он будет преобразован в строку (вместо этого см. Карта). Странные скачки для ваших второго и третьего элементов div связаны с тем, что вы добавляете ихoffset
позиции от mousedown к их относительной позиции при mousemove, поэтому они будут постепенно перемещаться дальше влево и вниз.