preventDefault()Уявіть: ви клікаєте на посилання — браузер автоматично переходить за ним. Натискаєте Enter у формі — вона відправляється на сервер. Клікаєте правою кнопкою — з'являється контекстне меню.
Ці дії відбуваються автоматично, без вашого коду. Браузер має вбудовану логіку для багатьох подій. Така поведінка називається типовою дією браузера (browser default action).
Ось найпоширеніші автоматичні дії, які виконує браузер:
🔗 Клік на посиланні
Подія: click на <a href="...">Типова дія: Перехід на URL у атрібуті href
<a href="/about">Про нас</a>
<!-- Клік → браузер переходить на /about -->
📋 Відправка форми
Подія: submit на <form>Типова дія: Відправка HTTP-запиту на сервер
<form method="POST" action="/api/login">
<button type="submit">Увійти</button>
</form>
<!-- Клік → браузер відправляє POST-запит -->
🖱️ Правий клік
Подія: contextmenuТипова дія: Показ контекстного меню браузера
<div>Правий клік → контекстне меню</div>
✏️ Виділення тексту
Подія: mousedown + рух миші
Типова дія: Виділення тексту
<p>Натисніть і перетягніть → виділення</p>
⌨️ Введення в input
Подія: keydown у <input>Типова дія: Додавання символу в поле
<input type="text" />
<!-- Натискання клавіші → символ з'являється -->
☑️ Чекбокс
Подія: click на <input type="checkbox">Типова дія: Зміна стану (вкл/викл)
<input type="checkbox" />
<!-- Клік → стан змінюється -->
| Подія | Елемент | Типова дія |
|---|---|---|
click | <a> | Перехід на URL з href |
submit | <form> | Відправка форми на сервер |
contextmenu | Будь-який | Показ контекстного меню |
mousedown | Текст | Початок виділення тексту |
focus | <input> | Встановлення фокусу, курсор у полі |
keydown | <input> | Введення символу |
wheel | Будь-який | Прокрутка сторінки |
touchmove | Сенсорний екран | Прокрутка на мобільних |
dragstart | Будь-який | Початок перетягування |
event.preventDefault()Щоб запобігти типовій дії браузера, використовуйте метод event.preventDefault():
element.addEventListener('click', function (event) {
event.preventDefault() // ❌ Скасовуємо типову дію
// Тут ваш власний код
})
Задача: Зробити посилання, яке не переходить на іншу сторінку, а виконує JavaScript-код.
<a href="https://google.com" id="link">Не переходь на Google</a>
<script>
const link = document.getElementById('link')
link.addEventListener('click', function (event) {
event.preventDefault() // ❌ Скасовуємо перехід
alert('Перехід скасовано! Залишаємося на сторінці.')
console.log('Виконується JavaScript замість переходу')
})
</script>
Що відбувається:
clickpreventDefault()Задача: Перевірити форму перед відправкою на сервер.
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8" />
<title>Форма з валідацією</title>
<style>
.error {
color: #ef4444;
font-size: 14px;
margin-top: 5px;
}
input.invalid {
border: 2px solid #ef4444;
}
input.valid {
border: 2px solid #10b981;
}
</style>
</head>
<body>
<h1>Реєстрація</h1>
<form id="registrationForm" action="/api/register" method="POST">
<div>
<label>Email:</label><br />
<input type="email" name="email" id="email" required />
<div class="error" id="emailError"></div>
</div>
<div style="margin-top: 10px;">
<label>Пароль (мін. 8 символів):</label><br />
<input type="password" name="password" id="password" required />
<div class="error" id="passwordError"></div>
</div>
<button type="submit" style="margin-top: 15px;">Зареєструватися</button>
</form>
<script>
const form = document.getElementById('registrationForm')
const emailInput = document.getElementById('email')
const passwordInput = document.getElementById('password')
const emailError = document.getElementById('emailError')
const passwordError = document.getElementById('passwordError')
form.addEventListener('submit', function (event) {
// Скидаємо попередні помилки
emailError.textContent = ''
passwordError.textContent = ''
emailInput.classList.remove('invalid', 'valid')
passwordInput.classList.remove('invalid', 'valid')
let isValid = true
// Валідація email
const emailValue = emailInput.value.trim()
if (!emailValue.includes('@')) {
emailError.textContent = '❌ Email має містити @'
emailInput.classList.add('invalid')
isValid = false
} else {
emailInput.classList.add('valid')
}
// Валідація пароля
const passwordValue = passwordInput.value
if (passwordValue.length < 8) {
passwordError.textContent = '❌ Пароль має бути мінімум 8 символів'
passwordInput.classList.add('invalid')
isValid = false
} else {
passwordInput.classList.add('valid')
}
// Якщо є помилки — скасовуємо відправку
if (!isValid) {
event.preventDefault() // ❌ Скасовуємо відправку форми
console.log('Форма НЕ відправлена через помилки валідації')
} else {
console.log('✅ Форма валідна, відправляємо на сервер')
// Браузер відправить форму автоматично
}
})
</script>
</body>
</html>
Розбір коду:
submit — спрацьовує при натисканні на кнопку або Enterevent.preventDefault() — скасовуємо відправкуpreventDefault()Задача: Замінити стандартне контекстне меню браузера на власне.
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8" />
<title>Власне контекстне меню</title>
<style>
.custom-menu {
display: none;
position: absolute;
background: white;
border: 2px solid #3b82f6;
border-radius: 8px;
padding: 10px 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
z-index: 1000;
min-width: 200px;
}
.custom-menu.active {
display: block;
}
.menu-item {
padding: 10px 20px;
cursor: pointer;
transition: background 0.2s;
}
.menu-item:hover {
background: #dbeafe;
}
.target-area {
width: 400px;
height: 300px;
border: 3px dashed #3b82f6;
display: flex;
align-items: center;
justify-content: center;
background: #f0f9ff;
font-size: 18px;
color: #1e40af;
}
</style>
</head>
<body>
<h1>Клікніть правою кнопкою на область</h1>
<div class="target-area" id="targetArea">ПРАВИЙ КЛІК СЮДИ</div>
<div class="custom-menu" id="customMenu">
<div class="menu-item" data-action="copy">📋 Копіювати</div>
<div class="menu-item" data-action="paste">📄 Вставити</div>
<div class="menu-item" data-action="delete">🗑️ Видалити</div>
<hr />
<div class="menu-item" data-action="info">ℹ️ Інформація</div>
</div>
<script>
const targetArea = document.getElementById('targetArea')
const customMenu = document.getElementById('customMenu')
// Показуємо власне меню при правому кліку
targetArea.addEventListener('contextmenu', function (event) {
event.preventDefault() // ❌ Скасовуємо стандартне меню браузера
// Показуємо наше меню на позиції курсора
customMenu.style.left = event.pageX + 'px'
customMenu.style.top = event.pageY + 'px'
customMenu.classList.add('active')
})
// Приховуємо меню при кліку в будь-якому місці
document.addEventListener('click', function () {
customMenu.classList.remove('active')
})
// Обробка кліків на пунктах меню
customMenu.addEventListener('click', function (event) {
const action = event.target.dataset.action
if (action) {
alert(`Ви вибрали: ${action}`)
customMenu.classList.remove('active')
}
})
</script>
</body>
</html>
Як це працює:
contextmenuevent.preventDefault() — скасовуємо стандартне менюreturn falseДля обробників, призначених через on<event> (НЕ addEventListener), можна використовувати return false:
<!-- HTML-атрибут -->
<a href="/page" onclick="alert('Клік!'); return false;">Посилання</a>
<!-- DOM-властивість -->
<script>
element.onclick = function () {
alert('Клік!')
return false // Еквівалент event.preventDefault()
}
</script>
return falseonclick="")element.onclick = ...)addEventListener() — потрібен event.preventDefault()// ❌ НЕ ПРАЦЮЄ
element.addEventListener('click', function () {
return false // Ігнорується!
})
// ✅ ПРАЦЮЄ
element.addEventListener('click', function (event) {
event.preventDefault()
})
// ✅ Найкращий спосіб — завжди працює
element.addEventListener('click', function (event) {
event.preventDefault()
// Ваш код
})
Переваги:
addEventListener// ⚠️ Працює тільки для on<event>
element.onclick = function () {
// Ваш код
return false
}
Переваги:
Недоліки:
addEventListenerreturn<!-- ✅ Працює, але не рекомендується -->
<a href="#" onclick="handleClick(); return false;"> Посилання </a>
<script>
function handleClick() {
alert('Клік!')
}
</script>
Недоліки:
Деякі події викликають інші події. Якщо скасувати першу — наступні не виконаються.
mousedown → focus<p>Спробуйте клікнути на обидва поля:</p>
<input type="text" value="Фокус працює" id="input1" />
<input type="text" value="Фокус НЕ працює" id="input2" />
<script>
const input1 = document.getElementById('input1')
const input2 = document.getElementById('input2')
// Звичайний input — фокус працює
input1.addEventListener('focus', function () {
console.log('✅ Input 1 отримав фокус')
this.style.background = '#dbeafe'
})
// Скасовуємо mousedown — фокус НЕ працює
input2.addEventListener('mousedown', function (event) {
event.preventDefault() // ❌ Скасовуємо mousedown
console.log('⚠️ mousedown скасовано на input 2')
})
input2.addEventListener('focus', function () {
console.log('Input 2 отримав фокус') // НЕ ВИКОНАЄТЬСЯ при кліку!
this.style.background = '#fef3c7'
})
</script>
Що відбувається:
mousedown → focus → поле активне ✅mousedown → preventDefault() → focus НЕ викликається ❌Примітка: Фокус на input2 все ще можна отримати через клавіатуру (Tab).
passive: оптимізація для мобільнихНа мобільних пристроях деякі події (наприклад, touchmove) можуть викликати затримки, якщо браузер чекає на виклик preventDefault().
// Браузер НЕ ЗНАЄ, чи буде викликано preventDefault()
document.addEventListener('touchmove', function (event) {
// Можливо тут event.preventDefault()?
// Браузер ЧЕКАЄ виконання функції перед прокруткою
console.log('Touch move')
})
Наслідок: Затримка прокрутки = погана продуктивність на мобільних.
passive: truedocument.addEventListener(
'touchmove',
function (event) {
// Браузер ЗНАЄ, що preventDefault() не буде
// Починає прокрутку ОДРАЗУ
console.log('Touch move')
},
{ passive: true },
) // ✅ Оптимізація!
Що робить passive: true:
preventDefault()"preventDefault() у пасивному обробнику, браузер проігнорує виклик і покаже попередження в консолі:document.addEventListener(
'touchmove',
function (event) {
event.preventDefault() // ⚠️ ІГНОРУЄТЬСЯ!
},
{ passive: true },
)
Unable to preventDefault inside passive event listenerpassive: trueДля обробників, які НЕ скасовують типову дію:
// Тільки логування — не потрібен preventDefault
document.addEventListener(
'touchmove',
(e) => {
console.log('Прокрутка', e.touches[0].pageY)
},
{ passive: true },
)
// Аналітика прокрутки
window.addEventListener(
'scroll',
() => {
analytics.track('scroll')
},
{ passive: true },
)
Коли потрібно скасовувати типову дію:
// Кастомна обробка свайпу
element.addEventListener(
'touchmove',
(e) => {
if (shouldPreventScroll(e)) {
e.preventDefault() // Потрібен!
}
},
{ passive: false },
) // Явно вказуємо false
event.defaultPrevented: перевірка стануВластивість event.defaultPrevented показує, чи було скасовано типову дію:
element.addEventListener('click', function (event) {
console.log(event.defaultPrevented) // false
event.preventDefault()
console.log(event.defaultPrevented) // true
})
Проблема: У нас є контекстне меню на кнопці та на документі. Як зробити, щоб показувалося лише найближче?
// Проблема: блокує ВСІ батьківські обробники!
button.addEventListener('contextmenu', (e) => {
e.preventDefault()
e.stopPropagation() // ❌ "Мертва зона" для аналітики
showButtonMenu()
})
document.addEventListener('contextmenu', (e) => {
e.preventDefault()
showDocumentMenu()
// Ніколи не виконається для кнопки!
})
// Аналітика НЕ працює для кнопки
document.addEventListener('contextmenu', () => {
analytics.track('context-menu')
})
// Рішення: перевірка defaultPrevented
button.addEventListener('contextmenu', (e) => {
e.preventDefault() // Позначаємо як оброблену
showButtonMenu()
})
document.addEventListener('contextmenu', (e) => {
if (e.defaultPrevented) return // Вже оброблено — виходимо
e.preventDefault()
showDocumentMenu()
})
// ✅ Аналітика працює для ВСІХ елементів!
document.addEventListener('contextmenu', () => {
analytics.track('context-menu')
})
Переваги підходу:
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8" />
<title>Вкладені контекстні меню</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 40px;
}
.container {
padding: 30px;
border: 3px solid #3b82f6;
background: #dbeafe;
}
button {
padding: 15px 30px;
font-size: 16px;
cursor: pointer;
background: #3b82f6;
color: white;
border: none;
border-radius: 5px;
}
.context-menu {
position: absolute;
background: white;
border: 2px solid #333;
border-radius: 5px;
padding: 5px 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1000;
min-width: 150px;
}
.context-menu div {
padding: 8px 15px;
cursor: pointer;
}
.context-menu div:hover {
background: #f0f0f0;
}
.active {
display: block !important;
}
</style>
</head>
<body>
<h1>Вкладені контекстні меню</h1>
<p>Правий клік на різних областях показує відповідне меню:</p>
<div class="container" id="container">
<p>Це контейнер (правий клік тут)</p>
<button id="button">Правий клік на кнопці</button>
</div>
<p>Правий клік поза контейнером показує меню документа</p>
<div class="context-menu" id="buttonMenu" style="display:none;">
<div>📋 Копіювати кнопку</div>
<div>🗑️ Видалити кнопку</div>
</div>
<div class="context-menu" id="containerMenu" style="display:none;">
<div>📦 Властивості контейнера</div>
<div>🎨 Змінити колір</div>
</div>
<div class="context-menu" id="documentMenu" style="display:none;">
<div>📄 Властивості документа</div>
<div>🔄 Перезавантажити</div>
</div>
<script>
const button = document.getElementById('button')
const container = document.getElementById('container')
const buttonMenu = document.getElementById('buttonMenu')
const containerMenu = document.getElementById('containerMenu')
const documentMenu = document.getElementById('documentMenu')
let activeMenu = null
function showMenu(menu, x, y) {
hideAllMenus()
menu.style.left = x + 'px'
menu.style.top = y + 'px'
menu.classList.add('active')
activeMenu = menu
}
function hideAllMenus() {
buttonMenu.classList.remove('active')
containerMenu.classList.remove('active')
documentMenu.classList.remove('active')
}
// Меню кнопки (найвищий пріоритет)
button.addEventListener('contextmenu', (e) => {
e.preventDefault() // Позначаємо як оброблену
showMenu(buttonMenu, e.pageX, e.pageY)
console.log('📋 Показано меню КНОПКИ')
})
// Меню контейнера
container.addEventListener('contextmenu', (e) => {
if (e.defaultPrevented) return // Вже оброблено кнопкою
e.preventDefault()
showMenu(containerMenu, e.pageX, e.pageY)
console.log('📦 Показано меню КОНТЕЙНЕРА')
})
// Меню документа (найнижчий пріоритет)
document.addEventListener('contextmenu', (e) => {
if (e.defaultPrevented) return // Вже оброблено кнопкою/контейнером
e.preventDefault()
showMenu(documentMenu, e.pageX, e.pageY)
console.log('📄 Показано меню ДОКУМЕНТА')
})
// Приховуємо меню при кліку
document.addEventListener('click', hideAllMenus)
// Аналітика (працює для ВСІХ контекстних меню!)
document.addEventListener('contextmenu', () => {
console.log('📊 [Аналітика] Відкрито контекстне меню')
})
</script>
</body>
</html>
Результат:
preventDefault() vs stopPropagation()| Метод | Що робить | Коли використовувати |
|---|---|---|
preventDefault() | Скасовує типову дію браузера (перехід, відправку форми) | Коли треба замінити поведінку браузера своєю логікою |
stopPropagation() | Зупиняє спливання події до батьків | Коли НЕ хочете, щоб батьки обробили подію |
element.addEventListener('click', (e) => {
e.preventDefault() // Скасовуємо типову дію
e.stopPropagation() // І зупиняємо спливання
})
<!-- ❌ ПОГАНА ПРАКТИКА -->
<div onclick="handleClick()">Я кнопка, але div</div>
<span style="cursor: pointer;">Я посилання, але span</span>
<div><!-- Кнопка для дій на поточній сторінці -->
<button onclick="handleClick()">Кнопка</button>
<!-- Посилання для переходу на іншу сторінку -->
<a href="/about">Про нас</a>
link.addEventListener('click', (e) => {
e.preventDefault()
router.navigate(e.target.href)
})
form.addEventListener('submit', (e) => {
if (!validate()) e.preventDefault()
})
element.addEventListener('contextmenu', (e) => {
e.preventDefault()
showCustomMenu(e.pageX, e.pageY)
})
element.addEventListener('dragstart', (e) => {
e.preventDefault()
startCustomDrag(e)
})
<button disabled>)Клік на посилання → перехід, відправка форми, контекстне меню і т.д.
event.preventDefault() // Браузер НЕ виконає свою дію
event.preventDefault() — працює завжди ✅return false — тільки для on<event> ⚠️Якщо скасувати першу подію — наступні не виконаються
Приклад: mousedown → focus
addEventListener('touchmove', handler, { passive: true })
// Повідомляє браузеру: "Я не викличу preventDefault"
if (event.defaultPrevented) return // Вже оброблено
preventDefault() — скасовує дію браузераstopPropagation() — зупиняє спливання подіїВикористовуйте <button> для кнопок, <a> для посилань
<a href="#modal" id="openModal">Відкрити модальне вікно</a>
preventDefault)display)event.preventDefault() + логіка показу/приховуванняisSubmittingdragstart на зображенні — показати повідомленняevent.preventDefault() на dragstart📚 MDN Web Docs
event.preventDefault() — повна документація методу
event.defaultPrevented — властивість перевірки стану
🎓 Специфікація W3C
🔗 Пов'язані статті
Наступна стаття: Запуск користувацьких подій — як створювати власні події