Этот обработчик для сработает, если вы кликните по любому из вложенных тегов, будь то или :
Если вы кликните на EM, сработает обработчик на DIV
Вам не кажется это странным? Почему же сработал обработчик на , если клик произошёл на ?
Всплытие
Принцип всплытия очень простой.
Когда на элементе происходит событие, обработчики сначала срабатывают на нём, потом на его родителе, затем выше и так далее, вверх по цепочке предков.
Например, есть 3 вложенных элемента FORM > DIV > P с обработчиком на каждом:
body *
Клик по внутреннему
вызовет обработчик onclick :
Сначала на самом
.
Потом на внешнем .
Затем на внешнем .
И так далее вверх по цепочке до самого document .
Поэтому если кликнуть на
, то мы увидим три оповещения: p → div → form .
Этот процесс называется «всплытием», потому что события «всплывают» от внутреннего элемента вверх через родителей подобно тому, как всплывает пузырёк воздуха в воде.
Почти все события всплывают.
Ключевое слово в этой фразе – «почти».
Например, событие focus не всплывает. В дальнейшем мы увидим и другие примеры. Однако, стоит понимать, что это скорее исключение, чем правило, всё-таки большинство событий всплывают.
event.target
Всегда можно узнать, на каком конкретно элементе произошло событие.
Самый глубокий элемент, который вызывает событие, называется целевым элементом, и он доступен через event.target .
Отличия от this (= event.currentTarget ):
event.target – это «целевой» элемент, на котором произошло событие, в процессе всплытия он неизменен.
this – это «текущий» элемент, до которого дошло всплытие, на нём сейчас выполняется обработчик.
Например, если стоит только один обработчик form.onclick , то он «поймает» все клики внутри формы. Где бы ни был клик внутри – он всплывёт до элемента , на котором сработает обработчик.
При этом внутри обработчика form.onclick :
this (= event.currentTarget ) всегда будет элемент , так как обработчик сработал на ней.
event.target будет содержать ссылку на конкретный элемент внутри формы, на котором произошёл клик.
example.css
form.onclick = function(event) < event.target.style.backgroundColor = 'yellow'; // браузеру нужно некоторое время, чтобы зарисовать всё жёлтым setTimeout(() =>< alert("target = " + event.target.tagName + ", this=" + this.tagName); event.target.style.backgroundColor = '' >, 0); >;
Клик покажет оба: и event.target, и this для сравнения:
Возможна и ситуация, когда event.target и this – один и тот же элемент, например, если клик был непосредственно на самом элементе , а не на его подэлементе.
Прекращение всплытия
Всплытие идёт с «целевого» элемента прямо наверх. По умолчанию событие будет всплывать до элемента , а затем до объекта document , а иногда даже до window , вызывая все обработчики на своём пути.
Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие.
Для этого нужно вызвать метод event.stopPropagation() .
Например, здесь при клике на кнопку обработчик body.onclick не сработает:
event.stopImmediatePropagation()
Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены.
То есть, event.stopPropagation() препятствует продвижению события дальше, но на текущем элементе все обработчики будут вызваны.
Для того, чтобы полностью остановить обработку, существует метод event.stopImmediatePropagation() . Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.
Не прекращайте всплытие без необходимости!
Всплытие – это удобно. Не прекращайте его без явной нужды, очевидной и архитектурно прозрачной.
Зачастую прекращение всплытия через event.stopPropagation() имеет свои подводные камни, которые со временем могут стать проблемами.
Мы делаем вложенное меню. Каждое подменю обрабатывает клики на своих элементах и делает для них stopPropagation , чтобы не срабатывало внешнее меню.
Позже мы решили отслеживать все клики в окне для какой-то своей функциональности, к примеру, для статистики – где вообще у нас кликают люди. Некоторые системы аналитики так делают. Обычно используют document.addEventListener(‘click’…) , чтобы отлавливать все клики.
Наша аналитика не будет работать над областью, где клики прекращаются stopPropagation . Увы, получилась «мёртвая зона».
Зачастую нет никакой необходимости прекращать всплытие. Задача, которая, казалось бы, требует этого, может быть решена иначе. Например, с помощью создания своего уникального события, о том, как это делать, мы поговорим позже. Также мы можем записывать какую-то служебную информацию в объект event в одном обработчике, а читать в другом, таким образом мы можем сообщить обработчикам на родительских элементах информацию о том, что событие уже было как-то обработано.
Погружение
Существует ещё одна фаза из жизненного цикла события – «погружение» (иногда её называют «перехват»). Она очень редко используется в реальном коде, однако тоже может быть полезной.
Стандарт DOM Events описывает 3 фазы прохода события:
Фаза погружения (capturing phase) – событие сначала идёт сверху вниз.
Фаза цели (target phase) – событие достигло целевого(исходного) элемента.
Фаза всплытия (bubbling stage) – событие начинает всплывать.
Ранее мы говорили только о всплытии, потому что другие стадии, как правило, не используются и проходят незаметно для нас.
Обработчики, добавленные через on -свойство или через HTML-атрибуты, или через addEventListener(event, handler) с двумя аргументами, ничего не знают о фазе погружения, а работают только на 2-ой и 3-ей фазах.
Чтобы поймать событие на стадии погружения, нужно использовать третий аргумент capture вот так:
elem.addEventListener(. ) // или просто "true", как сокращение для elem.addEventListener(. true)
Существуют два варианта значений опции capture :
Если аргумент false (по умолчанию), то событие будет поймано при всплытии.
Если аргумент true , то событие будет перехвачено при погружении.
Обратите внимание, что хоть и формально существует 3 фазы, 2-ую фазу («фазу цели»: событие достигло элемента) нельзя обработать отдельно, при её достижении вызываются все обработчики: и на всплытие, и на погружение.
Давайте посмотрим и всплытие и погружение в действии:
body *
Здесь обработчики навешиваются на каждый элемент в документе, чтобы увидеть в каком порядке они вызываются по мере прохода события.
Если вы кликните по
, то последовательность следующая:
HTML → BODY → FORM → DIV (фаза погружения, первый обработчик)
P (фаза цели, срабатывают обработчики, установленные и на погружение и на всплытие, так что выведется два раза)
DIV → FORM → BODY → HTML (фаза всплытия, второй обработчик)
Существует свойство event.eventPhase , содержащее номер фазы, на которой событие было поймано. Но оно используется редко, мы обычно и так знаем об этом в обработчике.
Чтобы убрать обработчик removeEventListener , нужна та же фаза
Если мы добавили обработчик вот так addEventListener(. true) , то мы должны передать то же значение аргумента capture в removeEventListener(. true) , когда снимаем обработчик.
На каждой фазе разные обработчики на одном элементе срабатывают в порядке назначения
Если у нас несколько обработчиков одного события, назначенных addEventListener на один элемент, в рамках одной фазы, то их порядок срабатывания – тот же, в котором они установлены:
elem.addEventListener("click", e => alert(1)); // всегда сработает перед следующим elem.addEventListener("click", e => alert(2));
Итого
При наступлении события – самый глубоко вложенный элемент, на котором оно произошло, помечается как «целевой» ( event.target ).
Затем событие сначала двигается вниз от корня документа к event.target , по пути вызывая обработчики, поставленные через addEventListener(. true) , где true – это сокращение для .
Далее обработчики вызываются на целевом элементе.
Далее событие двигается от event.target вверх к корню документа, по пути вызывая обработчики, поставленные через on и addEventListener без третьего аргумента или с третьим аргументом равным false .
Каждый обработчик имеет доступ к свойствам события event :
event.target – самый глубокий элемент, на котором произошло событие.
event.currentTarget (= this ) – элемент, на котором в данный момент сработал обработчик (тот, на котором «висит» конкретный обработчик)
event.eventPhase – на какой фазе он сработал (погружение=1, фаза цели=2, всплытие=3).
Любой обработчик может остановить событие вызовом event.stopPropagation() , но делать это не рекомендуется, так как в дальнейшем это событие может понадобиться, иногда для самых неожиданных вещей.
В современной разработке стадия погружения используется очень редко, обычно события обрабатываются во время всплытия. И в этом есть логика.
В реальном мире, когда происходит чрезвычайная ситуация, местные службы реагируют первыми. Они знают лучше всех местность, в которой это произошло, и другие детали. Вышестоящие инстанции подключаются уже после этого и при необходимости.
Всплытие и погружение являются основой для «делегирования событий» – очень мощного приёма обработки событий. Его мы изучим в следующей главе.
Всплытие и перехват
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/bubbling-and-capturing.
Давайте сразу начнём с примера.
Этот обработчик для сработает, если вы кликните по вложенному тегу или :
Кликните на EM, сработает обработчик на DIV
Вам не кажется это странным? Почему же сработал обработчик на , если клик произошёл на ?
Всплытие
Основной принцип всплытия:
При наступлении события обработчики сначала срабатывают на самом вложенном элементе, затем на его родителе, затем выше и так далее, вверх по цепочке вложенности.
Например, есть 3 вложенных элемента FORM > DIV > P , с обработчиком на каждом:
body *
Всплытие гарантирует, что клик по внутреннему
вызовет обработчик onclick (если есть) сначала на самом
, затем на элементе далее на элементе , и так далее вверх по цепочке родителей до самого document .
Поэтому если в примере выше кликнуть на P , то последовательно выведутся alert : p → div → form .
Этот процесс называется всплытием, потому что события «всплывают» от внутреннего элемента вверх через родителей, подобно тому, как всплывает пузырёк воздуха в воде.
Всплывают почти все события.
Ключевое слово в этой фразе – «почти».
Например, событие focus не всплывает. В дальнейших главах мы будем детально знакомиться с различными событиями и увидим ещё примеры.
Целевой элемент event.target
На каком бы элементе мы ни поймали событие, всегда можно узнать, где конкретно оно произошло.
Самый глубокий элемент, который вызывает событие, называется «целевым» или «исходным» элементом и доступен как event.target .
Отличия от this (= event.currentTarget ):
event.target – это исходный элемент, на котором произошло событие, в процессе всплытия он неизменен.
this – это текущий элемент, до которого дошло всплытие, на нём сейчас выполняется обработчик.
Например, если стоит только один обработчик form.onclick , то он «поймает» все клики внутри формы. Где бы ни был клик внутри – он всплывёт до элемента , на котором сработает обработчик.
this ( =event.currentTarget ) всегда будет сама форма, так как обработчик сработал на ней.
event.target будет содержать ссылку на конкретный элемент внутри формы, самый вложенный, на котором произошёл клик.
Возможна и ситуация, когда event.target и this – один и тот же элемент, например если в форме нет других тегов и клик был на самом элементе .
Прекращение всплытия
Всплытие идёт прямо наверх. Обычно событие будет всплывать наверх и наверх, до элемента , а затем до document , а иногда даже до window , вызывая все обработчики на своём пути.
Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие.
Для остановки всплытия нужно вызвать метод event.stopPropagation() .
Например, здесь при клике на кнопку обработчик body.onclick не сработает:
event.stopImmediatePropagation()
Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены.
То есть, stopPropagation препятствует продвижению события дальше, но на текущем элементе все обработчики отработают.
Для того, чтобы полностью остановить обработку, современные браузеры поддерживают метод event.stopImmediatePropagation() . Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.
Не прекращайте всплытие без необходимости!
Всплытие – это удобно. Не прекращайте его без явной нужды, очевидной и архитектурно прозрачной.
Зачастую прекращение всплытия создаёт свои подводные камни, которые потом приходится обходить.
Мы делаем меню. Оно обрабатывает клики на своих элементах и делает для них stopPropagation . Вроде бы, всё работает.
Позже мы решили отслеживать все клики в окне, для какой-то своей функциональности, к примеру, для статистики – где вообще у нас кликают люди. Например, Яндекс.Метрика так делает, если включить соответствующую опцию.
Над областью, где клики убиваются stopPropagation , статистика работать не будет! Получилась «мёртвая зона».
Проблема в том, что stopPropagation убивает всякую возможность отследить событие сверху, а это бывает нужно для реализации чего-нибудь «эдакого», что к меню отношения совсем не имеет.
Погружение
В современном стандарте, кроме «всплытия» событий, предусмотрено ещё и «погружение».
Оно гораздо менее востребовано, но иногда, очень редко, знание о нём может быть полезным.
Строго говоря, стандарт выделяет целых три стадии прохода события:
Событие сначала идёт сверху вниз. Эта стадия называется «стадия перехвата» (capturing stage).
Событие достигло целевого элемента. Это – «стадия цели» (target stage).
После этого событие начинает всплывать. Это – «стадия всплытия» (bubbling stage).
В стандарте DOM Events 3 это продемонстрировано так:
То есть, при клике на TD событие путешествует по цепочке родителей сначала вниз к элементу («погружается»), а потом наверх («всплывает»), по пути задействуя обработчики.
Ранее мы говорили только о всплытии, потому что другие стадии, как правило, не используются и проходят незаметно для нас.
Обработчики, добавленные через on. -свойство, ничего не знают о стадии перехвата, а начинают работать со всплытия.
Чтобы поймать событие на стадии перехвата, нужно использовать третий аргумент addEventListener :
Если аргумент true , то событие будет перехвачено по дороге вниз.
Если аргумент false , то событие будет поймано при всплытии.
Стадия цели, обозначенная на рисунке цифрой (2) , особо не обрабатывается, так как обработчики, назначаемые обоими этими способами, срабатывают также на целевом элементе.
Есть события, которые не всплывают, но которые можно перехватить
Бывают события, которые можно поймать только на стадии перехвата, а на стадии всплытия – нельзя…
Например, таково событие фокусировки на элементе onfocus. Конечно, это большая редкость, такое исключение существует по историческим причинам.
Примеры
В примере ниже на , ,
стоят те же обработчики, что и раньше, но на этот раз – на стадии погружения. Чтобы увидеть перехват в действии, кликните в нём на элементе
:
Свойство event.target
Свойство event.target содержит элемент, на котором сработало событие. Это не тот элемент, к которому был привязан обработчик этого события, а именно самый глубокий тег, на который непосредственно был, к примеру, совершен клик.
Синтаксис
event.target;
Пример
Пусть у нас есть div , а внутри него абзац. Привяжем событие к диву, но кликнем по абзацу — в этом случае event.target будет содержать конечный тег, в котором случилось событие — то есть абзац, а не див. Убедимся в этом с помощью tagName :
text
let div = document.querySelector(‘#div’); div.addEventListener(‘click’, function(event) < console.log(event.target); // выведет ссылку на абзац >);
Смотрите также
свойство event.currentTarget , содержащее элемент, к которому привязано событие
свойство code , которое получает код нажатой клавиши
свойство event.key , которое получает введенный символ
1. Распространение событий
Распространение событий (event propagation) — важная, но непонятная тема, когда речь идет о событиях. Это всеобъемлющий термин, который включает в себя три разных этапа жизни события: затопление, таргетинг и всплытие.
Распространение события двунаправленно — оно начинается на window , идет к целевому элементу и заканчивается на window . Распространение часто неправильно используется как синоним стадии всплытия. Каждый раз, когда происходит событие, происходит его распространение.
При наступлении события, оно проходит через три обязательные фазы:
Capture phase — событие начинается на window и тонет (проходит через все узлы-предки ) до самого глубокого целевого элемента где произошло событие.
Target phase — событие дошло до самого глубокого целевого элемента. Этот этап включает только уведомление узла на котором произошло событие.
Bubbling phase — заключительная фаза, событие всплывает от самого глубокого, целевого элемента, через все узлы-предки до window .
Мы рассмотрим фазу всплытия, так как она наиболее часто используемая и полезная. Для ознакомления с остальными фазами и более детальной информации прочитайте документацию на MDN.
2. Всплытие событий
Основной принцип всплытия — при наступлении события, обработчики сначала срабатывают на самом вложенном элементе, затем на его родителе, затем выше и так далее, вверх по цепочке вложенности. Всплывают почти все события, например события focus и blur не всплывают.
Давайте рассмотрим пример, так будет понятнее. Есть три вложенных div , с обработчиками клика на каждом. Всплытие гарантирует, что клик по внутреннему inner-child вызовет обработчик клика, если есть, сначала на самом inner-child , затем на элементе child , далее на элементе parent и так далее вверх по цепочке родителей до window . Поэтому если в примере кликнуть на inner-child , то последовательно выведутся alert: inner-child → child → parent .
Этот процесс называется всплытием (event bubbling), потому что события всплывают от внутреннего элемента вверх через предков, подобно тому, как всплывает пузырек воздуха в воде.
2.1. event.target
Целевой элемент — на каком бы элементе мы ни поймали событие, всегда можно узнать где конкретно оно произошло. Самый глубокий элемент, который вызывает событие, называется целевым или исходным и доступен как event.target .
Отличия event.target и event.currentTarget :
event.target — это ссылка на исходный элемент на котором произошло событие, в процессе всплытия он неизменен.
event.currentTarget — это текущий элемент до которого дошло всплытие, на нём сейчас выполняется обработчик.
Если стоит только один обработчик на самом верхнем элементе, то он поймает все клики внутри родителя. Где бы ни был клик внутри, он всплывёт до элемента-родителя на котором сработает обработчик.
2.2. Прекращение всплытия
Обычно событие будет всплывать наверх до элемента window , вызывая все обработчики на своем пути. Но любой промежуточный обработчик может решить, что событие полностью обработано и остановить всплытие. Остановить всплытие можно, вызвав метод на объекте события.
event.stopPropagation()
Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены. То есть, stopPropagation препятствует продвижению события дальше, но на текущем элементе все обработчики выполнятся.
event.stopImmediatePropagation()
Используется для того, чтобы полностью остановить обработку события. Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.
Не прекращайте всплытие без необходимости, всплытие – это удобно! Прекращение всплытия создаёт свои подводные камни, которые потом приходится обходить.
3. Делегирование событий
Всплытие событий позволяет реализовать один из самых важных приёмов разработки — делегирование событий. Он заключается в том, что если есть много элементов, события которых нужно обрабатывать похожим образом, то вместо того, чтобы назначать обработчик каждому, мы ставим один обработчик на их общего предка. Из него можно получить целевой элемент event.target , понять на каком именно потомке произошло событие и обработать его.
Делегирование (event delegation) — это средство оптимизации интерфейса. Мы используем один обработчик для схожих действий на однотипных элементах.
Рассмотрим делегирование на примере. Создаем элемент div , добавляем в него двести параграфов и вешаем слушатели событий с функцией respondToTheClick к каждому параграфу.
Проблема в том, что у нас есть двести слушателей событий. Все они указывают на одну и ту же функцию слушателя, но самих слушателей 200! Что если мы переместим всех слушателей на общего родителя?
Теперь есть только один слушатель и браузеру не нужно хранить в памяти двести различных функций. Это — существенная оптимизация. Теперь для доступа к элементу на котором произошло событие, используем свойство target на объекте события в котором хранится ссылка на целевой элемент.
При клике в параграф происходит приблизительно следующее:
Произошел клик в элемент параграфа
Событие проходит этап захвата (capturing phase) и достигает цели
Теперь наступает фаза всплытия и событие поднимается по иерархии DOM-дерева
Когда событие всплывает до div , срабатывает слушатель и вызывается функция respondToTheClick
Внутри callback-функции, event.target — это элемент, на котором произошел клик. Таким образом, мы получаем прямой доступ к целевому элементу, абзацу, и можем работать с ним.
Такой подход имеет ряд преимуществ.
Упрощает инициализацию и экономит память — не нужно вешать много обработчиков.
Меньше кода — при добавлении и удалении элементов не нужно ставить или снимать обработчики.
Удобство изменений — можно массово добавлять или удалять элементы изменения, при этом на добавленных элементах будет весь функционал.
3.1. Пример
Будем делать подсветку активного элемента навигации при клике. Вместо того, чтобы назначать обработчик каждому элементу навигации, которых может быть много, повесим единый обработчик на список. Используем event.target , чтобы получить элемент на котором произошло событие, и подсветить его.
Обязательно проверяем цель клика, чтобы это точно был элемент ссылки, мы не хотим подсвечивать весь список или его элементы, только ссылки. Для проверки типа узла используем свойство nodeName .