Event Types
The most important browser event types — mouse, keyboard, form, window, and more.
1. Introduction
The browser fires dozens of event types. This lesson covers the ones you will use in almost every project, grouped by category, with the key properties of each event object.
2. Theory
2.1 Mouse events
| Event | When it fires |
|---|---|
click | Mouse button pressed and released on element |
dblclick | Double-click |
mousedown | Mouse button pressed (before release) |
mouseup | Mouse button released |
mouseenter | Cursor enters element (does not bubble) |
mouseleave | Cursor leaves element (does not bubble) |
mouseover | Cursor enters element or child (bubbles) |
mouseout | Cursor leaves element or child (bubbles) |
mousemove | Cursor moves over element |
contextmenu | Right-click |
el.addEventListener('click', e => {
console.log(e.clientX, e.clientY); // position in viewport
console.log(e.pageX, e.pageY); // position in document (includes scroll)
console.log(e.button); // 0=left, 1=middle, 2=right
console.log(e.ctrlKey, e.shiftKey, e.altKey); // modifier keys held
});
2.2 Keyboard events
| Event | When |
|---|---|
keydown | Key pressed (fires repeatedly if held) |
keyup | Key released |
keypress | Deprecated — use keydown |
document.addEventListener('keydown', e => {
console.log(e.key); // 'Enter', 'a', 'ArrowLeft', ' ' (space)
console.log(e.code); // 'KeyA', 'Space', 'ArrowLeft' (physical key)
console.log(e.ctrlKey); // true if Ctrl held
// Common patterns
if (e.key === 'Escape') closeModal();
if (e.key === 'Enter') submitForm();
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
saveDocument();
}
});
e.key gives the character/key name; e.code gives the physical key position (useful for games where you care about key location, not character).
2.3 Form events
| Event | When / where |
|---|---|
submit | Form submitted (button click or Enter) |
input | Value changes on input/textarea/select (fires every change) |
change | Value committed — fires on blur for text inputs, immediately for checkboxes/select |
focus | Element receives focus (does not bubble) |
blur | Element loses focus (does not bubble) |
focusin | Like focus but bubbles |
focusout | Like blur but bubbles |
reset | Form reset button clicked |
// Real-time character count
textarea.addEventListener('input', e => {
charCount.textContent = e.target.value.length;
});
// Validate when user leaves field
emailInput.addEventListener('blur', e => {
if (!e.target.value.includes('@')) {
e.target.classList.add('error');
}
});
// Handle select change
countrySelect.addEventListener('change', e => {
loadRegions(e.target.value);
});
2.4 Window / document events
| Event | When |
|---|---|
DOMContentLoaded | HTML parsed, DOM built (on document) |
load | Page fully loaded including images (on window) |
beforeunload | User about to leave (can show confirm dialog) |
resize | Window resized |
scroll | Page or element scrolled |
online/offline | Network connectivity changed |
visibilitychange | Tab hidden or shown |
// Warn before leaving with unsaved changes
let isDirty = false;
form.addEventListener('input', () => isDirty = true);
window.addEventListener('beforeunload', e => {
if (isDirty) {
e.preventDefault();
e.returnValue = ''; // required for some browsers
}
});
// Responsive JS — react to window resize
window.addEventListener('resize', throttle(() => {
console.log('Width:', window.innerWidth);
}, 200));
// Pause expensive work when tab is hidden
document.addEventListener('visibilitychange', () => {
if (document.hidden) pauseAnimation();
else resumeAnimation();
});
2.5 Clipboard events
document.addEventListener('copy', e => {
const selected = window.getSelection().toString();
console.log('Copied:', selected);
});
document.addEventListener('paste', e => {
const text = e.clipboardData.getData('text/plain');
console.log('Pasted:', text);
e.preventDefault(); // prevent paste into the page
});
2.6 Drag and drop events
// On the draggable element
el.addEventListener('dragstart', e => {
e.dataTransfer.setData('text/plain', el.id);
});
// On the drop target
dropZone.addEventListener('dragover', e => {
e.preventDefault(); // required to allow drop
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', e => {
e.preventDefault();
const id = e.dataTransfer.getData('text/plain');
dropZone.appendChild(document.getElementById(id));
});
2.7 Custom events
// Create and dispatch a custom event
const event = new CustomEvent('cart:updated', {
detail: { count: 5, total: 49.99 },
bubbles: true,
});
document.dispatchEvent(event);
// Listen for it anywhere
document.addEventListener('cart:updated', e => {
cartBadge.textContent = e.detail.count;
});
3. Real World Example
// Keyboard-accessible modal
const modal = document.querySelector('#modal');
const openBtn = document.querySelector('#open-modal');
const closeBtn = modal.querySelector('.close');
function openModal() {
modal.classList.add('visible');
modal.setAttribute('aria-hidden', 'false');
closeBtn.focus(); // move focus into modal
}
function closeModal() {
modal.classList.remove('visible');
modal.setAttribute('aria-hidden', 'true');
openBtn.focus(); // return focus to trigger
}
openBtn.addEventListener('click', openModal);
closeBtn.addEventListener('click', closeModal);
// Close on Escape key
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && modal.classList.contains('visible')) {
closeModal();
}
});
// Close on backdrop click
modal.addEventListener('click', e => {
if (e.target === modal) closeModal(); // only if clicking backdrop, not content
});
4. Code Example
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Event Types Demo</title></head>
<body>
<div id="mouse-box" style="width:200px;height:200px;background:#e0e0e0;display:flex;align-items:center;justify-content:center">
Hover / Click me
</div>
<input id="key-input" placeholder="Press keys here" style="margin:1rem 0;display:block">
<input id="search" placeholder="Debounced search">
<p id="output"></p>
<script>
const out = document.querySelector('#output');
const box = document.querySelector('#mouse-box');
// Mouse events
box.addEventListener('mouseenter', () => box.style.background = '#93c5fd');
box.addEventListener('mouseleave', () => box.style.background = '#e0e0e0');
box.addEventListener('click', e => out.textContent = `Clicked at (${e.clientX}, ${e.clientY})`);
box.addEventListener('dblclick', () => out.textContent = 'Double-clicked!');
// Keyboard events
document.querySelector('#key-input').addEventListener('keydown', e => {
out.textContent = `Key: "${e.key}" | Code: ${e.code} | Ctrl:${e.ctrlKey}`;
});
// Debounced input
let timer;
document.querySelector('#search').addEventListener('input', e => {
clearTimeout(timer);
timer = setTimeout(() => {
out.textContent = `Searching for: "${e.target.value}"`;
}, 400);
});
// Window events
window.addEventListener('resize', () => {
document.title = `${window.innerWidth}×${window.innerHeight}`;
});
</script>
</body>
</html>
5. Code Breakdown
mouseenter vs click
mouseenter/mouseleave fire exactly once when the cursor crosses the element boundary — perfect for hover styles. click fires when the full press-and-release cycle completes on the same element.
e.key vs e.code
e.key gives the character ("a", "Enter", "ArrowLeft"). e.code gives the physical key ("KeyA", "Enter", "ArrowLeft"). For text input handling use e.key. For game controls where position matters (WASD) use e.code.
Inline debounce
A manual debounce using closured timer. Each keystroke clears the previous timeout and sets a new one. Only the final keystroke's timeout completes (400ms silence).
6. Common Mistakes
Mistake 1 — Using keypress (deprecated)
// Bad — keypress is deprecated
input.addEventListener('keypress', handler);
// Good — use keydown or keyup
input.addEventListener('keydown', handler);
Mistake 2 — Confusing input and change events
// input fires on every keystroke — best for real-time feedback
input.addEventListener('input', updatePreview);
// change fires when value is committed (on blur for text fields)
input.addEventListener('change', saveToStorage); // fires after user leaves field
Mistake 3 — mouseenter vs mouseover confusion
// mouseover BUBBLES — fires when cursor enters a child element too
// mouseenter does NOT bubble — fires only when cursor enters THIS element
// For hover effects, use mouseenter/mouseleave
7. Best Practices
- Use keydown, not the deprecated keypress.
- Use mouseenter/mouseleave for hover effects (no bubbling surprises).
- Use input for real-time feedback, change for committed value changes.
- Debounce resize and scroll handlers — they fire many times per second.
- Use visibilitychange to pause expensive work when the tab is hidden.
- Use CustomEvent for component communication instead of tight coupling.
8. Practice Exercise
- Build a "Keyboard Visualiser" — a div that shows each key pressed, highlights modifier keys, and plays a visual flash for Shift/Ctrl/Alt.
- Build a form with: email (validate on blur), message textarea (character counter with max 200, input event), and a submit that only works if both fields are valid.
- Create a drag-and-drop list where items can be rearranged by dragging.
9. Assignment
Build a "Markdown-style Text Editor."
- A textarea for writing, a preview div on the right.
- On input event (debounced 300ms), convert simple markdown to HTML and show in the preview (replace **bold** with <strong>, *italic* with <em>, and newlines with <br>).
- Implement keyboard shortcuts: Ctrl+B wraps selected text in **, Ctrl+I wraps in *.
- Warn with beforeunload if there is unsaved content.
- Add a character/word count that updates on input.
Deliverable: One HTML file.
10. Interview Questions
- What is the difference between the input and change events?
input fires on every value change (every keystroke). change fires when the value is committed — on blur for text inputs, immediately for checkboxes, select, and radio buttons. - What is the difference between e.key and e.code?
e.key gives the logical key value ('a', 'Enter'). e.code gives the physical key identifier ('KeyA', 'Enter'). Use e.key for character/shortcut detection; use e.code for layout-independent key detection (e.g., game controls). - What is the difference between mouseenter and mouseover?
mouseenter fires only when the cursor enters the element itself and does not bubble. mouseover fires when the cursor enters the element or any of its children and bubbles. Use mouseenter/mouseleave for hover effects to avoid false triggers from child elements. - What is a CustomEvent?
A user-defined event created with new CustomEvent('name', { detail: data }). It can be dispatched on any DOM element with dispatchEvent() and listened for with addEventListener. Used for decoupled component communication.
11. Additional Resources
- MDN — Event reference — complete list of all event types
- MDN — KeyboardEvent
- MDN — MouseEvent
- MDN — Drag and Drop API
- javascript.info — Mouse events
- javascript.info — Keyboard events