skip to Main Content

I already saw this thread, but I think my case is a bit different.

This is an example class, whose constructor receives an element as parameter, that can be moved dragging it. But it does not stop being dragged on mouse up. If I good understood what I read in the other thread, I guess I have to assign the methods to variables, but I don’t know how, as they take the event parameter:

class Example
{
    rect;

    constructor(rect)
    {
        this.rect = rect;
        this.rect.addEventListener('mousedown', event => this.onMouseDown(event));
    }

    onMouseDown(event)
    {
        document.addEventListener('mousemove', event => this.onMouseMove(event));
        document.addEventListener('mouseup', event => this.onMouseUp(event));
    }

    onMouseMove(event)
    {
        this.rect.style.left = event.clientX;
        this.rect.style.top = event.clientY;
    }

    onMouseUp(event)
    {
        document.removeEventListener('mousemove', this.onMouseMove);
        document.removeEventListener('mouseup', this.onMouseUp);
    }
}

document.addEventListener("DOMContentLoaded", function()
{
    var rect = document.getElementById('rect');
    var ex = new Example(rect);
});
#rect {
    position: absolute;
    border: 1px solid #ccc;
    background-color: #DFD;
    width: 100px;
    height: 100px;
}
<html>
    <head>
        <meta charset="utf-8"/>
        <title>Test</title>
    </head>

    <body>
        <div id="rect"></div>
    </body>
</html>

I don’t know why the snippet here does not work, I copied the code from some files, that perfectly work.

3

Answers


  1. TL;DR the easiest thing to do here is this.rect.addEventListener('mousedown', this.onMouseDown); and change your class methods to fat arrow functions onMouseDown = (event) => {...}

    The problem

    When you add an event listener, you need to keep a reference to the function you added to remove it. When you do this:

     document.addEventListener('mousemove', event => this.onMouseMove(event));
    

    You’re creating an entirely new function using the => "fat arrow" operator. Your code is equivalent to this:

    onMouseDown(event) {
        const listener = event => this.onMouseMove(event);
        document.addEventListener('mousemove', listener);
    }
    

    You can see that the listener function only exists inside the onMouseDown method. There’s no way to access it in other methods.

    So when you run this line, later:

    document.removeEventListener('mousemove', this.onMouseMove);
    

    Nothing happens, because the reference to your method this.onMouseMove is not the same as listener. They are different functions.

    Solution(s)

    You should research this, and methods vs functions in Javascript. This type of issue comes up a lot when dealing with classes and events.

    There a few ways to solve this problem. First of all, change your code to bind the class method, so that we can remove it later:

    this.rect.addEventListener('mousedown', this.onMouseDown);
    

    But there’s a problem here. When you pass in this.onMouseDown to addEventListener, you’re binding the event listener to the function onMouseDown. When you do this, it becomes a "function," not a "method". It loses its relationship to the Example class, so that when onMouseDown runs, this will no longer be the instance of Example. So when the code executes with:

    document.addEventListener('mousemove', event => this.onMouseMove(event));
    

    this will no longer point to the class instance, and the code will error rather than add an event listener.

    A handy trick in Javascript to get around this issue is to instead define methods with the fat arrow operator, which makes them always use the parent’s this, which will be your class instance. Here’s the full code:

    class Example {
        rect;
    
        constructor(rect) {
            this.rect = rect;
            this.rect.addEventListener('mousedown', this.onMouseDown);
        }
    
        onMouseDown = () => {
            document.addEventListener('mousemove', this.onMouseMove);
            document.addEventListener('mouseup', this.onMouseUp);
        }
    
        onMouseMove = (event) => {
            this.rect.style.left = event.clientX;
            this.rect.style.top = event.clientY;
        }
    
        onMouseUp = (event) => {
            document.removeEventListener('mousemove', this.onMouseMove);
            document.removeEventListener('mouseup', this.onMouseUp);
        }
    }
    
    Login or Signup to reply.
  2. you will need to store references to the event handler functions in order to remove them later.

    also you will need to assign the event handler functions using arrow functions in the constructor to make sure that the functions retain the correct reference to( this ) and prevent unnecessary creation of new function instances.

    last thing ,Inside the onMouseMove event handler,you didn’t specify the unit to the "left" and "top" CSS properties to make it draggable

        class Example {
      rect;
    
      onMouseMoveHandler;
      onMouseUpHandler;
    
      constructor(rect) {
        this.rect = rect;
        this.onMouseDownHandler = (event) => this.onMouseDown(event);
        this.rect.addEventListener('mousedown', this.onMouseDownHandler);
      }
    
      onMouseDown(event) {
        this.onMouseMoveHandler = (event) => this.onMouseMove(event);
        this.onMouseUpHandler = (event) => this.onMouseUp(event);
    
        document.addEventListener('mousemove', this.onMouseMoveHandler);
        document.addEventListener('mouseup', this.onMouseUpHandler);
      }
    
      onMouseMove(event) {
        this.rect.style.left = event.clientX + 'px'; // 
     you need to Add 'px' to specify the unit
    
        this.rect.style.top = event.clientY + 'px';
      }
    
      onMouseUp(event) {
        document.removeEventListener('mousemove', this.onMouseMoveHandler);
        document.removeEventListener('mouseup', this.onMouseUpHandler);
      }
    }
    
    document.addEventListener('DOMContentLoaded', function () {
      var rect = document.getElementById('rect');
      var ex = new Example(rect);
    });
    
    Login or Signup to reply.
  3. Two already posted answers explains perfectly why you code isn’t working – you have to pass exactly same reference to function both in addEventListener and removeEventListener. To add my two cents to existing answers – there is very convenient method for removing event listeners (supported by all major browsers) – AbortController. As third argument passed to addEventListener function you can pass object with options, and you can pass signal from AbortController as signal property. Now you don’t have to remember reference to passed function, just whenever you want to remove event listener, you can call abort() method from AbortController. Example:

    const button = document.querySelector('button');
    const abortController = new AbortController();
    
    button.addEventListener(
      'click',
      () => {
        console.log('CLICK');
      },
      { signal: abortController.signal }
    );
    
    setTimeout(() => {
      abortController.abort();
    }, 5000);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search