Источник: http://learn.javascript.ru/bubbling-and-capturing
Элементы DOM могут быть вложены друг в друга. При этом обработчик, привязанный к родителю, срабатывает, даже если посетитель кликнул по потомку.
Это происходит потому, что событие всплывает.
Например, этот обработчик для DIV
сработает, если вы кликните по вложенному тегу EM
или CODE
:
<div onclick="alert('Обработчик для Div сработал!')">
<em>Кликните на <code>EM</code>, сработает обработчик на <code>DIV</code></em>
</div>
Основной принцип всплытия:
После того, как событие сработает на самом вложенном элементе, оно также сработает на родителях, вверх по цепочке вложенности.
Например, есть 3 вложенных блока:
<!DOCTYPE HTML>
<html>
<body>
<link type="text/css" rel="stylesheet" href="example.css">
<div class="d1">1 <!-- внешний (topmost) -->
<div class="d2">2
<div class="d3">3 <!-- внутренний (innermost) -->
</div>
</div>
</div>
</body>
</html>
Всплытие гарантирует, что клик по внутреннему div 3
вызовет событие onclick
сначала на внутреннем элементе 3, затем на элементе 2 и в конце концов на элементе 1.
Этот процесс называется всплытием, потому что события «всплывают» от внутреннего элемента вверх через родителей, подобно тому, как всплывает пузырек воздуха в воде.
Элемент, на котором сработал обработчик, доступен через this.
Например, повесим на клик по каждому DIV
функцию highlight
, которая подсвечивает текущий элемент:
<!DOCTYPE HTML>
<html>
<body>
<link type="text/css" rel="stylesheet" href="example.css">
<div class="d1" onclick="highlight(this)">1
<div class="d2" onclick="highlight(this)">2
<div class="d3" onclick="highlight(this)">3
</div>
</div>
</div>
<script>
function highlight(elem) {
elem.style.backgroundColor = 'yellow';
alert(elem.className);
elem.style.backgroundColor = '';
}
</script>
</body>
</html>
Демонстрация: http://learn.javascript.ru/files/tutorial/browser/events/bubbling/bubble/index.html
Также существует свойство объекта события event.currentTarget
.
Оно имеет тот же смысл, что и this
, поэтому с первого взгляда избыточно, но иногда гораздо удобнее получить текущий элемент из объекта события.
IE8- при назначении обработчика через attachEvent
не передаёт this
.
Браузеры IE8- не предоставляют this
при назначении через attachEvent
. Также в них нет свойства event.currentTarget
.
Если вы будете использовать фреймворк для работы с событиями, то это не важно, так как он всё исправит. А при разработке на чистом JS имеет смысл вешать обработчики через onсобытие: это и кросс-браузерно, и this
всегда есть.
Самый глубокий элемент, который вызывает событие, называется «целевым» или «исходным» элементом.
В IE<9 он доступен как event.srcElement
, остальные браузеры используют event.target
. Кроссбраузерное решение выглядит так:
var target = event.target || event.srcElement;
event.target/srcElement
- означает исходный элемент, на котором произошло событие.this
- текущий элемент, до которого дошло всплытие и который запускает обработчик.Демонстрация: http://learn.javascript.ru/files/tutorial/browser/events/bubbling/bubble-target/index.html
Всплытие идет прямо наверх. Обычно оно будет всплывать до <HTML>
, а затем до document
, вызывая все обработчики на своем пути.
Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие.
Сценарий, при котором это может быть нужно:
Код для остановки всплытия различается между IE<9 и остальными браузерами:
Стандартный код — это вызов метода:
event.stopPropagation()
Для IE<9 — это назначение свойства:
event.cancelBubble = true
Кросс-браузерное решение:
element.onclick = function(event) {
event = event || window.event; // Кроссбраузерно получить событие
if (event.stopPropagation) { // существует ли метод?
// Стандартно:
event.stopPropagation();
} else {
// Вариант IE
event.cancelBubble = true;
}
}
Есть еще вариант записи в одну строчку:
event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true);
Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены.
Например, если на ссылке есть два onclick-обработчика, то остановка всплытия для одного из них никак не скажется на другом. Это логично, учитывая то, что, как мы уже говорили ранее, браузер не гарантирует взаимный порядок выполнения этих обработчиков. Они полностью независимы, и из одного нельзя отменить другой.
Всплытие – это удобно. Не прекращайте его без явной нужды, очевидной и архитектурно прозрачной.
Зачастую прекращение всплытия создаёт свои подводные камни, которые потом приходится обходить.
Например, вы для одного компонента интерфейса сделали stopPropagation
на событие click
. А позже, на совсем другом месте страницы понадобилось отследить «клик вне элемента» — скажем, чтобы закрыть пункт меню. Обычно для этого ставят обработчик document.onclick
и по event.target
проверяют, внутри был клик или нет. Но над областью, где клики убиваются stopPropagation
, такой способ будет нерабочим!
Во всех браузерах, кроме IE<9, есть три стадии прохода события.
Событие сначала идет сверху вниз. Эта стадия называется «стадия перехвата» (capturing stage). Событие достигло целевого элемента. Это — «стадия цели» (target stage). После этого событие начинает всплывать. Это — «стадия всплытия» (bubbling stage). Получается такая картина:
Третий аргумент addEventListener позволяет задать стадию, на которой будет поймано событие.
true
, то событие будет перехвачено по дороге вниз.false
, то событие будет поймано при всплытии.Стадия цели как-то особо не обрабатывается, но обработчики, назначаемые на стадии захвата и всплытия, срабатывают также на целевом элементе.
Обработчики, добавленные другими способами, ничего не знают о стадии перехвата, а начинают работать со всплытия.
Код:
<!DOCTYPE HTML>
<html>
<body>
<link type="text/css" rel="stylesheet" href="example.css">
<div class="d1">1
<div class="d2">2
<div class="d3">3
</div>
</div>
</div>
<script>
var divs = document.getElementsByTagName('div');
// на каждый DIV повесить обработчик на стадии захвата
for(var i = 0; i < divs.length; i++) {
divs[i].addEventListener("click", highlightThis, true);
}
function highlightThis() {
this.style.backgroundColor = 'yellow';
alert(this.className);
this.style.backgroundColor = '';
}
</script>
</body>
</html>
Демонстрация: http://learn.javascript.ru/files/tutorial/browser/events/bubbling/capture/index.html
Чтобы назначить обработчики для обеих стадий, добавим новое событие: var divs = document.getElementsByTagName('div');
for (var i=0; i<divs.length; i++) {
divs[i].addEventListener("click", highlightThis, true);
divs[i].addEventListener("click", highlightThis, false);
}
Как видно из примера, один и тот же обработчик можно назначить на разные стадии. При этом номер текущей стадии он, при необходимости, может получить из свойства event.eventPhase
.
addEventListener
с последним аргументом true.event.stopPropagation()
. В IE<9 нужно использовать для этого event.cancelBubble=true
.