最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

html - JavaScript nested addEventListener in callback of another addEventListener is firing immediately - Stack Overflow

programmeradmin4浏览0评论

I have this mobile link menu that I want to open with a button and be able to close by all means possible. Either by clicking the same button again, clicking one of the menu links, or anywhere else on the page. Meaning that once the menu is open, every single pixel on the screen is a valid candidate for detecting and closing the menu.

That's why I set up a listener for the menu button to toggle the menu. And once the menu is visible, set up another one-time-listener on the whole document to toggle it hidden again:

document.querySelector('#box').addEventListener('click', () => {
    console.log("box clicked");
    
    document.addEventListener('click', () => {
        console.log('everything clicked');
    }, {once: true})
})
<div id="box" style="width: 200px; height: 200px; background-color: red"></div>

I have this mobile link menu that I want to open with a button and be able to close by all means possible. Either by clicking the same button again, clicking one of the menu links, or anywhere else on the page. Meaning that once the menu is open, every single pixel on the screen is a valid candidate for detecting and closing the menu.

That's why I set up a listener for the menu button to toggle the menu. And once the menu is visible, set up another one-time-listener on the whole document to toggle it hidden again:

document.querySelector('#box').addEventListener('click', () => {
    console.log("box clicked");
    
    document.addEventListener('click', () => {
        console.log('everything clicked');
    }, {once: true})
})
<div id="box" style="width: 200px; height: 200px; background-color: red"></div>

When the first eventlistener detects a click, it runs the callback function. Inside that function, I've set up another onClick listener that fires instantaneously like it's piggybacking off the other click event!

I understand that due to propagation nested listeners will both be activated, but what I'm expecting is that the very first time a click is registered only the "box clicked" will run since once inside the callback function, the event has passed and the second listener needs to wait for the next click event to be able to detect it. But instead, it adds a listener and detects it immediately.

My expected logs (from sequentially clicking on the box):

    • box clicked
    • box clicked
    • everything clicked
    • box clicked
    • box clicked
    • everything clicked
    • box clicked
    • box clicked
    • everything clicked

etc...

Share Improve this question edited Aug 24, 2021 at 21:17 Nermin asked Aug 17, 2019 at 0:41 NerminNermin 94110 silver badges26 bronze badges 1
  • javascript.info/bubbling-and-capturing – kip Commented Aug 17, 2019 at 0:57
Add a ment  | 

2 Answers 2

Reset to default 4

You're setting the box up to have a click event as well as the document. But, the box is part of the document, so when you click the box, the event propagates to the document as well. This is because events propagate and as soon as you click the box, you've set up the document handler and it happens so quickly that the new handler is in place before the initial click event propagates to the document.

If you don't want box clicks to do this, use the stopPropagation() method in that handler.

But, you've got another issue with this code. Because you are setting up a document event handler when the box gets clicked, it will set up an additional handler on the document EVERY TIME you click the box.

Try it below by clicking the box 3 times and then click outside of the box once.

document.querySelector('#box').addEventListener('click', (evt) => {
  evt.stopPropagation();
  console.log("box clicked");
    
  document.addEventListener('click', () => {
    console.log('outside of box clicked');
  });
})
<div id="box" style="width: 200px; height: 200px; background-color: red">Click me to fire my handler and set up the document handler.</div>

To get around this, it's best to set up a variable to keep track of whether the document handler has already been set:

let boxClicked = false;

document.querySelector('#box').addEventListener('click', (evt) => {
  evt.stopPropagation();
  console.log("box clicked");
  
  // Only set the next handler if it's the first time you've clicked the box
  if(!boxClicked) {
    document.addEventListener('click', () => {
      console.log('outside of box clicked');
    });
  }
  
  boxClicked = true;
})
<div id="box" style="width: 200px; height: 200px; background-color: red">Click me to fire my handler and set up the document handler.</div>

Or (and this is more resource intensive), remove the old handler before adding a new one. This technique requires that the callback function be set up as a named function. Your edited question shows you using the {once:true} options argument for addEventLister(), which solves the problem, but that code essentially does the following, which (as I say) is more resource intensive because constantly adding and removing listeners uses memory.

document.querySelector('#box').addEventListener('click', (evt) => {
  evt.stopPropagation();
  console.log("box clicked");
  
  // Remove the first handler
  document.removeEventListener("click", handleDocClicked)
  
  // Then set up a new one
  document.addEventListener('click', handleDocClicked);  
});

function handleDocClicked(){
  console.log('outside of box clicked');
}
<div id="box" style="width: 200px; height: 200px; background-color: red">Click me to fire my handler and set up the document handler.</div>

I created this solution inspired and based on Scott's solution, but with the additional last tweaks to make the solution follow the expected behavior precisely.

let toggleBox = false

document.querySelector('#box').addEventListener('click', (evt) => {
  console.log("box clicked");
  if (!toggleBox) {
    evt.stopPropagation();
  }
  toggleBox = !toggleBox
  
  document.addEventListener('click', handleDocClicked, {once:true});  
});

function handleDocClicked(){
  console.log('everything clicked');
  toggleBox = false
}
<div id="box" style="width: 200px; height: 200px; background-color: red">Click me to fire my handler and set up the document handler.</div>

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论