У многих событий браузера есть действие по умолчанию: клик по ссылке ведёт на новый URL, кнопка отправляет форму с перезагрузкой страницы, клик по чекбоксу ставит галочку, mousedown с движением мыши выделяет текст, contextmenu открывает меню браузера. Метод event.preventDefault() отменяет именно это действие – и только его: событие продолжает всплывать по DOM, поэтому делегирование событий не ломается. Метод не принимает аргументов, возвращает undefined, работает во всех современных браузерах (Baseline Widely available, с июля 2015) и в Web Workers.
Как это работает
Обработчик вызывает event.preventDefault() – браузер получает сигнал «событие обработано явно, действие по умолчанию не выполнять». Три канонических сценария:
// ссылка: попап или SPA-роутер вместо перехода
link.addEventListener('click', (event) => {
event.preventDefault();
openPopup(link.href);
});
// форма: не отправлять, пока не пройдена валидация
form.addEventListener('submit', (event) => {
if (!isFormValid()) event.preventDefault();
});
// чекбокс: запретить переключение
checkbox.addEventListener('click', (event) => event.preventDefault());У события есть два служебных свойства. event.cancelable показывает, отменяемо ли действие в принципе: на неотменяемом событии вызов preventDefault() просто игнорируется. event.defaultPrevented возвращает true, если действие уже отменено, – так обработчики выше по цепочке всплытия узнают, что событие обработал другой код, и не дублируют логику. Типовой паттерн: обработчик contextmenu на document сначала проверяет if (event.defaultPrevented) return – и не перекрывает кастомные меню, которые уже отменили действие ниже по дереву.
preventDefault, stopPropagation и stopImmediatePropagation – в чём разница
| Метод | Что отменяет | Что продолжается |
|---|---|---|
| preventDefault() | Действие браузера по умолчанию | Событие всплывает дальше, все обработчики выполняются |
| stopPropagation() | Дальнейшее распространение: событие не дойдёт до предков | Действие браузера и остальные обработчики текущего элемента |
| stopImmediatePropagation() | Распространение и остальные обработчики на том же элементе | Действие браузера по умолчанию |
Методы независимы и не заменяют друг друга: preventDefault() управляет поведением браузера, два других – маршрутом события по DOM. Если нужно и то и другое, их вызывают вместе.
return false – три разных поведения
Классический источник путаницы при переносе старого кода:
- jQuery – return false из обработчика автоматически вызывает и preventDefault(), и stopPropagation(): два действия сразу (прямая формулировка документации api.jquery.com). После переноса такого кода на нативный JS «внезапно» ломается делегирование.
- HTML-атрибут – в onclick="return false" возврат false отменяет действие по умолчанию.
- addEventListener – return false не делает ничего: действие отменяется только явным preventDefault().
Зачем это нужно
Типовые задачи: SPA-навигация без перезагрузки страницы, попап по клику на ссылку с data-атрибутом, клиентская валидация формы перед отправкой, кастомное контекстное меню, фильтрация вводимых символов, блокировка скролла под модальным окном. Логика везде одна: браузерное поведение заменяется собственным. На Битрикс-сайтах это ежедневная практика: попап обратного звонка вместо перехода по ссылке, AJAX-отправка формы без перезагрузки страницы, шторка корзины поверх каталога – всё начинается с preventDefault() в первом же обработчике.
Два ограничения на практике. Первое – доступность: ссылка с отменённым переходом для скринридера остаётся ссылкой; если элемент ведёт себя как кнопка, семантически честнее использовать button. Второе – MDN рекомендует сначала искать декларативные альтернативы: атрибуты disabled и readonly вместо перехвата ввода, встроенную HTML-валидацию форм (constraint validation) вместо ручного перехвата submit, CSS overflow вместо блокировки прокрутки скриптом. Атрибуты required, pattern и type="email" отсекают невалидные данные без единой строки JavaScript. Меньше кода – меньше точек отказа.
Пример
Самый частый практический вопрос – «почему preventDefault() не работает». Разберём главный случай: блокировку скролла на мобильных под открытой модалкой.
// НЕ работает: слушатель пассивный, preventDefault()
// игнорируется, в консоли – предупреждение
document.addEventListener('touchmove',
(e) => e.preventDefault(), { passive: true });
// Работает: passive отключён явно
document.addEventListener('touchmove',
(e) => e.preventDefault(), { passive: false });Опция passive: true декларирует, что обработчик не будет отменять действие, поэтому браузер запускает скролл сразу, не дожидаясь JavaScript, – это убирает подтормаживания прокрутки. Ловушка в значении по умолчанию: по спецификации passive равен false, но все браузеры, кроме Safari, включают его автоматически для wheel, mousewheel, touchstart и touchmove на window, document и document.body. Поэтому блокировка скролла без явного { passive: false } молча не работает в Chrome и Firefox. Та же история со свайпами в галереях и на картах: обработчику touchstart или touchmove, который должен подавить нативный жест, passive: false нужен явно.
Чек-лист диагностики:
- Событие отменяемо? Проверьте event.cancelable: у неотменяемых событий preventDefault() не даёт эффекта.
- Слушатель не пассивный? Для wheel и touch-событий на документе passive включён по умолчанию – добавьте { passive: false }.
- То ли событие? Действие по умолчанию привязано к конкретному событию: галочку чекбокса отменяют в обработчике click – в change действие уже произошло.
Быстрая проверка в конце: выведите event.defaultPrevented сразу после вызова. true – отмена дошла до браузера; false – сработал один из пунктов выше.