Based on the suggestions here, if only one element is required to move, the below is possibly a simple way to do it:
(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>
What if there are 2 divs? what if there’s 1000? that’s when the approach above won’t be as convenient because if we have 1000 divs, then we need another 1000 event listeners keeping track of 1000 offset
as well as isDown
variables. During an earlier attempt, I tried to get rid of the offset
and isDown
logic using $('.target').offset()
and make the calculation happen inside mousemove
handler, but I failed. Here’s an attempt to do what I described earlier which is what I’m trying to improve:
(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>
Only the first square from the top works and if dragged quickly, some weird effects start to happen and the other ones’ side effects seem to be worse:
What’s happening here? What would be a way to efficiently make all of the squares move properly?
2
Answers
There’s a lot of possibilities – it’s not really such a big deal to have many event handlers, although managing them can be problematic if you’re not using a framework.
A fairly straightforward option is actually to use a single
mousedown
handler registered on the document, and check in the event handler if the event target (or any of its ancestors) is one of the draggable elements:Note: initial version did not handle re-dragging gracefully
You don’t need an array of offsets. A global one will work fine. Either way, you can store individual values within the element itself. This will save some memory and time. I also dislike the idea of adding an offset. Instead, subtract the offset of the mouse from the current mouse position. This offset will be equal to the offset of the div from when the DOM first loaded plus the offset of the mouse position from where the div currently is.
One major difference between our codes is where we place our event handlers. I place my
mouseup
andmousemove
events ondocument
rather than the element itself. This way, it doesn’t matter where in the document the mouse is. Your code requires the mouse to be over thediv
in order to handle mouse movements ormouseup
. And then there’s the difference in offset. Easy part first, I useevent.pageXY
instead ofevent.clientXY
in order to account for the scroll. Next, I stored the initial offset of eachdiv
within thediv
itself. I add the initial offset to mouse’s offset relative to thediv
for everymousedown
to setoffsetXY
. Finally, I subtract the mouse’s offset from the mouse’s position to set the position of thediv
.Note: I originally labeled
offsetXY
asinitialXY
. I realized that the terminitial
misrepresents the variable. Just a change in variable names.