DEV Community

Cover image for When Event Listeners Won't Listen
aryaziai
aryaziai

Posted on

When Event Listeners Won't Listen

As part of my first Javascript project at Flatiron. My partner and I decided to recreate Microsoft Paint using the Canva HTML Element and manipulating it via Javascript. The canva element is packed with tons of attributes that we can change, from stroke size to color. We simply attach an event listener to the Canva element, and it will populate with color corresponding with our mouse movements.


Console logging the mouse x-y coordinates. (Smiley face is unintentionally creepy).

Each color inside the color wheel is a contains an id with its respective color. i.e. yellow button = #yellow. Therefore, by adding a "click" event listener, we can assign the stroke color to the event.target.id without any issues.

The project was straight-forward until we decided to implement an eraser. Rather than assigning the stroke color to "white" when a user clicks the "eraser" button, we wanted a true eraser functionality for more flexibility later on in the project. After some searching, we found a solution here for the eraser.

The issue arose once we tried to revert back to color. Although the appropriate stroke settings were being reassigned (we know via console logging). The eraser function was also adding an event listener to the canva, and using the same mouse behaviors: mousedown, mousemove, and mouseout. Therefore creating event listener conflicts.

mousedown: The mousedown event is fired at an Element when a pointing device button is pressed while the pointer is inside the element.

mousemove: The mousemove event is fired at an element when a pointing device (usually a mouse) is moved while the cursor's hotspot is inside it.

mouseout: The mouseout event is fired at an Element when a pointing device (usually a mouse) is used to move the cursor so that it is no longer contained within the element or one of its children.

The event listeners within the eraser function were assigning new stroke attributes and these were conflicting with the stroke attributes that were defined within the coloring function. Therefore switching back and forth between coloring and erasing was very buggy. Notice how we are console logging "drawing" and "erasing" at the same time.

At this point, I looked into removing event listeners and found the following example from W3Schools:

document.getElementById("myDIV").addEventListener("mousemove", myFunction);

document.getElementById("myDIV").removeEventListener("mousemove", myFunction);
Enter fullscreen mode Exit fullscreen mode

In my case, myFunction would be myEraser. However, inside myEraser I had a lot of event listeners with anonymous functions (notice how there is no function name associated with mousemove).

 function myEraser () {       
        canvas.addEventListener('mousemove', (e) => {
        if (isPress) {

            let x = e.offsetX;
            let y = e.offsetY;
            ctx.globalCompositeOperation = 'destination-out';

            ctx.beginPath();
            ctx.arc(x, y, 10, 0, 2 * Math.PI);
            ctx.fill();
            ctx.lineWidth = 20;
            ctx.moveTo(old.x, old.y);
            ctx.lineTo(x, y);

            old = {x: x, y: y};
        }
        });
Enter fullscreen mode Exit fullscreen mode

I refactored all my of code and created a callback function for each event listener inside of myEraser(). i.e.:

    function myEraser() {
        canvas.addEventListener("mousemove", eraseMouseMove) 
    }


   function eraseMouseMove(e) {
        let x = e.offsetX;
        let y = e.offsetY;
        ctx.globalCompositeOperation = "destination-out";
        ctx.beginPath();
        ctx.arc(x, y, 10, 0, 2 * Math.PI);
        ctx.fill();
        ctx.lineWidth = 20;
        ctx.moveTo(old.x, old.y);
        ctx.lineTo(x, y);
        old = { x: x, y: y };
        console.log("Eraser is active")
    }
Enter fullscreen mode Exit fullscreen mode

Now I can directly remove the listener that I want by calling the function that corresponds with it. I will be adding this line of code inside the event listener handling clicks for the color buttons the user can select from:

canvas.removeEventListener("mousemove", eraseMouseMove);
Enter fullscreen mode Exit fullscreen mode

Final product (notice how we are only console logging the appropriate action).

The final code serves as on and off switch, depending on which button the user clicks:

  document.querySelector("#eraser").addEventListener("click", (e) => {
    // Remove drawing event listeners 
      canvas.removeEventListener("mousedown", drawingMouseDown);
      canvas.removeEventListener("mousemove", drawinngMouseMove);
      canvas.removeEventListener("mouseup", drawingMouseUpAndOut);

    // Add eraser event listeners 
      canvas.addEventListener("mousedown", eraseMouseDown);
      canvas.addEventListener("mousemove", eraseMouseMove);
      canvas.addEventListener("mouseup", eraseMouseUp);
  })


  document.querySelector("#color").addEventListener("click", (e) => {
    // Remove erasing event listeners
      canvas.removeEventListener("mousedown", eraseMouseDown);
      canvas.removeEventListener("mousemove", eraseMouseMove);
      canvas.removeEventListener("mouseup", eraseMouseUp);

    // Add drawing event listeners 
      canvas.addEventListener("mousedown", drawingMouseDown);
      canvas.addEventListener("mousemove", drawinngMouseMove);
      canvas.addEventListener("mouseup", drawingMouseUpAndOut);

    });
  }
Enter fullscreen mode Exit fullscreen mode

Top comments (7)

Collapse
 
neradev profile image
Moritz Schramm

Has there been a reason, why you decided for adding and removing event listeners (which causes a lot of overhead) vs. you do some state management and keep both listerns which are added once and check whether the state is corresponding to one or the other?

Collapse
 
jamesthomson profile image
James Thomson

This is what I was thinking while reading this as well. Rather than manage event listeners, manage the current state. Much simpler (and modular) to switch around the paintbrush type and call the associated function(s).

Collapse
 
ionline247 profile image
Matthew Bramer • Edited

Maintaining state would be the preferred approach. He could even use a pub/sub <gasp/> event listener and toggle state that way

Collapse
 
aryaziai profile image
aryaziai

Hey, thank you for the comment. I just started Javascript so I haven't gotten to state yet.

Collapse
 
wdiep10 profile image
Will Diep

Excellent article! Very helpful

Collapse
 
jwp profile image
John Peters

A great example of refactoring to contain the eventhandler. Thanks.

Collapse
 
dawncronin profile image
Dawn Cronin • Edited

This is a great overview of vanilla javascript event listeners. Arya, this will be very useful to many beginners