【问题标题】:CustomEvent listener callback is firing twice?CustomEvent 侦听器回调触发两次?
【发布时间】:2019-09-16 15:59:08
【问题描述】:

我创建了一个自定义元素:

const templ = document.createElement('template');
templ.innerHTML = `
<span><slot></slot></span>
`;
class SlideButton extends HTMLElement {

    constructor() {
        super();

        // Attach a shadow root to the element.
        const shadowRoot = this.attachShadow({ mode: 'open' });
        shadowRoot.appendChild(tmpl.content.cloneNode(true));
        this.span = shadowRoot.querySelector('span');

        this.triggerEvent = new CustomEvent("trigger", {
            bubbles: false,
            cancelable: false,
        });

        this.initMouseEvents();
    }

    initMouseEvents() {
        this.span.addEventListener('mousedown', (e) => {

            //Watch and calculate slide amount..
            this.addEventListener('mousemove', this.slide, false);

        });

        //When button is released...
        document.addEventListener('mouseup', handleMouseUp = (e) => {

            this.removeEventListener('mousemove', this.slide, false);
            document.removeEventListener('mouseup', handleMouseUp);

            //If slided enough, dispatch event...
            if (Math.abs(this.slideAmount) > (this.maxSlide * 0.75)) {
                console.log('firing event');
                this.dispatchEvent(this.triggerEvent);
            }
            //Reset button to normal state...

        }, false);
    }
}

我的代码中的其他地方..

class SpotLightModal {

    //Constructor..
    //code..
    //code..

    init() {
        this.actions.querySelector('slide-button[type="den"]').addEventListener('trigger', e => {
            console.log(e);
            console.log('den');
            //Do stuff..
        });
    }

    //code...
    //code...

}

除了事件侦听器中的回调运行两次并且输出为:

之外,一切都按预期工作
firing event
CustomEvent {...}
den
CustomEvent {...}
den

e.stopPropagation()e.preventDefault() 都没有效果,尝试使用它们也无济于事..

我已经编辑以包含this.span,并且还将“mouseup”事件侦听器移到“mousedown”事件侦听器之外,但这不起作用,事实上在记录this时,现在,它提供了另一个不同的元素(相同类型,&lt;slide-button&gt;,页面上的第一个),“鼠标悬停”侦听器不会被移除,事件也不会被触发。

我在这里做错了吗?或者我到底错过了什么?

提前谢谢..

【问题讨论】:

  • 我认为这是因为您将事件嵌套在事件中。
  • @ChrisHemmens 我尝试以不同的方式订购它们,但没有任何效果.. 其他订单导致事件多次触发或根本不触发

标签: javascript web-component custom-element custom-events


【解决方案1】:

如果其他人遇到类似问题,请尝试使用:

event.stopImmediatePropagation() 像这样进入你的回调函数

window.addEventListener('message', (event) => {
  event.stopImmediatePropagation();
  console.log('detail:', event.detail);
})

在我的情况下,这似乎可以解决问题,但它绝对是超级hacky,如果它需要超级可靠,建议尝试找出问题的根本原因。

https://developer.mozilla.org/en-US/docs/Web/API/Event/stopImmediatePropagation

【讨论】:

    【解决方案2】:

    问题在于代码的嵌套。

    首先,您添加第一个mousedown 事件侦听器,该侦听器将在单击鼠标但未释放时触发。

    this.span.addEventListener('mousedown', (e) => { ...
    

    然后在您的mousedown 事件侦听器中侦听document 上的mouseup 事件。

    document.addEventListener('mouseup', handleMouseUp = (e) => { ...
    

    所以现在,无论何时点击mousedownmouseup 都会被触发。即使您删除了 mouseup 事件侦听器中的事件侦听器。里面的代码已经在执行了。
    这就是导致您的代码触发CustomEvent 两次的原因。

    防止事件监听器的嵌套,除非一个依赖于另一个。就像在mousedown 事件侦听器中一样,您需要在其中添加mousemove 事件侦听器。现在代替嵌套添加另一个事件侦听器来侦听mouseupmousedown 事件侦听器相邻。删除那里的mousemove 事件。示例如下:

    class SlideButton extends HTMLElement {
    
        constructor() {
            super();
    
            // Attach a shadow root to the element.
            const shadowRoot = this.attachShadow({ mode: 'open' });
            shadowRoot.appendChild(tmpl.content.cloneNode(true));
            this.span = shadowRoot.querySelector('span');
    
            this.triggerEvent = new CustomEvent("trigger", {
                bubbles: false,
                cancelable: false,
            });
    
        }
    
        connectedCallback() {
    
            // It's preferred to set the event listeners in the connectedCallback method.
            // This method is called whenever the current element is connected to the document.
            this.initMouseEvents();
    
        }
    
        initMouseEvents() {
    
            // Bind slider-button as this context to the slide method.
            // In case you use the this keyword inside of the slide method
            // you would need to do this to keep using the methods of this class.
            const slide = this.slide.bind(this);
    
            // Create a toggle to indicate that the slide-button has been clicked so that ONLY then the event listeners will be added.
            let targetIsClicked = false;
    
            // Mouse is clicked, mousemove added.
            document.addEventListener('mousedown', (e) => {
    
                // Check if this button has been clicked.
                const slideButton = e.target.closest('slide-button');
                if (slideButton === this) {
    
                    // Toggle true and add mousemove event.
                    targetIsClicked = true;
                    document.addEventListener('mousemove', slide, false);
                }
    
            });
    
            // Mouse is released, remove mousemove and fire event.
            document.addEventListener('mouseup', (e) => {
    
                // Only do something if this button has been clicked in the mousedown event.
                if (targetIsClicked === true) {
                    document.removeEventListener('mousemove', slide, false);
    
                    // Reset toggle value.
                    targetIsClicked = false;
    
                    //If slided enough, dispatch event...
                    if (Math.abs(this.slideAmount) > (this.maxSlide * 0.75)) {
                        console.log('firing event');
                        this.dispatchEvent(this.triggerEvent);
                    }
                    //Reset button to normal state...
    
                }
    
            });
    
        }
    }
    

    编辑

    我已将事件侦听器的目标更改为document。您解释说您希望 mousemovemouseup 事件在按钮外触发它们时起作用。

    mousedown 中,我检查当前被点击的目标是否真的是this 按钮。如果是这样,targetIsClicked 值将切换以指示已单击正确的按钮。

    mouseup 事件中,首先检查targetIsClicked 是否为true。这意味着您单击了按钮并执行其余代码。

    不过,如果您有多个 &lt;slide-button&gt; 元素,则会有多个 mousedownmousemovemouseup 侦听器附加到 document,这可能会产生一些奇怪的结果。

    注意

    我已将 this.initMouseEvents() 函数调用移至 connectedCallback() 方法。这样做是一种很好的做法,因为现在您正在与自定义元素的 生命周期 进行交互。创建元素时在connectedCallback() 中添加事件侦听器,并在删除元素时在disconnectedCallback() 中删除事件侦听器。元素本身会触发这些方法,因此您不必调用它们。

    【讨论】:

    • this.span 是我的自定义元素中的一个影子 Dom 元素(它是应该滑动的部分)
    • 是否将“mouseup”事件附加到“this”会导致它仅在鼠标在元素上释放时触发?我以前有这个,但如果鼠标在其他地方释放,我希望它也能工作,这就是为什么我将侦听器添加到文档中......当我回家时会尽快尝试你的例子
    • 您能否修改您的代码,使其在 Shadow DOM 中包含 &lt;span&gt; 元素?是的,使用this 只会在元素上触发它。所以也许document 是一个更好的候选人。但在更改答案之前,我会等待您添加代码。
    • 我已经编辑了我的答案并试图解释大多数事情。请检查一下,如果有任何帮助,请告诉我。
    • 不幸的是没有运气,按照你的例子:第一次测试按钮工作正常(按钮本身,滑动良好并在释放时重置),但是,事件监听器仍然运行两次......并对其进行测试第二次导致错误行为,“mousemove”没有被删除并且事件没有被触发:/
    【解决方案3】:

    有一个“气泡”选项,您可以将其设置为 false,这应该消除鼠标向下/向上冒泡阶段:

    var evt = new CustomEvent(name, {
        detail : data,
        bubbles: true, // <-- try setting to false
        cancelable: true
    });
    
    document.dispatchEvent(evt);
    

    【讨论】:

      【解决方案4】:

      在我的例子中,我使用事件总线来调度自定义事件,这将触发发生双重事件的回调函数。

      最初,自定义事件监听器位于constructor()

      constructor() {
          EventBus.addEventListener('someEvent', this.doSomething);
      }
      

      一旦我把那条线移到 connectedCallback():

      connectedCallback() {
         EventBus.addEventListener('someEvent', this.doSomething);
      }
      

      【讨论】:

        猜你喜欢
        • 2016-10-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多