CustomEvent侦听器回调触发两次?

时间:2019-09-16 15:59:08

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

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

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时,它给出了另一个不同的元素(相同类型,<slide-button>,页面上的第一个元素)不会删除“ mouseover”侦听器,并且不会触发该事件。

我在这里做错什么了吗?还是我到底想念什么?

先谢谢了。

3 个答案:

答案 0 :(得分:0)

问题出在代码的嵌套上。

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

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

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

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

因此,现在,无论您同时单击mousedownmouseup都会被触发。即使您在mouseup事件监听器中删除了事件监听器,也是如此。其中的代码已经在执行。
这就是导致您的代码两次触发CustomEvent的原因。

防止事件侦听器嵌套,除非其中一个会依赖另一个。就像在mousedown事件监听器中一样,您需要在其中添加mousemove事件监听器。现在,不是嵌套,而是添加另一个事件监听器,该事件监听器侦听mouseup事件监听器附近的mousedown。在那里删除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中,我检查是否单击的当前目标实际上是按钮。如果是这样,targetIsClicked值将被切换为表明已单击正确的按钮。

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

尽管,如果您有多个<slide-button>元素,则会在mousedown上附加多个mousemovemouseupdocument侦听器,这可能产生一些奇怪的结果。

注意

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

答案 1 :(得分:0)

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

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

document.dispatchEvent(evt);

答案 2 :(得分:0)

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

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

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

就我而言,这似乎可以解决问题,但它绝对是超级黑客,如果需要超级可靠,建议您尝试找出问题的根本原因。

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

相关问题