Отмена всплытия события js. JavaScript - Всплытие события. Получение элемента, который вызвал обработчик

Начиналось все с использования JavaScript и классов.

Однако у меня возникла проблема. Я хотел использовать так называемые Всплывающие События, но также я хотел минимизировать зависимости, которые мне пришлось бы внедрять. Я не хотел подключать библиотеки jQuery для «этого маленького теста», толькло для того, чтобы использовать всплывающие события.

Давайте посмотрим поближе, что такое всплывающие события, как они работают, и рассмотрим несколько путей для их реализации.

Окей, так в чем проблема? Рассмотрим простой пример:

Предположим, имеется список кнопок. Каждый раз, когда я нажимаю на одну из них, она должна стать «активной». После повторного нажатия кнопка должна вернуться в исходное состояние.

Начнем с HTML:

  • Pencil
  • Pen
  • Eraser

Я мог бы использовать стандартный JavaScript обработчик событий вроде такого:

For(var i = 0; i < buttons.length; i++) { var button = buttons[i]; button.addEventListener("click", function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }); }
Выглядит неплохо… Но работать он не будет. По крайней мере, не так, как мы этого ожидаем.

Замыкания победили Для тех, кто немного знает функциональный JavaScript, проблема очевидна.

Для остальных же кратко объясню - функция обработчика замыкается на переменную button . Однако это переменная одна, и перезаписывается каждую итерацию.

В первой итерации переменная ссылается на первую кнопку. В последующей - на вторую, и так далее. Но, когда пользователь нажимает на кнопку, цикл уже закончился, и переменная button ссылается на последнюю кнопку, что всегда вызывает обработчик события для нее. Непорядок.

Что нам нужно, так это отдельный контекст для каждой функции:

Var buttons = document.querySelectorAll(".toolbar button"); var createToolbarButtonHandler = function(button) { return function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; }; for(var i = 0; i < buttons.length; i++) { buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i])); }
Намного лучше! А главное, правильно работает. Мы создали функцию createToolbarButtonHandle , которая возвращает обработчик события. Затем для каждой кнопки вешаем свой обработчик.

Так в чем проблема? И выглядит хорошо, и работает. Несмотря на это, мы все еще можем сделать наш код лучше.

Во-первых, мы создаем слишком много обработчиков. Для каждой кнопки внутри .toolbar мы создаем функцию и привязываем ее как обработчик события. Для трех кнопок использование памяти незначительное.

Но если мы имеем что-то подобное:

  • Foo
  • Bar
  • // ... еще 997 элементов...
  • baz

то компьютер, конечно, не взорвется от переполнения. Однако наше использование памяти далеко от идеального. Мы выделяем огромное ее количество, хотя можно обойтись и без этого. Давайте перепишем наш код еще раз, так, чтобы использовать одну функцию несколько раз.

Вместо того чтобы ссылаться на переменную button , чтобы следить, на какую кнопку мы нажали, мы можем использовать event объект (объект «события»), который первым аргументом передается в каждый обработчик события.

Event объект содержит некоторые данные о событии. В нашем случае нас интересует поле currentTarget . Из него мы получим ссылку на элемент, который был нажат:

Var toolbarButtonHandler = function(e) { var button = e.currentTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; for(var i = 0; i < buttons.length; i++) { button.addEventListener("click", toolbarButtonHandler); }
Отлично! Мы не только упростили все до единственной функции, которая используется несколько раз, мы еще и сделали наш код более читаемым, удалив лишнюю функцию-генератор.

Однако мы все еще можем лучше.

Предположим, мы добавили несколько кнопок в лист уже после того, как наш код исполнился. Тогда нам тоже понадобилось бы добавлять обработчики событий для каждой из них. И нам пришлось бы хранить ссылку на этот обработчик и ссылки из других мест. Выглядит не слишком заманчиво.

Возможно, существует и другой подход?

Начнем с того, что разберемся, как же работают события и как они двигаются по нашему DOM.

Как же большинство из них работает? Когда пользователь нажимает на элемент, генерируется событие, чтобы оповестить приложение об этом. Путешествие каждого события происходит в три стадии:
  • Фаза перехвата
  • Событие возникает для целевого элемента
  • Фаза всплывания
  • Пометка: не все события проходят стадию перехвата или всплывания, некоторые создаются сразу на элементе. Однако это скорее исключение из правил.

    Событие создается снаружи документа и затем последовательно перемещается по DOM иерархии до target (целевого) элемента. Как только оно добралось до своей цели, событие тем же путем выбирается из DOM элемента.

    Вот наш HTML шаблон:

    • Button A
    • Button B
    • Button C

    Когда пользователь нажимает на кнопку А, событие путешествует таким образом:

    Начало
    | #document
    | Фаза перехвата
    | HTML
    | BODY
    | UL
    | LI#li_1
    | Кнопка А < - Событие возникает для целевого элемента
    | Фаза всплывания
    | LI#li_1
    | UL
    | BODY
    | HTML
    v #document

    Заметьте, что мы можем проследить путь, по которому событие двигается до своего целевого элемента. В нашем случае для каждой нажатой кнопки мы можем быть уверены, что событие всплывет обратно, пройдя через своего родителя - ul элемент. Мы можем использовать это и реализовать всплывающие cобытия.

    Всплывающие события Всплывающие события - это те события, которые привязаны к элементу родителя, но исполняются лишь в случае, если они удовлетворяют какому-либо условию.

    В качестве конкретного примера возьмем нашу панель инструментов:

    Ul class="toolbar">

  • Pencil
  • Pen
  • Eraser

  • Теперь, зная, что любое нажатие на кнопке всплывет через элемент ul.toolbar , давайте прикрепим наш обработчик событий на него. К счастью, он у нас уже есть:

    Var toolbar = document.querySelector(".toolbar"); toolbar.addEventListener("click", function(e) { var button = e.target; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); });
    Теперь мы имеем намного более чистый код, и мы даже избавились от циклов! Заметьте однако, что мы заменили e.currentTarget на e.target . Причина кроется в том, что мы обрабатываем события на другом уровне.

    e.target - фактическая цель события, то, куда оно пробирается через DOM, и откуда потом будет всплывать.
    e.currentTarget - текущий элемент, который обрабатывает событие. В нашем случае, это ul.toolbar .

    Улучшенные всплывающие события В данный момент мы обрабатываем любое нажатие на каждый элемент, которое всплывает через ul.toolbar , но наше условие проверки слишком простое. Что произошло бы, если бы имели более сложный DOM, включащий в себя иконки и элементы, которые не были созданы для того, чтобы по ним кликали?

    • Pencil
    • Pen
    • Eraser

    Упс! Теперь, когда мы кликаем на li.separator или иконку, мы добавляем ему класс .active . Как минимум, это нехорошо. Нам нужен способ фильтровать события так, чтобы мы реагировали на нужный нам элемент.

    Создадим для этого небольшую функцию-помощника:

    Var delegate = function(criteria, listener) { return function(e) { var el = e.target; do { if (!criteria(el)) continue; e.delegateTarget = el; listener.apply(this, arguments); return; } while((el = el.parentNode)); }; };
    Наш помощник делает две вещи. Во-первых, он обходит каждый элемент и его родителей и проверят, удовлетворяют ли они условию, переданному в параметре criteria . Если элемент удовлетворяет - помощник добавляет объекту события поле, называемое delegateTarget , в котором хранится элемент, удовлевторяющий нашим условиям. И затем вызывает обработчик. Соответственно, если ни один элемент не удовлетворяет условию, ни один обработчик не будет вызван.

    Мы можем использовать это так:

    Var toolbar = document.querySelector(".toolbar"); var buttonsFilter = function(elem) { return elem.classList && elem.classList.contains("btn"); }; var buttonHandler = function(e) { var button = e.delegateTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; toolbar.addEventListener("click", delegate(buttonsFilter, buttonHandler));
    То, что доктор прописал: один обработчик событий, прикрепленный к одному элементу, который делает всю работу. Но делает ее только для нужных нам элементов. И он отлично реагирует на добавление и удаление объектов из DOM.

    Подводя итоги Мы вкратце рассмотрели основы реализации делегирования (обработки всплывающих) событий на чистом JavaScript. Это хорошо тем, что нам не нужно генерировать и прикреплять кучу обработчиков для каждого элемента.

    Если бы я хотел сделать из этого библиотеку или использовать код в разработке, я бы добавил пару вещей:

    Функция-помощник для проверки удовлетворения объекта критериям в более унифицированном и функциональном виде. Вроде:

    Var criteria = { isElement: function(e) { return e instanceof HTMLElement; }, hasClass: function(cls) { return function(e) { return criteria.isElement(e) && e.classList.contains(cls); } } // Больше критериев };
    Частичное использование помощника так же было бы не лишним:

    Var partialDelgate = function(criteria) { return function(handler) { return delgate(criteria, handler); } };
    Оригинал статьи: Understanding Delegated JavaScript Events
    (От переводчика: мой первый, судите строго.)

    Счастливого кодинга!

    Начиналось все с использования JavaScript и классов.

    Однако у меня возникла проблема. Я хотел использовать так называемые Всплывающие События, но также я хотел минимизировать зависимости, которые мне пришлось бы внедрять. Я не хотел подключать библиотеки jQuery для «этого маленького теста», толькло для того, чтобы использовать всплывающие события.

    Давайте посмотрим поближе, что такое всплывающие события, как они работают, и рассмотрим несколько путей для их реализации.

    Окей, так в чем проблема? Рассмотрим простой пример:

    Предположим, имеется список кнопок. Каждый раз, когда я нажимаю на одну из них, она должна стать «активной». После повторного нажатия кнопка должна вернуться в исходное состояние.

    Начнем с HTML:

    • Pencil
    • Pen
    • Eraser

    Я мог бы использовать стандартный JavaScript обработчик событий вроде такого:

    For(var i = 0; i < buttons.length; i++) { var button = buttons[i]; button.addEventListener("click", function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }); }
    Выглядит неплохо… Но работать он не будет. По крайней мере, не так, как мы этого ожидаем.

    Замыкания победили Для тех, кто немного знает функциональный JavaScript, проблема очевидна.

    Для остальных же кратко объясню - функция обработчика замыкается на переменную button . Однако это переменная одна, и перезаписывается каждую итерацию.

    В первой итерации переменная ссылается на первую кнопку. В последующей - на вторую, и так далее. Но, когда пользователь нажимает на кнопку, цикл уже закончился, и переменная button ссылается на последнюю кнопку, что всегда вызывает обработчик события для нее. Непорядок.

    Что нам нужно, так это отдельный контекст для каждой функции:

    Var buttons = document.querySelectorAll(".toolbar button"); var createToolbarButtonHandler = function(button) { return function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; }; for(var i = 0; i < buttons.length; i++) { buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i])); }
    Намного лучше! А главное, правильно работает. Мы создали функцию createToolbarButtonHandle , которая возвращает обработчик события. Затем для каждой кнопки вешаем свой обработчик.

    Так в чем проблема? И выглядит хорошо, и работает. Несмотря на это, мы все еще можем сделать наш код лучше.

    Во-первых, мы создаем слишком много обработчиков. Для каждой кнопки внутри .toolbar мы создаем функцию и привязываем ее как обработчик события. Для трех кнопок использование памяти незначительное.

    Но если мы имеем что-то подобное:

    • Foo
    • Bar
    • // ... еще 997 элементов...
    • baz

    то компьютер, конечно, не взорвется от переполнения. Однако наше использование памяти далеко от идеального. Мы выделяем огромное ее количество, хотя можно обойтись и без этого. Давайте перепишем наш код еще раз, так, чтобы использовать одну функцию несколько раз.

    Вместо того чтобы ссылаться на переменную button , чтобы следить, на какую кнопку мы нажали, мы можем использовать event объект (объект «события»), который первым аргументом передается в каждый обработчик события.

    Event объект содержит некоторые данные о событии. В нашем случае нас интересует поле currentTarget . Из него мы получим ссылку на элемент, который был нажат:

    Var toolbarButtonHandler = function(e) { var button = e.currentTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; for(var i = 0; i < buttons.length; i++) { button.addEventListener("click", toolbarButtonHandler); }
    Отлично! Мы не только упростили все до единственной функции, которая используется несколько раз, мы еще и сделали наш код более читаемым, удалив лишнюю функцию-генератор.

    Однако мы все еще можем лучше.

    Предположим, мы добавили несколько кнопок в лист уже после того, как наш код исполнился. Тогда нам тоже понадобилось бы добавлять обработчики событий для каждой из них. И нам пришлось бы хранить ссылку на этот обработчик и ссылки из других мест. Выглядит не слишком заманчиво.

    Возможно, существует и другой подход?

    Начнем с того, что разберемся, как же работают события и как они двигаются по нашему DOM.

    Как же большинство из них работает? Когда пользователь нажимает на элемент, генерируется событие, чтобы оповестить приложение об этом. Путешествие каждого события происходит в три стадии:
  • Фаза перехвата
  • Событие возникает для целевого элемента
  • Фаза всплывания
  • Пометка: не все события проходят стадию перехвата или всплывания, некоторые создаются сразу на элементе. Однако это скорее исключение из правил.

    Событие создается снаружи документа и затем последовательно перемещается по DOM иерархии до target (целевого) элемента. Как только оно добралось до своей цели, событие тем же путем выбирается из DOM элемента.

    Вот наш HTML шаблон:

    • Button A
    • Button B
    • Button C

    Когда пользователь нажимает на кнопку А, событие путешествует таким образом:

    Начало
    | #document
    | Фаза перехвата
    | HTML
    | BODY
    | UL
    | LI#li_1
    | Кнопка А < - Событие возникает для целевого элемента
    | Фаза всплывания
    | LI#li_1
    | UL
    | BODY
    | HTML
    v #document

    Заметьте, что мы можем проследить путь, по которому событие двигается до своего целевого элемента. В нашем случае для каждой нажатой кнопки мы можем быть уверены, что событие всплывет обратно, пройдя через своего родителя - ul элемент. Мы можем использовать это и реализовать всплывающие cобытия.

    Всплывающие события Всплывающие события - это те события, которые привязаны к элементу родителя, но исполняются лишь в случае, если они удовлетворяют какому-либо условию.

    В качестве конкретного примера возьмем нашу панель инструментов:

    Ul class="toolbar">

  • Pencil
  • Pen
  • Eraser

  • Теперь, зная, что любое нажатие на кнопке всплывет через элемент ul.toolbar , давайте прикрепим наш обработчик событий на него. К счастью, он у нас уже есть:

    Var toolbar = document.querySelector(".toolbar"); toolbar.addEventListener("click", function(e) { var button = e.target; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); });
    Теперь мы имеем намного более чистый код, и мы даже избавились от циклов! Заметьте однако, что мы заменили e.currentTarget на e.target . Причина кроется в том, что мы обрабатываем события на другом уровне.

    e.target - фактическая цель события, то, куда оно пробирается через DOM, и откуда потом будет всплывать.
    e.currentTarget - текущий элемент, который обрабатывает событие. В нашем случае, это ul.toolbar .

    Улучшенные всплывающие события В данный момент мы обрабатываем любое нажатие на каждый элемент, которое всплывает через ul.toolbar , но наше условие проверки слишком простое. Что произошло бы, если бы имели более сложный DOM, включащий в себя иконки и элементы, которые не были созданы для того, чтобы по ним кликали?

    • Pencil
    • Pen
    • Eraser

    Упс! Теперь, когда мы кликаем на li.separator или иконку, мы добавляем ему класс .active . Как минимум, это нехорошо. Нам нужен способ фильтровать события так, чтобы мы реагировали на нужный нам элемент.

    Создадим для этого небольшую функцию-помощника:

    Var delegate = function(criteria, listener) { return function(e) { var el = e.target; do { if (!criteria(el)) continue; e.delegateTarget = el; listener.apply(this, arguments); return; } while((el = el.parentNode)); }; };
    Наш помощник делает две вещи. Во-первых, он обходит каждый элемент и его родителей и проверят, удовлетворяют ли они условию, переданному в параметре criteria . Если элемент удовлетворяет - помощник добавляет объекту события поле, называемое delegateTarget , в котором хранится элемент, удовлевторяющий нашим условиям. И затем вызывает обработчик. Соответственно, если ни один элемент не удовлетворяет условию, ни один обработчик не будет вызван.

    Мы можем использовать это так:

    Var toolbar = document.querySelector(".toolbar"); var buttonsFilter = function(elem) { return elem.classList && elem.classList.contains("btn"); }; var buttonHandler = function(e) { var button = e.delegateTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; toolbar.addEventListener("click", delegate(buttonsFilter, buttonHandler));
    То, что доктор прописал: один обработчик событий, прикрепленный к одному элементу, который делает всю работу. Но делает ее только для нужных нам элементов. И он отлично реагирует на добавление и удаление объектов из DOM.

    Подводя итоги Мы вкратце рассмотрели основы реализации делегирования (обработки всплывающих) событий на чистом JavaScript. Это хорошо тем, что нам не нужно генерировать и прикреплять кучу обработчиков для каждого элемента.

    Если бы я хотел сделать из этого библиотеку или использовать код в разработке, я бы добавил пару вещей:

    Функция-помощник для проверки удовлетворения объекта критериям в более унифицированном и функциональном виде. Вроде:

    Var criteria = { isElement: function(e) { return e instanceof HTMLElement; }, hasClass: function(cls) { return function(e) { return criteria.isElement(e) && e.classList.contains(cls); } } // Больше критериев };
    Частичное использование помощника так же было бы не лишним:

    Var partialDelgate = function(criteria) { return function(handler) { return delgate(criteria, handler); } };
    Оригинал статьи: Understanding Delegated JavaScript Events
    (От переводчика: мой первый, судите строго.)

    Счастливого кодинга!

    Перехват события

    Одна из важных особенностей языка - перехват события. Если кто-то, к примеру, щелкает на кнопке, то вызывается программа обработки события onClick, соответствующая этой кнопке. С помощью обработки событий Вы можете добиться того, чтобы объект, соответсвующий вашему окну, документу или слою, перехватывал и обрабатывал событие еще до того, как для этой цели объектом указанной кнопки будет вызван обработчик событий. Точно так же объект вашего окна, документа или слоя может обрабатывать сигнал о событии еще до того, как он достигает своего обычного адресата.
    Чтобы увидеть, для чего это может пригодиться, давайте рассмотрим следующий пример:



    window.onclick= handle;

    function handle(e) {­
    alert("Объект window перехватывает это событие!");
    return true; // т.е. проследить ссылку
    }




    Click on this link

    Как видно, мы не указываем программы обработки событий в тэге . Вместо этого мы пишем

    window.captureEvents(Event.CLICK);

    с тем, чтобы перехватить событие Click объектом window. Обычно объект window не работает с событием Click . Однако, перехватив, мы затем его переадресуем в объект window. Заметим, что в Event.CLICK фрагмент CLICK должен писаться заглавными буквами. Если же Вы хотите перехватывать несколько событий, то Вам следует отделить их друг от друга символами |. Например:

    window.captureEvents(Event.CLICK | Event.MOVE);

    Помимо этого в функции handle() , назначенной нами на роль обработчика событий, мы пользуемся инструкцией return true; . В действительности это означает, что браузер должен обработать и саму ссылку, после того, как завершится выполнение функции handle() . Если же Вы напишете вместо этого return false; , то на этом все и закончится.

    Если теперь в тэге Вы зададите программу обработки события onClick , то поймете, что данная программа при возникновении данного события вызвана уже не будет. И это не удивительно, поскольку объект window перехватывает сигнал о событии еще до того, как он достигает объекта link. Если же Вы определите функцию handle() как

    function handle(e) {­
    alert("The window object captured this event!");
    window.routeEvent(e);
    return true;
    }

    то компьютер будет проверять, определены ли другие программы обработки событий для данного объекта. Переменная e - это наш объект Event, передаваемый функции обработки событий в виде аргумента.

    Кроме того, Вы можете непосредственно послать сигнал о событии какому-либо объекту. Для этого Вы можете воспользоваться методом handleEvent() . Это выглядит следующим образом:


    window.captureEvents(Event.CLICK);

    window.onclick= handle;

    function handle(e) {­
    document.links.handleEvent(e);
    }


    "Кликните" по этой ссылке

    Вторая ссылка

    Все сигналы о событиях Click, посылаются на обработку по второй ссылке - даже если Вы вовсе и не щелкнули ни по одной из ссылок!

    Следующий скрипт демонстрирует, как Ваш скрипт может реагировать на сигналы о нажатии клавиш. Нажмите на какую-либо клавишу и посмотрите, как работает этот скрипт.


    window.captureEvents(Event.KEYPRESS);

    window.onkeypress= pressed;

    function pressed(e) {­
    alert("Key pressed! ASCII-value: " + e.which);
    }

    На этом уроке мы познакомимся с таким понятием как всплытие события, а также рассмотрим, как его можно прервать. Кроме этого выясним, какие ещё этапы (фазы) проходит событие, перед тем как начать всплывать.

    Всплытие события

    Если у некоторого элемента возникает событие, то оно начинает "всплывать", т.е. возникает у родителя, потом у прародителя и т.д.

    Из этого следует, что событие, которое сгенерировал некоторый элемент, можно перехватить с помощью обработчика на родителе, прародителе и т.д.

    Всплытие события (пузырька) продемонстрируем на следующем примере:

    Заголовок

    Некоторый очень важный текст

    Раздел

    Некоторый текст

    Остальной текст

    Напишем небольшой скрипт, с помощью которого добавим обработчик события " click " для всех элементов страницы, а также для объектов document и window .

    document.addEventListener("DOMContentLoaded", function() { var allElements = document.getElementsByTagName("*"); for (var i=0; i < allElements.length; i++) { allElements[i].addEventListener("click",function() {console.log(this.tagName);},false); }; document.addEventListener("click",function() {console.log(this);},false); window.addEventListener("click",function() {console.log(this);},false); });

    Создадим HTML-страницу и вставим в неё вышеприведённый HTML код. Сценарий, написанный на языке JavaScript, вставим перед закрывающим тегом body . После этого откроем только что созданную страницу в веб-браузере, нажмём клавишу F12 и перейдём в консоль. Теперь нажмём левой кнопкой мышкой в области, принадлежащей элементу strong , и посмотрим, как событие будет всплывать.

    Как прервать всплытие события

    Всплытие события (пузырька) можно прервать. В этом случае у вышестоящих (родительских) элементов, данное событие вызвано не будет. Метод, который предназначен для прекращения всплытия события (пузрька) называется stopPropagation() .

    Например, изменим наш вышеприведённый пример таким образом, чтобы событие не всплывало выше body: document.addEventListener("DOMContentLoaded", function() { var allElements = document.getElementsByTagName("*"); for (var i=0; i

    Бесспорно всплытие - это очень удобно и архитектурно прозрачно. Не прекращайте его без явной нужды.

    Получение элемента, который вызвал обработчик

    Для того чтобы получить DOM-элемент (объект), который вызвал обработчик события, необходимо использовать ключевое слово this . Данное ключевое слово (this) доступно в обработчике только в том случае, если Вы подписались на событие с помощью JavaScript.

    Например, выведем в консоль id элемента, который вызвал обработчик события:

    Var myP = document.getElementById("myP"); myP.addEventListener("click",function(){ //получим DOM-элемент, который вызвал обработчик события - this //получим его id и выведем его в консоль console.log(this.id); });

    Для получения текущего элемента также можно использовать свойство currentTarget (event.currentTarget).

    Этапы (фазы) прохода события

    Перед тем как события начинает всплывать (этап всплытия), оно предварительно проходит ещё 2 этапа:

    • 1 этап - это этап погружения до элемента, сгенерировавшего событие. Т.е. на данном этапе происходит движение сверху вниз, т.е. от объекта window до элемента. Также данный этап ещё называют этапом перехвата.
    • 2 этап - это этап достижение цели, т.е. элемента (объекта), сгенерировавшего событие.

    С учётом всех этапов, которые проходит событие, получается следующая картина:

    Изменим сценарий вышеприведённого примера следующим образом:

    Document.addEventListener("DOMContentLoaded", function() { var allElements = document.getElementsByTagName("*"); for (var i=0; i

    Третий параметр методов addEventListener и removeEventListener определяет этап, на котором будет поймано событие. Если данный параметр имеет значение true , то событие будет перехватываться на стадии погружения (перехвата) события. А если параметр имеет значение false , то событие будет перехватываться на этапе всплытия. Для обработки события на самой цели, можно использовать метод addEventListener как со значением false , так и со значением true .

    Внимание: на стадии погружения (перехвата), события могут перехватывать только обработчики, добавленные с помощью метода addEventListener() . Обработчики, добавленные с помощью других способов (атрибута HTML или через JavaScript с помощью свойства on[событие]) могут перехватывать события только на стадии всплытия.

    Получение элемента, который сгенерировал событие

    Для того чтобы получить целевой элемент, т.е. элемент, который сгенерировал событие, необходимо использовать свойство target (event.target).

    Рассмотрим вышеприведённый пример, в котором изменим содержимое элемента script на следующее:

    Document.addEventListener("DOMContentLoaded", function() { var elementBody = document.body; elementBody.addEventListener("click",function(){ console.log(this.tagName + " - элемент, который вызвал обработчик"); console.log(event.currentTarget.tagName + " - элемент, который вызвал обработчик"); console.log(event.target.tagName + " - элемент, который сгенерировал событие"); },false); });

    Продемонстрируем наш пример, кликнув левой кнопкой мыши в области, принадлежащей элементу strong:

    Сейчас мы с вами разберем некоторые продвинутые вещи при работе с объектом Event, а именно: всплытие и перехват, а также делегирование событий.

    Всплытие событий

    Представьте себе, что у вас есть несколько вложенных друг в друга блоков:

    самый внутренний блок

    Когда вы кликаете на самый внутренний блок, событие onclick возникает сначала в нем, а затем срабатывает в его родителе, в родителе его родителя и так далее, пока не дойдет то тега body и далее до тега html (затем до document и до window ).

    И это логично, ведь кликая на внутренний блок, вы одновременно кликаете на все внешние.

    Давайте убедимся в этом на следующем примере: у нас есть 3 блока, к каждому из них привязано событие onclick:

    Нажмите на самый внутренний красный блок - и вы увидите, как сначала сработает onclick красного блока, потом голубого, потом зеленого:

    Такое поведение называется всплытием событий - по аналогии со всплытием пузырька воздуха со дна. Так же, как и пузырек, наш клик по внутреннему элементу как будто выплывает наверх, каждый раз срабатывая на более высоких блоках.

    event.target

    Пусть у нас есть два элемента: div и абзац p, лежащий внутри этого дива. Пусть onlick мы привязали в диву:

    Когда мы кликаем на этот див, мы можем попасть по абзацу, а можем попасть в место, где этого абзаца нет.

    Как такое может быть - посмотрите на следующем примере: зеленый цвет - это наш див, а голубой - наш абзац:

    Если кликнуть в зеленую часть - мы кликнем именно по диву, а если кликнуть на голубую часть - клик произойдет сначала по абзацу, а потом уже по диву. Но так как onclick привязан именно к диву - мы в общем-то присутствие абзаца можем и не заметить.

    Однако, иногда нам хотелось бы знать - клик произошел непосредственно по диву или по его потомку абзацу. В этом нам поможет объект Event и его свойство event.target - в нем хранится именно тот элемент, в котором произошел клик.

    В следующем примере у нас есть div , внутри него лежит p , а внутри него - span .

    Давайте привяжем событие onclick самому верхнему элементу (диву) и будем кликать на разные элементы: на div, на p, на span. С помощью event.target получим самый нижний элемент, в котором случилось событие и выведем его название с помощью tagName .

    Если кликнуть, к примеру, на span - то событие отловит наш div (ведь именно к нему привязан onclick), но в event.target будет лежать именно span :

    Покликайте по разным блокам - вы увидите результат:

    Прекращение всплытия

    Итак, вы уже знаете, что все события всплывают до самого верха (до тега html, а затем до document, а затем до window). Иногда есть нужда это всплытие остановить. Это может сделать любой элемент, через который всплывает событие. Для этого в коде элемента следует вызвать метод event.stopPropagation() .

    В следующем примере клик по красному блоку сработает на нем самом, затем на голубом блоке и все - голубой блок прекращает дальнейшее всплытие и зеленый блок уже никак не отреагирует:

    Кликните на красный блок - вы увидите результат:

    Погружение

    Кроме всплытия событий есть еще и погружение (по научному стадия перехвата ). Это значит, что событие сначала идет сверху вниз (стадия перехвата), доходит до нашего элемента (стадия цели) и только потом начинает всплывать (стадия всплытия).

    Повесить обработчик события с учетом стадии перехвата можно только с помощью addEventListener . Для этого у него есть третий параметр: если он равен true - событие сработает на стадии перехвата, а если false - на стадии всплытия (это по умолчанию):

    Var green = document.getElementById("green"); green.addEventListener("click", func, true); function func(event) { }

    Стадию, на которой произошло событие можно определить с помощью свойства event.eventPhase . Оно может принимать следующие значения: 1 - стадия перехвата, 2 - стадия цели, 3 - стадия всплытия.

    Вступление к делегированию

    Представим себе ситуацию: пусть у нас есть ul с несколькими li . К каждой li привязано следующее событие: по нажатию на li ей в конец добавляется "!".

    Давайте реализуем описанное:

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5
    var li = document.querySelectorAll("#ul li"); //В цикле вешаем функцию addSign на каждую li: for (var i = 0; i

    Понажимайте на li - вы увидите, как им в конец добавляется "!":

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5

    Пусть теперь у нас также есть кнопочка, по нажатию на которую в конец ul добавляется новая li с текстом "пункт". Нас ждет сюрприз: привязанное событие не будет работать для новых li! Убедимся в этом:

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5
    Добавить li

    Нажмите на кнопочку для добавления li, а затем на эту новую li - она не среагирует:

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5
    Добавить li

    Для решения проблемы можно в момент создания новой li повесить на нее функцию addSign через addEventListener. Давайте реализуем это:

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5
    Добавить li var li = document.querySelectorAll("#ul li"); for (var i = 0; i

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5
    Добавить li

    Существует и второй способ обойти проблему - делегирование событий. Давайте его разберем.

    Делегирование событий

    Суть делегирования в следующем: навесим событие не на каждую li, а на их родителя - на ul .

    При этом работоспособность нашего скрипта должна сохраниться: по-прежнему при клике на li ей в конец будет добавляться "!". Только событие в новом варианте будет навешано на ul:

    Var ul = document.getElementById("ul"); //Вешаем событие на ul: ul.addEventListener("click", addSign); function addSign() { }

    Как мы это провернем: так как событие навешано на ul, то внутри функции мы можем поймать li с помощью event.target . Напомню, что такое event.target - это именно тот тег, в котором случился клик, в нашем случае это li .

    Итак, вот решение нашей задачи через делегирование:

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5

    Результат выполнения кода:

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5

    При этом наше решение будет работать автоматически даже для новых li , ведь событие навешено не на li, а на ul:

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5
    Добавить li var ul = document.getElementById("ul"); ul.addEventListener("click", addSign); function addSign() { event.target.innerHTML = event.target.innerHTML + "!"; } //Реализация кнопочки добавления новой li: var button = document.getElementById("button"); button.addEventListener("click", addLi); function addLi() { var li = document.createElement("li"); li.innerHTML = "новая li"; ul.appendChild(li); }

    Нажмите на кнопочку для добавления li, а затем на эту новую li - она среагирует:

    • пункт 1
    • пункт 2
    • пункт 3
    • пункт 4
    • пункт 5
    Добавить li

    Наш код рабочий, однако не без недостатков. Давайте разберем эти недостатки и напишем более универсальное решение.

    Универсальное делегирование событий

    Недостаток нашего кода проявится в том случае, когда внутри li будут какие-то вложенные теги. В нашем случае пусть это будут теги i :

    В этом случае нажатие на i приведет к добавлению восклицательного знака в конец тега i , а не тега li , как мы хотели бы (если нажать на li вне курсива - то все будет ок):

    • пункт курсив 1
    • пункт курсив 2
    • пункт курсив 3
    • пункт курсив 4
    • пункт курсив 5
    var ul = document.getElementById("ul"); ul.addEventListener("click", addSign); function addSign() { event.target.innerHTML = event.target.innerHTML + "!"; }

    Нажмите на курсив - вы увидите как "!" добавится ему в конец (нажатие вне курсива будет работать нормально):

    Проблема исправляется следующим образом (описанный способ не единственный, но самый простой): с помощью метода closest найдем ближайшую li, котоорая является родителем для event.target вот так: event.target.closest("li") .

    Как это работает: если клик был на i , то в event.target лежит этот i, а в event.target.closest("li") - наша li, для которой должно сработать событие.

    Если же клик был на самой li , то и в event.target , и в event.target.closest("li") будет лежать наша li.

    Давайте проверим:

    • пункт курсив 1
    • пункт курсив 2
    • пункт курсив 3
    • пункт курсив 4
    • пункт курсив 5
    var ul = document.getElementById("ul"); ul.addEventListener("click", function(event) { var li = event.target.closest("li"); if (li) { //проверяем, вдруг li-родителя вообще нет li.innerHTML = li.innerHTML + "!"; } });

    Результат выполнения кода:

    Не важно, какая глубина вложенности: тег i может лежать в теге b , а тот в теге span и только потом в li - это не имеет значения: конструкция event.target.closest("li") найдет родителя из любого уровня вложенности.

    Тонкости