0

Based on javascript use drag to move element The objective is to adjust cursor position inside the square whenever dragged from/to anywhere. The below is a modified version in which I tried to eliminate having to save x and y offsets as variables for further reuse. Instead, I'm intending to replace the suggested method with $('.target').offset() top and left offset values.

 (function () {
    let x = 0;
    let y = 0;
    let target = document.querySelector('.target');
    let mouseDown = false;
    target.addEventListener(
        'mousedown',
        function (e) {
            mouseDown = true;
            target.style.position = 'relative';
            x = target.offsetLeft - e.clientX;
            y = target.offsetTop - e.clientY;
        }
    );
    document.addEventListener(
        'mouseup',
        function () {
            mouseDown = false;
        }
    );

    document.addEventListener(
        'mousemove',
        function (e) {
            event.preventDefault();
            const offset = $(target).offset();
            const offsetLeft = offset.left;
            const offsetTop = offset.top;
            if (mouseDown) {
                console.log(
                    e.clientX + x,
                    offsetLeft,
                    e.clientY + y,
                    offsetTop
                );
                target.style.left = e.clientX + x + 'px';
                target.style.top = e.clientY + y + 'px';
                // target.style.left = offsetLeft + 'px'; // comment out for issue
                // target.style.top = offsetTop + 'px';
            }
        }
    );
})();
 

.target {
    width: 100px;
    height: 100px;
    background-color: #0000FF;
}
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<div class="target"></div>

As shown in the snippet, the console is showing a comparison between suggested solution x, x, suggested solution y, and y respectively. The offsets are identical or close depending on how fast the square is dragged:

61 69 19 27
62 69 19 27
62 70 19 27
63 70 19 27
63 71 19 27
64 71 19 27
64 72 19 27
    ...

which implies it should work if used for assignment. However, when these 2 lines:

target.style.left = e.clientX + x + 'px';
target.style.top = e.clientY + y + 'px';

are replaced with the commented out ones:

// target.style.left = offsetLeft + 'px';
// target.style.top = offsetTop + 'px';

The offsets calculated using $('.target').offsets() keep infinitely increasing:

212 2408 424 2408
172 2416 423 2416
146 2424 418 2424
133 2432 409 2432
127 2440 401 2440
       ...

Is there something I'm missing here? Why the issue only happens when the offset values are used?

nlblack323
  • 155
  • 1
  • 10
  • The first mousemouse call changes the offset. Then when you call your mousemove handler the second time, it uses the values after the first mousemove call (and not the values from the first mousedown). The third time, it uses the values after the second mousemove call, and so on. The mousedown handler is called repeatedly in a short period of time, so the offsets can keep increasing very quickly. – qrsngky Apr 08 '23 at 04:32
  • why the values are only wrong when assigned? – nlblack323 Apr 08 '23 at 04:33
  • what would be the fix in this case? – nlblack323 Apr 08 '23 at 04:38

1 Answers1

1

The body of the html element has 8px margin by default in Chrome. So, in a very simple case where there is only one element in the body, and the target has position: relative;, and its left and top properties are in pixels, the result of $(target).offset().top is target.style.top plus 8px and $(target).offset().left is target.style.left plus 8px.

The 8 can be obtained programmatically from $(target).offset().left - $('html').offset().left and $(target).offset().top - $('html').offset().top.

You can verify that it's constant 8px by replacing the console.log(...) in your mousemove handler in your original code with console.log(offsetTop - parseFloat(target.style.top) , offsetTop - parseFloat(target.style.left)) and uncommenting the other part.

So, after running target.style.left = $(target).offset().left + 'px' will result in "previous value + 8px". A similar thing happens for the .top property. Every time you use mousemove is called, the top/left will increase by 8px. Since mousemove is called repeatedly in a short period of time, this can add up quickly.

If there are other elements, a similar phenomenon occurs, i.e. it will still "infinitely increasing after reassigned" (but the change each time the handler is called may not be 8 anymore) unless the change is exactly 0.


I don't know if there is a "quick fix" for your problem, but since you want to drag the item, I can write a simple code for that. One point to make is to save some data each mousedown, then work out the new position from the old data in mousemove (to avoid unintended repeated addition to x/y). Here, I use x = oldX + ... (oldX is the value from mousedown) instead of x = x + ... (using the value of x from previous mousemove in the right-hand side, which can add up quickly).

(function () {
    let x = 0;
    let y = 0;
    let oldX = 0;
    let oldY = 0;
    let pressX = 0;
    let pressY = 0;

    let target = document.querySelector('.target');
    let mouseDown = false;
    target.addEventListener(
        'mousedown',
        function (e) {
            mouseDown = true;
            target.style.position = 'relative';
            pressX = e.clientX;
            pressY = e.clientY;
            oldX = x;
            oldY = y;
        }
    );
    document.addEventListener(
        'mouseup',
        function () {
            mouseDown = false;
        }
    );

    document.addEventListener(
        'mousemove',
        function (e) {
            event.preventDefault();
            const offset = $(target).offset();
            const offsetLeft = offset.left;
            const offsetTop = offset.top;
            if (mouseDown) {
               x = oldX + e.clientX - pressX
               y = oldY + e.clientY - pressY 
               target.style.left = x + 'px';
               target.style.top = y + 'px';
            }
        }
    );
})();
.target {
    width: 100px;
    height: 100px;
    background-color: #0000FF;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<div class="target"></div>
qrsngky
  • 2,263
  • 2
  • 13
  • 10
  • That pretty much explains the `8px` increments. I initially intended to shorten and simplify the code by eliminating the x, y global variables and the associated logic. This just adds more variables and more overhead. If a shorter solution doesn't exist then perhaps I'll have to stick to the initial approach. – nlblack323 Apr 08 '23 at 05:12
  • @nlblack323 your initial approach will cause a sudden change the first time you try to drag. Subtracting by 8 manually like `target.style.left = e.clientX - 8 + x + 'px';` and `target.style.top = e.clientY - 8 + y + 'px'` seems to fix that. I think there should be a way to automatically detect that "8" (note that the number may be different in other cases). – qrsngky Apr 08 '23 at 05:18
  • By the initial approach I was referring to the one suggested in the other [question](https://stackoverflow.com/questions/45831399/javascript-use-drag-to-move-element). The whole purpose of the modifications I made is to get rid of `x` and `y` variables entirely. Using `e.clientX - 8 + x` implies I will still have to keep `x` and `y` which is what I'm trying to avoid. – nlblack323 Apr 08 '23 at 05:26
  • @nlblack323 The codepen link in the accepted answer of the other question looks good. Is there an issue with that? – qrsngky Apr 08 '23 at 05:41
  • No, there is not, I just wanted to simplify or shorten it is all. – nlblack323 Apr 08 '23 at 05:44