preventDefault
и stopPropagation
: когда использовать и что именно делает каждый метод.
Event.stopPropagation() и Event.preventDefault()
Обработка событий JavaScript часто проста. Это особенно актуально при работе с простой (относительно плоской) структурой HTML. Однако все становится немного сложнее, когда события путешествуют (или распространяются) через иерархию элементов. Обычно это происходит тогда, когда разработчики обращаются к stopPropagation()
и/или preventDefault()
чтобы решить проблемы, с которыми они столкнулись. Если вы когда-нибудь думали про себя: «Я просто попробую preventDefault()
, а если это не сработает, я попробую stopPropagation()
, а если это не сработает, я попробую оба варианта», то эта статья для вас! Я подробно объясню, что делает каждый метод, когда какой из них использовать, и предоставлю вам множество рабочих примеров для изучения. Моя цель — положить конец вашему замешательству раз и навсегда.
Однако прежде чем мы углубимся в подробности, важно кратко коснуться двух видов обработки событий, возможных в JavaScript (то есть во всех современных браузерах — Internet Explorer до версии 9 вообще не поддерживал перехват событий).
Стили событий (захват и всплывание)
Все современные браузеры поддерживают перехват событий, но разработчиками он используется очень редко. Интересно, что это была единственная форма событий, которую изначально поддерживал Netscape. Крупнейший конкурент Netscape, Microsoft Internet Explorer, вообще не поддерживал захват событий, а лишь поддерживал другой стиль обработки событий, называемый барботированием событий. Когда был создан W3C, они нашли преимущества в обоих стилях обработки событий и заявили, что браузеры должны поддерживать оба, посредством третьего параметра метода addEventListener
. Первоначально этот параметр был просто логическим значением, но все современные браузеры поддерживают объект options
в качестве третьего параметра, который вы можете использовать, чтобы указать (помимо прочего), хотите ли вы использовать перехват событий или нет:
someElement.addEventListener('click', myClickHandler, { capture: true | false });
Обратите внимание, что объект options
не является обязательным, как и его свойство capture
. Если какой-либо из них опущен, значением по умолчанию для capture
является false
, что означает, что будет использоваться всплытие событий.
Захват событий
Что означает, если ваш обработчик событий «прослушивает фазу захвата»? Чтобы понять это, нам нужно знать, как возникают события и как они распространяются. Следующее справедливо для всех событий, даже если вы, как разработчик, не используете их, не заботитесь об этом и не думаете об этом.
Все события начинаются в окне и сначала проходят фазу захвата. Это означает, что когда событие отправляется, оно запускает окно и сначала перемещается «вниз» к своему целевому элементу. Это происходит, даже если вы слушаете только на этапе «пузырения». Рассмотрим следующий пример разметки и JavaScript:
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('#C was clicked');
},
true,
);
Когда пользователь нажимает на элемент #C
, отправляется событие, происходящее в window
. Затем это событие будет распространяться через своих потомков следующим образом:
window
=> document
=> <html>
=> <body>
=> и так далее, пока не достигнет цели.
Не имеет значения, прослушивает ли ничто событие щелчка в window
или document
, элементе <html>
или элементе <body>
(или любом другом элементе на пути к своей цели). Событие по-прежнему зарождается в window
и начинает свое путешествие, как только что описано.
В нашем примере событие щелчка затем будет распространяться (это важное слово, поскольку оно напрямую связано с тем, как работает метод stopPropagation()
и будет объяснено позже в этом документе) от window
к его целевому элементу (в данном случае #C
) через каждый элемент между window
и #C
.
Это означает, что событие щелчка начнется в window
, и браузер задаст следующие вопросы:
«Прослушивает ли что-нибудь событие щелчка в window
на этапе захвата?» В этом случае сработают соответствующие обработчики событий. В нашем примере ничего нет, поэтому никакие обработчики не сработают.
Затем событие распространится на document
, и браузер спросит: «Прослушивает ли что-нибудь событие щелчка по document
на этапе захвата?» В этом случае сработают соответствующие обработчики событий.
Затем событие распространится на элемент <html>
, и браузер спросит: «Прослушивает ли что-нибудь щелчок по элементу <html>
на этапе захвата?» В этом случае сработают соответствующие обработчики событий.
Затем событие распространится на элемент <body>
, и браузер спросит: «Прослушивает ли что-нибудь событие щелчка по элементу <body>
на этапе захвата?» В этом случае сработают соответствующие обработчики событий.
Далее событие распространится на элемент #A
. И снова браузер спросит: «Прослушивает ли что-нибудь событие щелчка по #A
на этапе захвата, и если да, то сработают соответствующие обработчики событий.
Далее событие распространится на элемент #B
(и будет задан тот же вопрос).
Наконец, событие достигнет своей цели, и браузер спросит: «Прослушивает ли что-нибудь событие щелчка по элементу #C
на этапе захвата?» На этот раз ответ «да!» Этот короткий период времени, когда событие достигает цели, известен как «целевая фаза». В этот момент сработает обработчик событий, браузер выведет в console.log «#C был нажат», и все готово, верно? Неправильный! Мы еще не закончили. Процесс продолжается, но теперь он переходит в фазу «пузырения».
Всплывание событий
Браузер спросит:
«Прослушивает ли что-нибудь событие щелчка на #C
в фазе всплытия?» Обратите здесь пристальное внимание. Вполне возможно прослушивать клики (или события любого типа) как на этапе захвата , так и на этапе всплытия. И если бы вы подключили обработчики событий на обеих фазах (например, дважды вызвав .addEventListener()
, один раз с capture = true
и один раз с capture = false
), то да, оба обработчика событий абсолютно точно сработают для одного и того же элемента. Но также важно отметить, что они срабатывают на разных фазах (одна на этапе захвата, другая на стадии пузырька).
Затем событие будет распространяться (чаще называемое «пузырьком», потому что кажется, что событие перемещается «вверх» по дереву DOM) к своему родительскому элементу #B
, и браузер спросит: «Прослушивает ли что-нибудь события щелчка на #B
в фазе всплытия?» В нашем примере ничего нет, поэтому никакие обработчики не сработают.
Затем событие перейдет к #A
, и браузер спросит: «Прослушивает ли что-нибудь события кликов на #A
в фазе всплытия?»
Затем событие всплывает в <body>
: «Прослушивает ли что-нибудь события щелчка по элементу <body>
на этапе всплытия?»
Далее, элемент <html>
: «Прослушивает ли что-нибудь события кликов по элементу <html>
на этапе всплытия?
Далее document
: «Прослушивает ли что-нибудь события кликов по document
на этапе всплытия?»
Наконец, window
: «Прослушивает ли что-нибудь события щелчка в окне на этапе всплывания?»
Уф! Это был долгий путь, и наше мероприятие, наверное, уже очень устало, но хотите верьте, хотите нет, но именно этот путь проходит каждое мероприятие! В большинстве случаев на это никогда не обращают внимания, поскольку разработчики обычно интересуются только той или иной фазой событий (обычно это фаза всплытия).
Стоит потратить некоторое время на то, чтобы поиграться с перехватом и всплыванием событий, а также записать некоторые заметки на консоль при срабатывании обработчиков. Очень полезно видеть путь, по которому проходит событие. Вот пример, который прослушивает каждый элемент на обеих фазах.
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.addEventListener(
'click',
function (e) {
console.log('click on document in capturing phase');
},
true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in capturing phase');
},
true,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in capturing phase');
},
true,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in capturing phase');
},
true,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in capturing phase');
},
true,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in capturing phase');
},
true,
);
document.addEventListener(
'click',
function (e) {
console.log('click on document in bubbling phase');
},
false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in bubbling phase');
},
false,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in bubbling phase');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in bubbling phase');
},
false,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in bubbling phase');
},
false,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in bubbling phase');
},
false,
);
Вывод консоли будет зависеть от того, какой элемент вы нажмете. Если вы щелкнете по самому «глубокому» элементу в дереве DOM (элемент #C
), вы увидите срабатывание каждого из этих обработчиков событий. Немного стилизовав CSS, чтобы было более понятно, какой элемент какой, вот элемент #C
вывода консоли (также со снимком экрана):
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Нажмите на элемент #C
и посмотрите вывод консоли.
event.stopPropagation()
Поняв, откуда происходят события и как они перемещаются (т.е. распространяются) через DOM как на этапе захвата, так и на этапе всплытия, теперь мы можем обратить внимание на event.stopPropagation()
.
Метод stopPropagation()
можно вызывать для (большинства) собственных событий DOM. Я говорю «большинство», потому что есть несколько, на которых вызов этого метода ничего не даст (потому что событие изначально не распространяется). В эту категорию попадают такие события, как focus
, blur
, load
, scroll
и некоторые другие. Вы можете вызвать stopPropagation()
, но ничего интересного не произойдет, поскольку эти события не распространяются.
Но что делает stopPropagation
?
Он делает, в основном, именно то, что говорит. Когда вы его вызовете, с этого момента событие перестанет распространяться на любые элементы, к которым оно в противном случае могло бы перейти. Это справедливо для обоих направлений (захвата и всплытия). Поэтому, если вы вызовете stopPropagation()
где-нибудь на этапе захвата, событие никогда не дойдет до целевой фазы или фазы всплытия. Если вы вызовете его на этапе всплеска, он уже пройдет фазу захвата, но перестанет «всплывать» с той точки, в которой вы его вызвали.
Возвращаясь к нашему примеру разметки, как вы думаете, что произойдет, если мы вызовем функцию stopPropagation()
на этапе захвата элемента #B
?
Это приведет к следующему результату:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и посмотрите вывод консоли.
Как насчет остановки распространения на #A
в фазе всплеска? Это приведет к следующему результату:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и посмотрите вывод консоли.
Еще один, просто для развлечения. Что произойдет, если мы вызовем stopPropagation()
на целевой фазе для #C
? Напомним, что «целевая фаза» — это название периода времени, когда событие достигает своей цели. Это приведет к следующему результату:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
Обратите внимание, что обработчик событий для #C
, в котором мы регистрируем «нажмите на #C на этапе захвата», все еще выполняется, а тот, в котором мы регистрируем «нажмите на #C на этапе всплывания», — нет. Это должно иметь смысл. Мы вызвали функцию stopPropagation()
из первого метода, так что это точка, в которой распространение события прекратится.
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и посмотрите вывод консоли.
Я советую вам поэкспериментировать с любой из этих живых демонстраций. Попробуйте нажать только на элемент #A
или только на элемент body
. Попробуйте предсказать, что произойдет, а затем проверьте, правы ли вы. На этом этапе вы должны быть в состоянии довольно точно предсказывать.
event.stopImmediatePropagation()
Что это за странный и не часто используемый метод? Он похож на stopPropagation
, но вместо того, чтобы останавливать перемещение события к потомкам (захват) или предкам (всплывание), этот метод применяется только в том случае, если к одному элементу подключено более одного обработчика событий. Поскольку addEventListener()
поддерживает многоадресный стиль обработки событий, вполне возможно подключить обработчик событий к одному элементу более одного раза. Когда это происходит (в большинстве браузеров), обработчики событий выполняются в том порядке, в котором они были подключены. Вызов stopImmediatePropagation()
предотвращает срабатывание любых последующих обработчиков. Рассмотрим следующий пример:
<html>
<body>
<div id="A">I am the #A element</div>
</body>
</html>
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run first!');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run second!');
e.stopImmediatePropagation();
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
},
false,
);
Приведенный выше пример приведет к следующему выводу консоли:
"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"
Обратите внимание, что третий обработчик событий никогда не запускается из-за того, что второй обработчик событий вызывает e.stopImmediatePropagation()
. Если бы вместо этого мы вызвали e.stopPropagation()
, третий обработчик все равно работал бы.
event.preventDefault()
Если stopPropagation()
предотвращает перемещение события «вниз» (захват) или «вверх» (всплеск), что тогда делает preventDefault()
? Похоже, он делает что-то подобное. Это так?
Не совсем. Хотя эти два понятия часто путают, на самом деле они не имеют ничего общего друг с другом. Когда вы увидите в уме preventDefault()
, добавьте слово «действие». Подумайте: «Предотвратите действие по умолчанию».
И какое действие по умолчанию вы можете спросить? К сожалению, ответ на этот вопрос не так однозначен, поскольку он сильно зависит от рассматриваемой комбинации элемент + событие. И что еще больше запутывает ситуацию, иногда действия по умолчанию вообще не существует!
Начнем с очень простого для понимания примера. Что вы ожидаете, если щелкнете ссылку на веб-странице? Очевидно, вы ожидаете, что браузер перейдет по URL-адресу, указанному по этой ссылке. В данном случае элементом является тег привязки, а событием является событие щелчка. Эта комбинация ( <a>
+ click
) имеет «действие по умолчанию» — переход к href ссылки. Что, если вы хотите запретить браузеру выполнять это действие по умолчанию? То есть, предположим, вы хотите запретить браузеру переходить по URL-адресу, указанному атрибутом href
элемента <a>
? Это то, что за вас сделает preventDefault()
. Рассмотрим этот пример:
<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
'click',
function (e) {
e.preventDefault();
console.log('Maybe we should just play some of their music right here instead?');
},
false,
);
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Щелкните ссылку The Avett Brothers и обратите внимание на вывод консоли (и на то, что вы не перенаправлены на веб-сайт Avett Brothers).
Обычно щелчок по ссылке The Avett Brothers приводит к переходу на www.theavettbrothers.com
. Однако в данном случае мы подключили обработчик события щелчка к элементу <a>
и указали, что действие по умолчанию должно быть запрещено. Таким образом, когда пользователь нажимает на эту ссылку, он никуда не переходит, а вместо этого на консоли просто появляется сообщение: «Может быть, нам стоит вместо этого просто воспроизвести немного их музыки прямо здесь?»
Какие еще комбинации элементов/событий позволяют предотвратить действие по умолчанию? Я не могу перечислить их все, и иногда вам нужно просто поэкспериментировать, чтобы увидеть. Но вкратце, вот некоторые из них:
Элемент
<form>
+ событие «отправить»:preventDefault()
для этой комбинации предотвратит отправку формы. Это полезно, если вы хотите выполнить проверку, и если что-то потерпит неудачу, вы можете условно вызвать PreventDefault, чтобы остановить отправку формы.Элемент
<a>
+ событие «click»:preventDefault()
для этой комбинации не позволяет браузеру перейти по URL-адресу, указанному в атрибуте href элемента<a>
.Событие
document
+ «mousewheel»:preventDefault()
для этой комбинации предотвращает прокрутку страницы с помощью колесика мыши (хотя прокрутка с помощью клавиатуры все равно будет работать).
↜ Для этого необходимо вызватьaddEventListener()
с{ passive: false }
.document
+ событие «keydown»:preventDefault()
для этой комбинации смертельно опасно. Это делает страницу практически бесполезной, предотвращая прокрутку клавиатуры, табуляцию и подсветку клавиатуры.Событие
document
+ «mousedown»:preventDefault()
для этой комбинации предотвратит выделение текста с помощью мыши и любое другое действие «по умолчанию», которое можно вызвать, удерживая мышь.Элемент
<input>
+ событие «keypress»:preventDefault()
для этой комбинации предотвратит попадание вводимых пользователем символов в элемент ввода (но не делайте этого; для этого редко, если вообще когда-либо, есть веская причина).Событие
document
+ «contextmenu»:preventDefault()
для этой комбинации предотвращает появление собственного контекстного меню браузера, когда пользователь щелкает правой кнопкой мыши или долгое нажатие (или любым другим способом, которым может появиться контекстное меню).
Это ни в коем случае не исчерпывающий список, но, надеюсь, он даст вам хорошее представление о том, как можно использовать preventDefault()
.
Веселая розыгрыш?
Что произойдет, если вы stopPropagation()
и preventDefault()
на этапе захвата, начиная с документа? Веселье наступает! Следующий фрагмент кода сделает любую веб-страницу практически полностью бесполезной:
function preventEverything(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });
Я действительно не знаю, почему вам вообще может понадобиться это сделать (разве что, возможно, чтобы подшутить над кем-то), но полезно подумать о том, что здесь происходит, и понять, почему это создает такую ситуацию.
Все события возникают в window
, поэтому в этом фрагменте мы останавливаем, как вкопанные, все события click
, keydown
, mousedown
, contextmenu
и mousewheel
, чтобы они никогда не добирались до каких-либо элементов, которые могут их прослушивать. Мы также вызываем stopImmediatePropagation
, чтобы любые обработчики, подключенные к документу после этого, также были заблокированы.
Обратите внимание, что stopPropagation()
и stopImmediatePropagation()
(по крайней мере, в большинстве случаев) не делают страницу бесполезной. Они просто не позволяют событиям прийти туда, куда они в противном случае пошли бы.
Но мы также вызываем preventDefault()
, который, как вы помните, предотвращает действие по умолчанию. Таким образом, любые действия по умолчанию (такие как прокрутка колесика мыши, прокрутка клавиатуры, выделение или табуляция, щелчок по ссылке, отображение контекстного меню и т. д.) блокируются, что оставляет страницу в довольно бесполезном состоянии.
Живые демонстрации
Чтобы еще раз изучить все примеры из этой статьи в одном месте, посмотрите встроенную демонстрацию ниже.
Благодарности
Изображение героя Тома Уилсона на Unsplash .
, preventDefault
и stopPropagation
: когда использовать и что именно делает каждый метод.
Event.stopPropagation() и Event.preventDefault()
Обработка событий JavaScript часто проста. Это особенно актуально при работе с простой (относительно плоской) структурой HTML. Однако все становится немного сложнее, когда события путешествуют (или распространяются) через иерархию элементов. Обычно это происходит тогда, когда разработчики обращаются к stopPropagation()
и/или preventDefault()
чтобы решить проблемы, с которыми они столкнулись. Если вы когда-нибудь думали про себя: «Я просто попробую preventDefault()
, а если это не сработает, я попробую stopPropagation()
, а если это не сработает, я попробую оба варианта», то эта статья для вас! Я подробно объясню, что делает каждый метод, когда какой из них использовать, и предоставлю вам множество рабочих примеров для изучения. Моя цель — положить конец вашему замешательству раз и навсегда.
Прежде чем мы углубимся в подробности, важно кратко коснуться двух видов обработки событий, возможных в JavaScript (то есть во всех современных браузерах — Internet Explorer до версии 9 вообще не поддерживал перехват событий).
Стили событий (захват и всплывание)
Все современные браузеры поддерживают перехват событий, но разработчиками он используется очень редко. Интересно, что это была единственная форма событий, которую изначально поддерживал Netscape. Крупнейший конкурент Netscape, Microsoft Internet Explorer, вообще не поддерживал захват событий, а лишь поддерживал другой стиль обработки событий, называемый барботированием событий. Когда W3C был сформирован, они нашли преимущества в обоих стилях обработки событий и заявили, что браузеры должны поддерживать оба, через третий параметр метода addEventListener
. Первоначально этот параметр был просто логическим значением, но все современные браузеры поддерживают объект options
в качестве третьего параметра, который вы можете использовать, чтобы указать (помимо прочего), хотите ли вы использовать перехват событий или нет:
someElement.addEventListener('click', myClickHandler, { capture: true | false });
Обратите внимание, что объект options
не является обязательным, как и его свойство capture
. Если какой-либо из них опущен, значением по умолчанию для capture
является false
, что означает, что будет использоваться всплытие событий.
Захват событий
Что означает, если ваш обработчик событий «прослушивает фазу захвата»? Чтобы понять это, нам нужно знать, как возникают события и как они распространяются. Следующее справедливо для всех событий, даже если вы, как разработчик, не используете их, не заботитесь об этом и не думаете об этом.
Все события начинаются в окне и сначала проходят фазу захвата. Это означает, что когда событие отправляется, оно запускает окно и сначала перемещается «вниз» к своему целевому элементу. Это происходит, даже если вы слушаете только на этапе «пузырения». Рассмотрим следующий пример разметки и JavaScript:
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('#C was clicked');
},
true,
);
Когда пользователь нажимает на элемент #C
, отправляется событие, происходящее в window
. Затем это событие будет распространяться через своих потомков следующим образом:
window
=> document
=> <html>
=> <body>
=> и так далее, пока не достигнет цели.
Не имеет значения, прослушивает ли ничто событие щелчка в window
или document
, элементе <html>
или элементе <body>
(или любом другом элементе на пути к своей цели). Событие по-прежнему зарождается в window
и начинает свое путешествие, как только что описано.
В нашем примере событие щелчка затем будет распространяться (это важное слово, поскольку оно напрямую связано с тем, как работает метод stopPropagation()
и будет объяснено позже в этом документе) из window
к его целевому элементу (в данном случае #C
) посредством каждого элемента между window
и #C
.
Это означает, что событие щелчка начнется в window
, и браузер задаст следующие вопросы:
«Прослушивает ли что-нибудь событие щелчка в window
на этапе захвата?» В этом случае сработают соответствующие обработчики событий. В нашем примере ничего нет, поэтому никакие обработчики не сработают.
Затем событие распространится на document
, и браузер спросит: «Прослушивает ли что-нибудь событие щелчка по document
на этапе захвата?» В этом случае сработают соответствующие обработчики событий.
Затем событие распространится на элемент <html>
, и браузер спросит: «Прослушивает ли что-нибудь щелчок по элементу <html>
на этапе захвата?» В этом случае сработают соответствующие обработчики событий.
Затем событие распространится на элемент <body>
, и браузер спросит: «Прослушивает ли что-нибудь событие щелчка по элементу <body>
на этапе захвата?» В этом случае сработают соответствующие обработчики событий.
Далее событие распространится на элемент #A
. И снова браузер спросит: «Прослушивает ли что-нибудь событие щелчка по #A
на этапе захвата, и если да, то сработают соответствующие обработчики событий.
Далее событие распространится на элемент #B
(и будет задан тот же вопрос).
Наконец, событие достигнет своей цели, и браузер спросит: «Прослушивает ли что-нибудь событие щелчка по элементу #C
на этапе захвата?» На этот раз ответ «да!» Этот короткий период времени, когда событие достигает цели, известен как «целевая фаза». В этот момент сработает обработчик событий, браузер выведет в console.log «#C был нажат», и все готово, верно? Неправильный! Мы еще не закончили. Процесс продолжается, но теперь он переходит в фазу «пузырения».
Всплывание событий
Браузер спросит:
«Прослушивает ли что-нибудь событие щелчка на #C
в фазе всплытия?» Обратите здесь пристальное внимание. Вполне возможно прослушивать клики (или события любого типа) как на этапе захвата , так и на этапе всплытия. И если бы вы подключили обработчики событий на обеих фазах (например, дважды вызвав .addEventListener()
, один раз с capture = true
и один раз с capture = false
), то да, оба обработчика событий абсолютно точно сработают для одного и того же элемента. Но также важно отметить, что они срабатывают на разных фазах (одна на этапе захвата, другая на стадии пузырька).
Затем событие будет распространяться (чаще называемое «пузырьком», потому что кажется, что событие перемещается «вверх» по дереву DOM) к своему родительскому элементу #B
, и браузер спросит: «Прослушивает ли что-нибудь события щелчка на #B
в фазе всплытия?» В нашем примере ничего нет, поэтому никакие обработчики не сработают.
Затем событие перейдет к #A
, и браузер спросит: «Прослушивает ли что-нибудь события кликов на #A
в фазе всплытия?»
Затем событие всплывает в <body>
: «Прослушивает ли что-нибудь события щелчка по элементу <body>
на этапе всплытия?»
Далее, элемент <html>
: «Прослушивает ли что-нибудь события кликов по элементу <html>
на этапе всплытия?
Далее document
: «Прослушивает ли что-нибудь события кликов по document
на этапе всплытия?»
Наконец, window
: «Прослушивает ли что-нибудь события щелчка в окне на этапе всплывания?»
Уф! Это был долгий путь, и наше мероприятие, наверное, уже очень устало, но хотите верьте, хотите нет, но именно этот путь проходит каждое мероприятие! В большинстве случаев на это никогда не обращают внимания, поскольку разработчики обычно интересуются только той или иной фазой событий (обычно это фаза всплытия).
Стоит потратить некоторое время на то, чтобы поиграться с перехватом и всплыванием событий, а также записать некоторые заметки на консоль при срабатывании обработчиков. Очень полезно видеть путь, по которому проходит событие. Вот пример, который прослушивает каждый элемент на обеих фазах.
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.addEventListener(
'click',
function (e) {
console.log('click on document in capturing phase');
},
true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in capturing phase');
},
true,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in capturing phase');
},
true,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in capturing phase');
},
true,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in capturing phase');
},
true,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in capturing phase');
},
true,
);
document.addEventListener(
'click',
function (e) {
console.log('click on document in bubbling phase');
},
false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in bubbling phase');
},
false,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in bubbling phase');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in bubbling phase');
},
false,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in bubbling phase');
},
false,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in bubbling phase');
},
false,
);
Вывод консоли будет зависеть от того, какой элемент вы нажмете. Если вы щелкнете по самому «глубокому» элементу в дереве DOM (элемент #C
), вы увидите срабатывание каждого из этих обработчиков событий. Немного стилизовав CSS, чтобы было более понятно, какой элемент какой, вот элемент #C
вывода консоли (также со снимком экрана):
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Нажмите на элемент #C
и посмотрите вывод консоли.
event.stopPropagation()
Поняв, откуда происходят события и как они перемещаются (т.е. распространяются) через DOM как на этапе захвата, так и на этапе всплытия, теперь мы можем обратить внимание на event.stopPropagation()
.
Метод stopPropagation()
можно вызывать для (большинства) собственных событий DOM. Я говорю «большинство», потому что есть несколько, на которых вызов этого метода ничего не даст (потому что событие изначально не распространяется). В эту категорию попадают такие события, как focus
, blur
, load
, scroll
и некоторые другие. Вы можете вызвать stopPropagation()
, но ничего интересного не произойдет, поскольку эти события не распространяются.
Но что делает stopPropagation
?
В значительной степени он делает именно то, что говорит. Когда вы его вызовете, с этого момента событие перестанет распространяться на любые элементы, к которым оно в противном случае могло бы перейти. Это справедливо для обоих направлений (захвата и всплытия). Поэтому, если вы вызовете stopPropagation()
где-нибудь на этапе захвата, событие никогда не дойдет до целевой фазы или фазы всплытия. Если вы вызовете его на этапе всплеска, он уже пройдет фазу захвата, но перестанет «всплывать» с той точки, в которой вы его вызвали.
Возвращаясь к нашему примеру разметки, как вы думаете, что произойдет, если мы вызовем функцию stopPropagation()
на этапе захвата элемента #B
?
Это приведет к следующему результату:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и посмотрите вывод консоли.
Как насчет остановки распространения на #A
в фазе всплеска? Это приведет к следующему результату:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и посмотрите вывод консоли.
Еще один, просто для развлечения. Что произойдет, если мы вызовем stopPropagation()
на целевой фазе для #C
? Напомним, что «целевая фаза» — это название периода времени, когда событие достигает своей цели. Это приведет к следующему результату:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
Обратите внимание, что обработчик событий для #C
, в котором мы регистрируем «нажмите на #C на этапе захвата», все еще выполняется, а тот, в котором мы регистрируем «нажмите на #C на этапе всплывания», — нет. Это должно иметь смысл. Мы вызвали функцию stopPropagation()
из первого метода, так что это точка, в которой распространение события прекратится.
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и посмотрите вывод консоли.
Я советую вам поэкспериментировать с любой из этих живых демонстраций. Попробуйте нажать только на элемент #A
или только на элемент body
. Попробуйте предсказать, что произойдет, а затем проверьте, правы ли вы. На этом этапе вы должны быть в состоянии довольно точно предсказывать.
event.stopImmediatePropagation()
Что это за странный и не часто используемый метод? Он похож на stopPropagation
, но вместо того, чтобы останавливать перемещение события к потомкам (захват) или предкам (всплывание), этот метод применяется только в том случае, если к одному элементу подключено более одного обработчика событий. Поскольку addEventListener()
поддерживает многоадресный стиль обработки событий, вполне возможно подключить обработчик событий к одному элементу более одного раза. Когда это происходит (в большинстве браузеров), обработчики событий выполняются в том порядке, в котором они были подключены. Вызов stopImmediatePropagation()
предотвращает срабатывание любых последующих обработчиков. Рассмотрим следующий пример:
<html>
<body>
<div id="A">I am the #A element</div>
</body>
</html>
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run first!');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run second!');
e.stopImmediatePropagation();
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
},
false,
);
Приведенный выше пример приведет к следующему выводу консоли:
"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"
Обратите внимание, что третий обработчик событий никогда не запускается из-за того, что второй обработчик событий вызывает e.stopImmediatePropagation()
. Если бы вместо этого мы вызвали e.stopPropagation()
, третий обработчик все равно работал бы.
event.preventDefault()
Если stopPropagation()
предотвращает перемещение события «вниз» (захват) или «вверх» (всплеск), что тогда делает preventDefault()
? Похоже, он делает что-то подобное. Это так?
Не совсем. Хотя эти два понятия часто путают, на самом деле они не имеют ничего общего друг с другом. Когда вы увидите в уме preventDefault()
, добавьте слово «действие». Подумайте: «Предотвратите действие по умолчанию».
И какое действие по умолчанию вы можете спросить? К сожалению, ответ на этот вопрос не так однозначен, поскольку он сильно зависит от рассматриваемой комбинации элемент + событие. И что еще больше запутывает ситуацию, иногда действия по умолчанию вообще не существует!
Начнем с очень простого для понимания примера. Что вы ожидаете, если щелкнете ссылку на веб-странице? Очевидно, вы ожидаете, что браузер перейдет по URL-адресу, указанному по этой ссылке. В данном случае элементом является тег привязки, а событием является событие щелчка. Эта комбинация ( <a>
+ click
) имеет «действие по умолчанию» — переход к href ссылки. Что, если вы хотите запретить браузеру выполнять это действие по умолчанию? То есть, предположим, вы хотите запретить браузеру переходить по URL-адресу, указанному атрибутом href
элемента <a>
? Это то, что за вас сделает preventDefault()
. Рассмотрим этот пример:
<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
'click',
function (e) {
e.preventDefault();
console.log('Maybe we should just play some of their music right here instead?');
},
false,
);
Вы можете интерактивно поиграть с этим в живой демонстрации ниже. Щелкните ссылку The Avett Brothers и обратите внимание на вывод консоли (и на то, что вы не перенаправлены на веб-сайт Avett Brothers).
Обычно щелчок по ссылке The Avett Brothers приводит к переходу на www.theavettbrothers.com
. Однако в данном случае мы подключили обработчик события щелчка к элементу <a>
и указали, что действие по умолчанию должно быть запрещено. Таким образом, когда пользователь нажимает на эту ссылку, он никуда не переходит, а вместо этого на консоли просто появляется сообщение: «Может быть, нам стоит вместо этого просто воспроизвести немного их музыки прямо здесь?»
Какие еще комбинации элементов/событий позволяют предотвратить действие по умолчанию? Я не могу перечислить их все, и иногда вам нужно просто поэкспериментировать, чтобы увидеть. Но вкратце, вот некоторые из них:
Элемент
<form>
+ событие «отправить»:preventDefault()
для этой комбинации предотвратит отправку формы. Это полезно, если вы хотите выполнить проверку, и если что-то потерпит неудачу, вы можете условно вызвать PreventDefault, чтобы остановить отправку формы.<a>
element + "click" Событие:preventDefault()
для этой комбинации предотвращает перемещение браузера к URL -адресу, указанному в атрибуте href<a>
href.document
+ "Mousewheel" Событие:preventDefault()
для этой комбинации предотвращает прокрутку страниц с помощью мышиного колеса (хотя прокрутка с клавиатурой все еще будет работать).
↜ This requires callingaddEventListener()
with{ passive: false }
.document
+ "keydown" event:preventDefault()
for this combination is lethal. It renders the page largely useless, preventing keyboard scrolling, tabbing, and keyboard highlighting.document
+ "Mousedown" событие:preventDefault()
для этой комбинации предотвратит выделение текста с помощью мыши и любого другого действия «по умолчанию», которое можно вызвать с помощью мыши.<input>
element + "keypress" Событие:preventDefault()
для этой комбинации предотвратит напечатанные символы пользователем входной элемент (но не делайте этого; есть редко, если вообще когда -либо, допустимая причина для него).document
+ "Contextmenu" Событие:preventDefault()
для этой комбинации предотвращает появление контекстного меню на собственном браузере, когда пользователь щелкнет правой кнопкой мыши или длинные дары (или любой другой способ, которым может появиться контекстное меню).
This is not an exhaustive list by any means, but hopefully it gives you a good idea of how preventDefault()
can be used.
A fun practical joke?
What happens if you stopPropagation()
and preventDefault()
in the capturing phase, starting at the document? Hilarity ensues! Следующий фрагмент кода будет отображать любую веб -страницу почти совершенно бесполезно:
function preventEverything(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });
Я действительно не знаю, почему вы когда -нибудь захотите сделать это (за исключением того, что, возможно, пошутить над кем -то), но полезно подумать о том, что здесь происходит, и понять, почему она создает ситуацию.
Все события происходят в window
, поэтому в этом фрагменте мы останавливаемся, мертвы в их треках, все click
, keydown
, mousedown
, contextmenu
и события mousewheel
когда -либо попадают в любые элементы, которые могут их слушать. Мы также называем stopImmediatePropagation
, чтобы любые обработчики подключались к документу после этого, также были сорваны.
Обратите внимание, что stopPropagation()
и stopImmediatePropagation()
не (по крайней мере, в основном), что делает страницу бесполезной. Они просто мешают событиям попасть туда, куда они иначе пойдут.
Но мы также называем preventDefault()
, который, как вы помните, предотвращает действие по умолчанию. Таким образом, любые действия по умолчанию (такие как прокрутка мышиного колеса, прокрутка клавиатуры или выделение или вкладки, нажатие на ссылку, отображение контекстного меню и т. Д.), Тем самым оставляя страницу в довольно бесполезном состоянии.
Живые демонстрации
Чтобы снова изучить все примеры из этой статьи в одном месте, ознакомьтесь с встроенной демонстрацией ниже.
Благодарности
Герой Изображение Тома Уилсона на Unsplash .
, preventDefault
и stopPropagation
: когда использовать то, что и что именно делает каждый метод.
Event.stoppropagation () и event.preventdefault ()
Обработка событий JavaScript часто проста. Это особенно верно при работе с простой (относительно плоской) структурой HTML. Все становится немного более вовлеченным, хотя, когда события путешествуют (или распространяются) через иерархию элементов. Как правило, это происходит, когда разработчики достигают stopPropagation()
и/или preventDefault()
для решения проблем, с которыми они сталкиваются. Если вы когда -нибудь задумывались над собой: «Я просто попробую preventDefault()
, и если это не сработает, я попробую stopPropagation()
и если это не сработает, я попробую оба», то эта статья для вас! Я точно объясню, что делает каждый метод, когда использовать какой из них, и предоставлю вам различные рабочие примеры для вас. Моя цель - покончить с вами раз и навсегда.
Прежде чем мы слишком глубоко погрузимся, важно кратко затронуть два вида обработки событий, возможных в JavaScript (во всех современных браузерах - Internet Explorer до версии 9 не поддерживал захват событий вообще).
Стили событий (захват и пузырька)
Все современные браузеры поддерживают мероприятие, но он очень редко используется разработчиками. Интересно, что это была единственная форма событий, которую Netscape изначально поддерживал. Крупнейший конкурент Netscape, Microsoft Internet Explorer, вообще не поддерживал захват событий, а скорее поддержал только другой стиль событий, называемых пузырьками событий. Когда W3C был сформирован, они нашли заслуги в обоих стилях событий и заявили, что браузеры должны поддерживать оба, через третий параметр для метода addEventListener
. Первоначально этот параметр был лишь логическим, но все современные браузеры поддерживают объект options
в качестве третьего параметра, который вы можете использовать для указания (среди прочего), если вы хотите использовать захват событий или нет:
someElement.addEventListener('click', myClickHandler, { capture: true | false });
Обратите внимание, что объект options
является необязательным, как и его свойство capture
. Если ни один из них опущен, значение по умолчанию для capture
является false
, то есть будет использоваться пузырьки событий.
Соблюдение событий
Что это значит, если ваш обработчик событий «слушает на этапе захвата?» Чтобы понять это, нам нужно знать, как происходят события и как они путешествуют. Следующее относится ко всем событиям, даже если вы, как разработчик, не используйте их, заботитесь об этом или не думайте об этом.
Все события начинаются у окна и сначала проходят фазу захвата. Это означает, что когда событие отправляется, оно запускает окно и перемещает «вниз» в сторону своего целевого элемента сначала . Это происходит, даже если вы слушаете только в фазе пузырьков. Рассмотрим следующий пример разметки и JavaScript:
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('#C was clicked');
},
true,
);
Когда пользователь нажимает на элемент #C
, событие, происходящее в window
, отправляется. Это событие затем будет распространяться через его потомков следующим образом:
window
=> document
=> <html>
=> <body>
=> и так далее, пока не достигнет цели.
Не имеет значения, если ничто не слушает событие щелчка в window
или document
, или элемент <html>
или элемент <body>
(или любой другой элемент на пути к своей цели). Событие все еще происходит в window
и начинает свое путешествие, как только что описано.
В нашем примере событие Click затем распространяется (это важное слово, так как оно будет напрямую связаться с тем, как работает метод stopPropagation()
и будет объяснен позже в этом документе) из window
к своему целевому элементу (в данном случае, #C
) с каждым элементом между window
и #C
.
Это означает, что событие Click начнется в window
, а в браузере задают следующие вопросы:
"Что -нибудь слушает событие на клике в window
на этапе захвата?" Если это так, соответствующие обработчики событий будут стрелять. В нашем примере ничего не так, поэтому ни один из них не будет стрелять.
Далее, событие будет распространяться в document
, а в браузере спросит: «Что -то слушает событие на клике в document
на этапе захвата?» Если это так, соответствующие обработчики событий будут стрелять.
Далее, событие будет распространяться на элемент <html>
, а браузер спросит: «Что -то слушает, чтобы щелкнуть элемент <html>
на фазе захвата?» Если это так, соответствующие обработчики событий будут стрелять.
Далее, событие будет распространяться на элемент <body>
, а браузер спросит: «Что -то прислушивается к событию щелчка на элементе <body>
в фазе захвата?» Если это так, соответствующие обработчики событий будут стрелять.
Далее, событие будет распространяться на элемент #A
. Опять же, браузер спросит: «Все это слушает событие на клике на #A
на этапе захвата, и если это так, соответствующие обработчики событий будут стрелять.
Далее, событие будет распространяться на элемент #B
(и тот же вопрос будет задан).
Наконец, событие достигнет своей цели, и браузер спросит: «Что -нибудь прислушивается к событию Click на элементе #C
на этапе захвата?» Ответ на этот раз - "Да!" Этот короткий период времени, когда событие находится в цели, известен как «целевая фаза». На этом этапе обработчик событий уводит, браузер будет консоль. Неправильный! Мы вообще не закончили. Процесс продолжается, но теперь он изменяется в фазе пузырька.
СОБЫТИЕ БУБЛИЧЕСКИЕ
Браузер спросит:
«Что -то слушает событие на клике на #C
в фазе пузырьков?» Обратите пристальное внимание здесь. Вполне возможно прослушать клики (или любой тип события) как на фазах захвата , так и на пузырьках. И если вы подключили обработчики событий по обеим фазам (например, дважды вызывая .addEventListener()
, один раз с capture = true
и Once с capture = false
), то да, оба обработчика событий абсолютно стреляют в один и тот же элемент. Но также важно отметить, что они стреляют в разные фазы (один в фазе захвата и один в фазе пузырьков).
Далее, событие будет распространяться (чаще называется «пузырьком», потому что кажется, что событие путешествует «вверх» на дереве DOM) к своему родительскому элементу, #B
, и браузер спросит: «Что -нибудь прислушивается к событиям на клике на #B
в фазе пузыря?» В нашем примере ничего не так, поэтому ни один из них не будет стрелять.
Далее, событие будет пузыриться до #A
, а браузер спросит: «Что -нибудь слушает события клика на #A
в фазе пузырьков?»
Далее, событие будет пузыриться до <body>
: «Что -то слушает события клика на элементе <body>
в фазе пузырька?»
Далее, элемент <html>
: «Что -то слушает события клика на элементе <html>
в фазе пузырьков?
Далее, document
: «Что -то слушает события клика в document
в фазе пузырьков?»
Наконец, window
: «Что -то слушает события клика на окне в фазе пузырьков?»
Уф! Это было долгий путь, и наше событие, вероятно, уже очень уставшее, но верьте, хотите нет, это путешествие, через которое проходит каждое событие! В большинстве случаев это никогда не замечают, потому что разработчики обычно заинтересованы только в одном или другом этапе событий (и обычно это фаза пузырька).
Стоит потратить некоторое время на поиграть с захватом событий и пузырьками событий и зарегистрировал некоторые заметки в консоли, когда стрелятели стреляют. Очень проницательно видеть путь, по которому идет событие. Вот пример, который слушает каждый элемент на обеих этапах.
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.addEventListener(
'click',
function (e) {
console.log('click on document in capturing phase');
},
true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in capturing phase');
},
true,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in capturing phase');
},
true,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in capturing phase');
},
true,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in capturing phase');
},
true,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in capturing phase');
},
true,
);
document.addEventListener(
'click',
function (e) {
console.log('click on document in bubbling phase');
},
false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in bubbling phase');
},
false,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in bubbling phase');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in bubbling phase');
},
false,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in bubbling phase');
},
false,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in bubbling phase');
},
false,
);
Вывод консоли будет зависеть от того, какой элемент вы нажимаете. Если вы нажмете на «самый глубокий» элемент в дереве DOM (элемент #C
), вы увидите, как каждый из этих обработчиков событий стреляет. С небольшим количеством стиля CSS, чтобы сделать его более очевидным, какой элемент, который есть, вот элемент консоли #C
(также с скриншотом):
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"
Вы можете интерактивно играть с этим в живой демонстрации ниже. Нажмите на элемент #C
и соблюдайте вывод консоли.
event.stopPropagation()
С пониманием того, где происходят события и как они путешествуют (т.е. распространяются) через DOM как в фазе захвата, так и в фазе пузырька, мы теперь можем обратить наше внимание на event.stopPropagation()
.
Метод stopPropagation()
можно вызвать (большинство) нативных событий DOM. Я говорю «большинство», потому что есть несколько, в которых вызов этого метода ничего не сделает (потому что событие не распространяется с самого начала). Такие события, как focus
, blur
, load
, scroll
и несколько других, попадают в эту категорию. Вы можете назвать stopPropagation()
, но ничего интересного не произойдет, так как эти события не распространяются.
Но что делает stopPropagation
?
Это делает, в значительной степени, именно то, что говорит. Когда вы это называете, с этого момента событие прекратит распространять любые элементы, по которым он в противном случае отправился бы. Это верно для обоих направлений (захват и пузырька). Поэтому, если вы позвоните в stopPropagation()
в любом месте фазы захвата, событие никогда не попадет в целевую фазу или фазу пузырьков. Если вы позвоните это в фазе пузырьков, он уже пройдет через фазу захвата, но он прекратит «пузыриться» с точки зрения, в которой вы его назвали.
Вернувшись к нашей же примере разметки, что, по вашему мнению, произойдет, если бы мы позвонили stopPropagation()
на этапе захвата на элементе #B
?
Это приведет к следующему выводу:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
Вы можете интерактивно играть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и соблюдайте вывод консоли.
Как насчет прекращения распространения в #A
в фазе пузырьков? Это приведет к следующему выводу:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
Вы можете интерактивно играть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и соблюдайте вывод консоли.
Еще один, просто для развлечения. Что произойдет, если мы назваем stopPropagation()
в целевой фазе для #C
? Напомним, что «целевая фаза» - это название, данное периоду времени, когда событие находится в своем цели. Это приведет к следующему выводу:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
Обратите внимание, что обработчик событий для #C
, в котором мы регистрируем «Нажмите на #C в фазе захвата», все еще выполняется, но тот, в котором мы регистрируем », нажимая на #C в фазе пузырька». Это должно иметь смысл. Мы позвонили stopPropagation()
от первого, так что это тот момент, когда распространение события прекратится.
Вы можете интерактивно играть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и соблюдайте вывод консоли.
В любой из этих живых демонстраций я призываю вас поиграть. Попробуйте нажать только на элемент #A
или только элемент body
. Постарайтесь предсказать, что произойдет, а затем соблюдайте, если вы правы. На этом этапе вы должны быть в состоянии предсказать довольно точно.
event.stopImmediatePropagation()
Что это за странный, а не часто используемый метод? Это похоже на stopPropagation
, но вместо того, чтобы останавливать событие от перемещения потомкам (захватывающим) или предкам (пузырьковые), этот метод применяется только тогда, когда у вас есть более одного обработчика событий, подключенного к одному элементу. Поскольку addEventListener()
поддерживает многоадресный стиль событий, вполне возможно подключить обработчик событий к одному элементу более одного раза. Когда это происходит (в большинстве браузеров), обработчики событий выполняются в том порядке, в котором они были подключены. Вызов stopImmediatePropagation()
предотвращает стрельбу любых последующих обработчиков. Рассмотрим следующий пример:
<html>
<body>
<div id="A">I am the #A element</div>
</body>
</html>
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run first!');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run second!');
e.stopImmediatePropagation();
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
},
false,
);
Приведенный выше пример приведет к следующему выводу консоли:
"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"
Обратите внимание, что третий обработчик событий никогда не работает из -за того, что второй обработчик событий вызывает e.stopImmediatePropagation()
. Если бы мы вместо этого называли e.stopPropagation()
, третий обработчик все равно работал бы.
event.preventDefault()
Если stopPropagation()
предотвращает путешествие события «вниз» (захват) или «вверх» (пузырьковые), что тогда делает preventDefault()
? Похоже, это делает что -то подобное. Это так?
Не совсем. Хотя оба часто сбиваются с толку, они на самом деле не имеют ничего общего друг с другом. Когда вы видите preventDefault()
, в вашей голове добавьте слово «действие». Подумайте «предотвратить действие по умолчанию».
А какое действие по умолчанию вы можете спросить? К сожалению, ответ на это не так ясно, потому что он сильно зависит от рассматриваемой комбинации элемента + событий. И чтобы сделать дело еще более запутанными, иногда нет никаких действий по умолчанию вообще!
Начнем с очень простого примера для понимания. Что вы ожидаете, когда нажимаете ссылку на веб -странице? Очевидно, вы ожидаете, что браузер перейдет на URL -адрес, указанный по этой ссылке. В этом случае элемент является якорной тегом, а событие - это событие Click. Эта комбинация ( <a>
+ click
) имеет «действие по умолчанию» навигации по href ссылки. Что если вы хотите предотвратить выполнение этого действия браузера? То есть, предположим, вы хотите предотвратить перемещение браузера в URL -адрес, указанный атрибутом href <a>
href
? Это то, preventDefault()
будет делать для вас. Рассмотрим этот пример:
<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
'click',
function (e) {
e.preventDefault();
console.log('Maybe we should just play some of their music right here instead?');
},
false,
);
Вы можете интерактивно играть с этим в живой демонстрации ниже. Нажмите на ссылку The Avett Brothers и соблюдайте вывод консоли (и тот факт, что вы не перенаправлены на веб -сайт Avett Brothers).
Обычно, нажав на ссылку с надписью «Братья Аветт», что приведет к просмотру на www.theavettbrothers.com
. В этом случае мы подключили обработчик событий Click к элементу <a>
и указали, что действие по умолчанию следует предотвратить. Таким образом, когда пользователь нажимает на эту ссылку, его нигде никуда не перемещаются, и вместо этого консоль просто войдет в систему: «Может быть, мы должны просто воспроизвести часть их музыки прямо здесь?»
Какие еще комбинации элементов/событий позволяют вам предотвратить действие по умолчанию? Я не могу перечислить их все, и иногда вам приходится просто экспериментировать, чтобы увидеть. Но вкратце, вот несколько:
<form>
element + "Отправить" событие:preventDefault()
для этой комбинации предотвратит подачу формы. Это полезно, если вы хотите выполнить проверку, и если что -то не удалось, вы можете условно позвонить в предотвращение, чтобы остановить подачу формы.<a>
element + "click" Событие:preventDefault()
для этой комбинации предотвращает перемещение браузера к URL -адресу, указанному в атрибуте href<a>
href.document
+ "Mousewheel" Событие:preventDefault()
для этой комбинации предотвращает прокрутку страниц с помощью мышиного колеса (хотя прокрутка с клавиатурой все еще будет работать).
↜ Это требует вызоваaddEventListener()
с{ passive: false }
.document
+ "Keydown" Событие:preventDefault()
для этой комбинации смертельна. Это делает страницу в значительной степени бесполезной, предотвращая прокрутку клавиатуры, вкладка и выделение клавиатуры.document
+ "Mousedown" событие:preventDefault()
для этой комбинации предотвратит выделение текста с помощью мыши и любого другого действия «по умолчанию», которое можно вызвать с помощью мыши.<input>
element + "keypress" Событие:preventDefault()
для этой комбинации предотвратит напечатанные символы пользователем входной элемент (но не делайте этого; есть редко, если вообще когда -либо, допустимая причина для него).document
+ "Contextmenu" Событие:preventDefault()
для этой комбинации предотвращает появление контекстного меню на собственном браузере, когда пользователь щелкнет правой кнопкой мыши или длинные дары (или любой другой способ, которым может появиться контекстное меню).
Это не исчерпывающий список ни в коем случае, но, надеюсь, он дает вам хорошее представление о том, как можно использовать preventDefault()
.
Веселая практическая шутка?
Что произойдет, если вы stopPropagation()
и preventDefault()
на этапе захвата, начиная с документа? Веселье последовало! Следующий фрагмент кода будет отображать любую веб -страницу почти совершенно бесполезно:
function preventEverything(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });
Я действительно не знаю, почему вы когда -нибудь захотите сделать это (за исключением того, что, возможно, пошутить над кем -то), но полезно подумать о том, что здесь происходит, и понять, почему она создает ситуацию.
Все события происходят в window
, поэтому в этом фрагменте мы останавливаемся, мертвы в их треках, все click
, keydown
, mousedown
, contextmenu
и события mousewheel
когда -либо попадают в любые элементы, которые могут их слушать. Мы также называем stopImmediatePropagation
, чтобы любые обработчики подключались к документу после этого, также были сорваны.
Обратите внимание, что stopPropagation()
и stopImmediatePropagation()
не (по крайней мере, в основном), что делает страницу бесполезной. Они просто мешают событиям попасть туда, куда они иначе пойдут.
Но мы также называем preventDefault()
, который, как вы помните, предотвращает действие по умолчанию. Таким образом, любые действия по умолчанию (такие как прокрутка мышиного колеса, прокрутка клавиатуры или выделение или вкладки, нажатие на ссылку, отображение контекстного меню и т. Д.), Тем самым оставляя страницу в довольно бесполезном состоянии.
Живые демонстрации
Чтобы снова изучить все примеры из этой статьи в одном месте, ознакомьтесь с встроенной демонстрацией ниже.
Благодарности
Герой Изображение Тома Уилсона на Unsplash .
, preventDefault
и stopPropagation
: когда использовать то, что и что именно делает каждый метод.
Event.stoppropagation () и event.preventdefault ()
Обработка событий JavaScript часто проста. Это особенно верно при работе с простой (относительно плоской) структурой HTML. Все становится немного более вовлеченным, хотя, когда события путешествуют (или распространяются) через иерархию элементов. Как правило, это происходит, когда разработчики достигают stopPropagation()
и/или preventDefault()
для решения проблем, с которыми они сталкиваются. Если вы когда -нибудь задумывались над собой: «Я просто попробую preventDefault()
, и если это не сработает, я попробую stopPropagation()
и если это не сработает, я попробую оба», то эта статья для вас! Я точно объясню, что делает каждый метод, когда использовать какой из них, и предоставлю вам различные рабочие примеры для вас. Моя цель - покончить с вами раз и навсегда.
Прежде чем мы слишком глубоко погрузимся, важно кратко затронуть два вида обработки событий, возможных в JavaScript (во всех современных браузерах - Internet Explorer до версии 9 не поддерживал захват событий вообще).
Стили событий (захват и пузырька)
Все современные браузеры поддерживают мероприятие, но он очень редко используется разработчиками. Интересно, что это была единственная форма событий, которую Netscape изначально поддерживал. Крупнейший конкурент Netscape, Microsoft Internet Explorer, вообще не поддерживал захват событий, а скорее поддержал только другой стиль событий, называемых пузырьками событий. Когда W3C был сформирован, они нашли заслуги в обоих стилях событий и заявили, что браузеры должны поддерживать оба, через третий параметр для метода addEventListener
. Первоначально этот параметр был лишь логическим, но все современные браузеры поддерживают объект options
в качестве третьего параметра, который вы можете использовать для указания (среди прочего), если вы хотите использовать захват событий или нет:
someElement.addEventListener('click', myClickHandler, { capture: true | false });
Обратите внимание, что объект options
является необязательным, как и его свойство capture
. Если ни один из них опущен, значение по умолчанию для capture
является false
, то есть будет использоваться пузырьки событий.
Соблюдение событий
Что это значит, если ваш обработчик событий «слушает на этапе захвата?» Чтобы понять это, нам нужно знать, как происходят события и как они путешествуют. Следующее относится ко всем событиям, даже если вы, как разработчик, не используйте их, заботитесь об этом или не думайте об этом.
Все события начинаются у окна и сначала проходят фазу захвата. Это означает, что когда событие отправляется, оно запускает окно и перемещает «вниз» в сторону своего целевого элемента сначала . Это происходит, даже если вы слушаете только в фазе пузырьков. Рассмотрим следующий пример разметки и JavaScript:
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('#C was clicked');
},
true,
);
Когда пользователь нажимает на элемент #C
, событие, происходящее в window
, отправляется. Это событие затем будет распространяться через его потомков следующим образом:
window
=> document
=> <html>
=> <body>
=> и так далее, пока не достигнет цели.
Не имеет значения, если ничто не слушает событие щелчка в window
или document
, или элемент <html>
или элемент <body>
(или любой другой элемент на пути к своей цели). Событие все еще происходит в window
и начинает свое путешествие, как только что описано.
В нашем примере событие Click затем распространяется (это важное слово, так как оно будет напрямую связаться с тем, как работает метод stopPropagation()
и будет объяснен позже в этом документе) из window
к своему целевому элементу (в данном случае, #C
) с каждым элементом между window
и #C
.
Это означает, что событие Click начнется в window
, а в браузере задают следующие вопросы:
"Что -нибудь слушает событие на клике в window
на этапе захвата?" Если это так, соответствующие обработчики событий будут стрелять. В нашем примере ничего не так, поэтому ни один из них не будет стрелять.
Далее, событие будет распространяться в document
, а в браузере спросит: «Что -то слушает событие на клике в document
на этапе захвата?» Если это так, соответствующие обработчики событий будут стрелять.
Далее, событие будет распространяться на элемент <html>
, а браузер спросит: «Что -то слушает, чтобы щелкнуть элемент <html>
на фазе захвата?» Если это так, соответствующие обработчики событий будут стрелять.
Далее, событие будет распространяться на элемент <body>
, а браузер спросит: «Что -то прислушивается к событию щелчка на элементе <body>
в фазе захвата?» Если это так, соответствующие обработчики событий будут стрелять.
Далее, событие будет распространяться на элемент #A
. Опять же, браузер спросит: «Все это слушает событие на клике на #A
на этапе захвата, и если это так, соответствующие обработчики событий будут стрелять.
Далее, событие будет распространяться на элемент #B
(и тот же вопрос будет задан).
Наконец, событие достигнет своей цели, и браузер спросит: «Что -нибудь прислушивается к событию Click на элементе #C
на этапе захвата?» Ответ на этот раз - "Да!" Этот короткий период времени, когда событие находится в цели, известен как «целевая фаза». На этом этапе обработчик событий уводит, браузер будет консоль. Неправильный! Мы вообще не закончили. Процесс продолжается, но теперь он изменяется в фазе пузырька.
СОБЫТИЕ БУБЛИЧЕСКИЕ
Браузер спросит:
«Что -то слушает событие на клике на #C
в фазе пузырьков?» Обратите пристальное внимание здесь. Вполне возможно прослушать клики (или любой тип события) как на фазах захвата , так и на пузырьках. И если вы подключили обработчики событий по обеим фазам (например, дважды вызывая .addEventListener()
, один раз с capture = true
и Once с capture = false
), то да, оба обработчика событий абсолютно стреляют в один и тот же элемент. Но также важно отметить, что они стреляют в разные фазы (один в фазе захвата и один в фазе пузырьков).
Далее, событие будет распространяться (чаще называется «пузырьком», потому что кажется, что событие путешествует «вверх» на дереве DOM) к своему родительскому элементу, #B
, и браузер спросит: «Что -нибудь прислушивается к событиям на клике на #B
в фазе пузыря?» В нашем примере ничего не так, поэтому ни один из них не будет стрелять.
Далее, событие будет пузыриться до #A
, а браузер спросит: «Что -нибудь слушает события клика на #A
в фазе пузырьков?»
Далее, событие будет пузыриться до <body>
: «Что -то слушает события клика на элементе <body>
в фазе пузырька?»
Далее, элемент <html>
: «Что -то слушает события клика на элементе <html>
в фазе пузырьков?
Далее, document
: «Что -то слушает события клика в document
в фазе пузырьков?»
Наконец, window
: «Что -то слушает события клика на окне в фазе пузырьков?»
Уф! Это было долгий путь, и наше событие, вероятно, уже очень уставшее, но верьте, хотите нет, это путешествие, через которое проходит каждое событие! В большинстве случаев это никогда не замечают, потому что разработчики обычно заинтересованы только в одном или другом этапе событий (и обычно это фаза пузырька).
Стоит потратить некоторое время на поиграть с захватом событий и пузырьками событий и зарегистрировал некоторые заметки в консоли, когда стрелятели стреляют. Очень проницательно видеть путь, по которому идет событие. Вот пример, который слушает каждый элемент на обеих этапах.
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.addEventListener(
'click',
function (e) {
console.log('click on document in capturing phase');
},
true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in capturing phase');
},
true,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in capturing phase');
},
true,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in capturing phase');
},
true,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in capturing phase');
},
true,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in capturing phase');
},
true,
);
document.addEventListener(
'click',
function (e) {
console.log('click on document in bubbling phase');
},
false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in bubbling phase');
},
false,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in bubbling phase');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in bubbling phase');
},
false,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in bubbling phase');
},
false,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in bubbling phase');
},
false,
);
Вывод консоли будет зависеть от того, какой элемент вы нажимаете. Если вы нажмете на «самый глубокий» элемент в дереве DOM (элемент #C
), вы увидите, как каждый из этих обработчиков событий стреляет. С небольшим количеством стиля CSS, чтобы сделать его более очевидным, какой элемент, который есть, вот элемент консоли #C
(также с скриншотом):
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"
Вы можете интерактивно играть с этим в живой демонстрации ниже. Нажмите на элемент #C
и соблюдайте вывод консоли.
event.stopPropagation()
С пониманием того, где происходят события и как они путешествуют (т.е. распространяются) через DOM как в фазе захвата, так и в фазе пузырька, мы теперь можем обратить наше внимание на event.stopPropagation()
.
Метод stopPropagation()
можно вызвать (большинство) нативных событий DOM. Я говорю «большинство», потому что есть несколько, в которых вызов этого метода ничего не сделает (потому что событие не распространяется с самого начала). Такие события, как focus
, blur
, load
, scroll
и несколько других, попадают в эту категорию. Вы можете назвать stopPropagation()
, но ничего интересного не произойдет, так как эти события не распространяются.
Но что делает stopPropagation
?
Это делает, в значительной степени, именно то, что говорит. Когда вы это называете, с этого момента событие прекратит распространять любые элементы, по которым он в противном случае отправился бы. Это верно для обоих направлений (захват и пузырька). Поэтому, если вы позвоните в stopPropagation()
в любом месте фазы захвата, событие никогда не попадет в целевую фазу или фазу пузырьков. Если вы позвоните это в фазе пузырьков, он уже пройдет через фазу захвата, но он прекратит «пузыриться» с точки зрения, в которой вы его назвали.
Вернувшись к нашей же примере разметки, что, по вашему мнению, произойдет, если бы мы позвонили stopPropagation()
на этапе захвата на элементе #B
?
Это приведет к следующему выводу:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
Вы можете интерактивно играть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и соблюдайте вывод консоли.
Как насчет прекращения распространения в #A
в фазе пузырьков? Это приведет к следующему выводу:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
Вы можете интерактивно играть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и соблюдайте вывод консоли.
Еще один, просто для развлечения. Что произойдет, если мы назваем stopPropagation()
в целевой фазе для #C
? Напомним, что «целевая фаза» - это название, данное периоду времени, когда событие находится в своем цели. Это приведет к следующему выводу:
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
Обратите внимание, что обработчик событий для #C
, в котором мы регистрируем «Нажмите на #C в фазе захвата», все еще выполняется, но тот, в котором мы регистрируем », нажимая на #C в фазе пузырька». Это должно иметь смысл. Мы позвонили stopPropagation()
от первого, так что это тот момент, когда распространение события прекратится.
Вы можете интерактивно играть с этим в живой демонстрации ниже. Нажмите на элемент #C
в живой демонстрации и соблюдайте вывод консоли.
В любой из этих живых демонстраций я призываю вас поиграть. Попробуйте нажать только на элемент #A
или только элемент body
. Постарайтесь предсказать, что произойдет, а затем соблюдайте, если вы правы. На этом этапе вы должны быть в состоянии предсказать довольно точно.
event.stopImmediatePropagation()
Что это за странный, а не часто используемый метод? Это похоже на stopPropagation
, но вместо того, чтобы останавливать событие от перемещения потомкам (захватывающим) или предкам (пузырьковые), этот метод применяется только тогда, когда у вас есть более одного обработчика событий, подключенного к одному элементу. Поскольку addEventListener()
поддерживает многоадресный стиль событий, вполне возможно подключить обработчик событий к одному элементу более одного раза. Когда это происходит (в большинстве браузеров), обработчики событий выполняются в том порядке, в котором они были подключены. Calling stopImmediatePropagation()
prevents any subsequent handlers from firing. Consider the following example:
<html>
<body>
<div id="A">I am the #A element</div>
</body>
</html>
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run first!');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run second!');
e.stopImmediatePropagation();
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
},
false,
);
The above example will result in the following console output:
"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"
Note that the third event handler never runs due to the fact that the second event handler calls e.stopImmediatePropagation()
. If we had instead called e.stopPropagation()
, the third handler would still run.
event.preventDefault()
If stopPropagation()
prevents an event from traveling "downwards" (capturing) or "upwards" (bubbling), what then, does preventDefault()
do? It sounds like it does something similar. Это так?
Не совсем. While the two are often confused, they actually don't have much to do with each other. When you see preventDefault()
, in your head, add the word "action." Think "prevent the default action."
And what is the default action you may ask? Unfortunately, the answer to that isn't quite as clear because it's highly dependent on the element + event combination in question. And to make matters even more confusing, sometimes there is no default action at all!
Let's begin with a very simple example to understand. What do you expect to happen when you click a link on a web page? Obviously, you expect the browser to navigate to the URL specified by that link. In this case, the element is an anchor tag and the event is a click event. That combination ( <a>
+ click
) has a "default action" of navigating to the link's href. What if you wanted to prevent the browser from performing that default action? That is, suppose you want to prevent the browser from navigating to the URL specified by the <a>
element's href
attribute? This is what preventDefault()
will do for you. Рассмотрим этот пример:
<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
'click',
function (e) {
e.preventDefault();
console.log('Maybe we should just play some of their music right here instead?');
},
false,
);
You can interactively play with this in the live demo below. Click the link The Avett Brothers and observe the console output (and the fact that you are not redirected to the Avett Brothers website).
Normally, clicking the link labelled The Avett Brothers would result in browsing to www.theavettbrothers.com
. In this case though, we've wired up a click event handler to the <a>
element and specified that the default action should be prevented. Thus, when a user clicks this link, they won't be navigated anywhere, and instead the console will simply log "Maybe we should just play some of their music right here instead?"
What other element/event combinations allow you to prevent the default action? I cannot possibly list them all, and sometimes you have to just experiment to see. But briefly, here are a few:
<form>
element + "submit" event:preventDefault()
for this combination will prevent a form from submitting. This is useful if you want to perform validation and should something fail, you can conditionally call preventDefault to stop the form from submitting.<a>
element + "click" event:preventDefault()
for this combination prevents the browser from navigating to the URL specified in the<a>
element's href attribute.document
+ "mousewheel" event:preventDefault()
for this combination prevents page scrolling with the mousewheel (scrolling with keyboard would still work though).
↜ This requires callingaddEventListener()
with{ passive: false }
.document
+ "keydown" event:preventDefault()
for this combination is lethal. It renders the page largely useless, preventing keyboard scrolling, tabbing, and keyboard highlighting.document
+ "mousedown" event:preventDefault()
for this combination will prevent text highlighting with the mouse and any other "default" action that one would invoke with a mouse down.<input>
element + "keypress" event:preventDefault()
for this combination will prevent characters typed by the user from reaching the input element (but don't do this; there is rarely, if ever, a valid reason for it).document
+ "contextmenu" event:preventDefault()
for this combination prevents the native browser context menu from appearing when a user right-clicks or long-presses (or any other way in which a context menu might appear).
This is not an exhaustive list by any means, but hopefully it gives you a good idea of how preventDefault()
can be used.
A fun practical joke?
What happens if you stopPropagation()
and preventDefault()
in the capturing phase, starting at the document? Hilarity ensues! The following code snippet will render any web page just about completely useless:
function preventEverything(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });
I don't really know why you'd ever want to do this (except maybe to play a joke on someone), but it is useful to think about what's happening here, and realize why it creates the situation it does.
All events originate at window
, so in this snippet, we're stopping, dead in their tracks, all click
, keydown
, mousedown
, contextmenu
, and mousewheel
events from ever getting to any elements that might be listening for them. We also call stopImmediatePropagation
so that any handlers wired up to the document after this one, will be thwarted as well.
Note that stopPropagation()
and stopImmediatePropagation()
aren't (at least not mostly) what render the page useless. They simply prevent events from getting where they would otherwise go.
But we also call preventDefault()
, which you'll recall prevents the default action . So any default actions (like mousewheel scroll, keyboard scroll or highlight or tabbing, link clicking, context menu display, etc.) are all prevented, thus leaving the page in a fairly useless state.
Live demos
To explore all the examples from this article again in one place, check out the embedded demo below.
Благодарности
Hero image by Tom Wilson on Unsplash .