Делегирование событий Vanilla JS - работа с дочерними элементами целевого элемента

Я пытаюсь сделать делегирование событий в ванильном JS. У меня есть кнопка внутри контейнера, как это

<div id="quiz">
    <button id="game-again" class="game-again"><span class="icon-spinner icon"></span><span>Go again</span></button>
</div>

И, следуя хорошим инструкциям Дэвида Уолша, я добавляю обработчик событий к предку кнопки следующим образом:

this.container.addEventListener('click', function(e){
    if (e.target && e.target.id == 'game-again') {
        e.stopPropagation();
        self.publish('primo:evento');
    }
});

Где this.container - элемент #quiz. Это работает половину времени, но в остальное время целью события click является один из промежутков внутри кнопки, поэтому мой обработчик событий не вызывается. Какой лучший способ справиться с этой ситуацией?

1 ответ

Решение

Используя.matches, вы можете найти этот polyfill полезным (т.е. 9+)

if (!Element.prototype.matches)
Element.prototype.matches =
    Element.prototype.msMatchesSelector ||
    Element.prototype.webkitMatchesSelector;

скопировано с этой страницы MDN

Новые браузеры

Поддержка новых браузеров .matches:

this.container.addEventListener('click', function(e){
    if (e.target.matches('#game-again,#game-again *')) {
        e.stopPropagation();
        self.publish('primo:evento');
    }
});

Вы можете получить версию без префикса с

var matches = document.body.matchesSelector || document.body.webkitMatchesSelector || document.body.mozMatchesSelector || document.body.msMatchesSelector || document.body.webkitMatchesSelector

А потом использовать .apply для большего количества браузеров (все еще IE9+).

Старые браузеры

Предполагая, что вы должны поддерживать более старые браузеры, вы можете пройти DOM:

function hasInParents(el,id){
    if(el.id === id) return true; // the element
    if(el.parentNode) return hasInParents(el.parentNode,id); // a parent
    return false; // not the element nor its parents
}

Тем не менее, это поднимется на весь дом, и вы хотите остановиться на цели делегирования:

function hasInParentsUntil(el,id,limit){
    if(el.id === id) return true; // the element
    if(el === limit) return false;
    if(element.parentNode) return hasInParents(el.parentNode,id); // a parent
    return false; // not the element nor its parents
}

Который, сделает ваш код:

this.container.addEventListener('click', function(e){
    if (hasInParentsUntil(e.target,'game-again',container)) { // container should be 
        e.stopPropagation();                                  // available for this
        self.publish('primo:evento');
    }
});

Альтернативное решение:

Добавить класс ко всем вложенным дочерним элементам (.pointer-none)

/* css */
.pointer-none {
  pointer-events: none;
}

Ваша наценка становится

<div id="quiz">
    <button id="game-again" class="game-again">
        <span class="icon-spinner icon pointer-none"></span>
        <span class="pointer-none">Go again</span>
    </button>
</div>

Если указатель не установлен, событие click не будет срабатывать для этих элементов.

https://css-tricks.com/slightly-careful-sub-elements-clickable-things/

Другие вопросы по тегам