DOM events are the foundation of interactive web applications, allowing JavaScript to respond to user interactions, browser events, and custom application events. Understanding event handling is crucial for creating dynamic, responsive user interfaces and building modern web applications.
In this tutorial, we'll explore event types, event handling patterns, event delegation, modern event APIs, and best practices for working with DOM events in JavaScript.
Understanding Events
Events are signals that something has occurred in the DOM, such as user interactions or browser state changes.
Event Basics
// Event object properties
function handleEvent(event) {
console.log('Event type:', event.type);
console.log('Target element:', event.target);
console.log('Current target:', event.currentTarget);
console.log('Timestamp:', event.timeStamp);
console.log('Bubbles:', event.bubbles);
console.log('Cancelable:', event.cancelable);
}
// Event phases
// 1. Capturing phase - Event travels down from window to target
// 2. Target phase - Event reaches the target element
// 3. Bubbling phase - Event travels up from target to window
button.addEventListener('click', function(event) {
console.log('Event phase:', event.eventPhase);
// 1: CAPTURING_PHASE
// 2: AT_TARGET
// 3: BUBBLING_PHASE
}, true); // Use capture phase
// Event flow example
document.getElementById('parent').addEventListener('click', function(event) {
console.log('Parent clicked (bubbling)');
}, false); // Bubble phase
document.getElementById('parent').addEventListener('click', function(event) {
console.log('Parent clicked (capturing)');
}, true); // Capture phase
document.getElementById('child').addEventListener('click', function(event) {
console.log('Child clicked');
});
Event Object
// Event object properties
button.addEventListener('click', function(event) {
// Basic properties
console.log('Type:', event.type); // 'click'
console.log('Target:', event.target); // Element that was clicked
console.log('Current target:', event.currentTarget); // Element with listener
console.log('Time:', event.timeStamp); // When event occurred
console.log('View:', event.view); // Window object
// Position properties (mouse events)
console.log('Client X:', event.clientX); // Viewport X
console.log('Client Y:', event.clientY); // Viewport Y
console.log('Screen X:', event.screenX); // Screen X
console.log('Screen Y:', event.screenY); // Screen Y
// Keyboard properties (keyboard events)
console.log('Key:', event.key); // 'a', 'Enter', etc.
console.log('Code:', event.keyCode); // Key code (deprecated)
console.log('Which:', event.which); // Key code (deprecated)
console.log('Alt:', event.altKey); // Alt key pressed
console.log('Ctrl:', event.ctrlKey); // Ctrl key pressed
console.log('Shift:', event.shiftKey); // Shift key pressed
console.log('Meta:', event.metaKey); // Meta/Cmd key pressed
});
// Event methods
button.addEventListener('click', function(event) {
// Prevent default behavior
event.preventDefault();
// Stop event propagation
event.stopPropagation();
// Stop immediate propagation
event.stopImmediatePropagation();
// Check if default was prevented
console.log('Default prevented:', event.defaultPrevented);
});
Event Listeners
Methods for adding and removing event listeners.
addEventListener
// Basic event listener
const button = document.getElementById('myButton');
function handleClick(event) {
console.log('Button clicked!');
console.log('Event:', event);
}
button.addEventListener('click', handleClick);
// Event listener with options
button.addEventListener('click', handleClick, {
once: true, // Remove after first trigger
passive: true, // Improve performance for scroll/touch
capture: false, // Use bubble phase (default)
signal: abortSignal // Abort signal for cancellation
});
// Multiple events on same element
button.addEventListener('click', handleClick);
button.addEventListener('mouseover', handleMouseOver);
button.addEventListener('mouseout', handleMouseOut);
// Event listener with arrow function
button.addEventListener('click', (event) => {
console.log('Arrow function:', this); // 'this' is button
});
// Event listener with regular function
button.addEventListener('click', function(event) {
console.log('Regular function:', this); // 'this' is button
});
// Event listener with object method
const handler = {
element: null,
init(element) {
this.element = element;
this.element.addEventListener('click', this.handleClick);
},
handleClick(event) {
console.log('Method:', this.element); // 'this' is handler object
},
destroy() {
if (this.element) {
this.element.removeEventListener('click', this.handleClick);
}
}
};
handler.init(button);
removeEventListener
// Remove specific listener
function handleClick(event) {
console.log('Handling click');
}
button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);
// Remove anonymous function (need reference)
const handler = function(event) {
console.log('Anonymous handler');
};
button.addEventListener('click', handler);
button.removeEventListener('click', handler);
// Remove all listeners of type
function removeAllListeners(element, eventType) {
// Clone element to remove all listeners
const clone = element.cloneNode(true);
element.parentNode.replaceChild(clone, element);
}
// Remove listener with capture option
button.addEventListener('click', handler, true); // Capture phase
button.removeEventListener('click', handler, true); // Must match options
// Safe removal
function safeRemoveListener(element, eventType, handler) {
try {
element.removeEventListener(eventType, handler);
} catch (error) {
console.error('Error removing listener:', error);
}
}
Mouse Events
Events triggered by mouse interactions.
Mouse Event Types
// Click events
element.addEventListener('click', function(event) {
console.log('Click at:', event.clientX, event.clientY);
});
// Double click
element.addEventListener('dblclick', function(event) {
console.log('Double click');
});
// Mouse down/up
element.addEventListener('mousedown', function(event) {
console.log('Mouse down:', event.button); // 0=left, 1=middle, 2=right
});
element.addEventListener('mouseup', function(event) {
console.log('Mouse up:', event.button);
});
// Mouse move
element.addEventListener('mousemove', function(event) {
console.log('Mouse moved to:', event.clientX, event.clientY);
// Throttle mouse move events for performance
});
// Mouse enter/leave
element.addEventListener('mouseenter', function(event) {
console.log('Mouse entered element');
});
element.addEventListener('mouseleave', function(event) {
console.log('Mouse left element');
});
// Mouse over/out (bubbles)
element.addEventListener('mouseover', function(event) {
console.log('Mouse over element');
});
element.addEventListener('mouseout', function(event) {
console.log('Mouse out of element');
});
// Context menu
element.addEventListener('contextmenu', function(event) {
event.preventDefault(); // Prevent default context menu
console.log('Context menu requested');
});
// Wheel event
element.addEventListener('wheel', function(event) {
console.log('Wheel delta:', event.deltaY);
// Prevent page scroll
if (event.ctrlKey) {
event.preventDefault();
}
});
Mouse Event Properties
// Mouse position properties
element.addEventListener('mousemove', function(event) {
console.log('Client coordinates:', event.clientX, event.clientY);
console.log('Screen coordinates:', event.screenX, event.screenY);
console.log('Page coordinates:', event.pageX, event.pageY);
// Calculate relative position
const rect = event.target.getBoundingClientRect();
const relativeX = event.clientX - rect.left;
const relativeY = event.clientY - rect.top;
console.log('Relative position:', relativeX, relativeY);
});
// Mouse button properties
element.addEventListener('mousedown', function(event) {
console.log('Button:', event.button); // 0=left, 1=middle, 2=right
console.log('Buttons:', event.buttons); // Bitmask of pressed buttons
});
// Modifier keys
element.addEventListener('click', function(event) {
console.log('Alt:', event.altKey);
console.log('Ctrl:', event.ctrlKey);
console.log('Shift:', event.shiftKey);
console.log('Meta:', event.metaKey);
// Check for common combinations
if (event.ctrlKey && event.key === 's') {
console.log('Ctrl+S pressed');
event.preventDefault(); // Prevent save dialog
}
});
// Related target
element.addEventListener('click', function(event) {
console.log('Target:', event.target);
console.log('Related target:', event.relatedTarget);
// For mouseover, relatedTarget is the element being left
// For mouseout, relatedTarget is the element being entered
});
Keyboard Events
Events triggered by keyboard interactions.
Keyboard Event Types
// Key events
element.addEventListener('keydown', function(event) {
console.log('Key pressed down:', event.key);
});
element.addEventListener('keyup', function(event) {
console.log('Key released:', event.key);
});
element.addEventListener('keypress', function(event) {
console.log('Key pressed (character):', event.key);
// Note: keypress is deprecated, use keydown/keyup
});
// Input events (for form elements)
input.addEventListener('input', function(event) {
console.log('Input changed:', event.target.value);
});
input.addEventListener('beforeinput', function(event) {
console.log('Input about to change:', event.data);
});
input.addEventListener('change', function(event) {
console.log('Input value changed:', event.target.value);
});
// Composition events (for complex input)
input.addEventListener('compositionstart', function(event) {
console.log('Composition started');
});
input.addEventListener('compositionupdate', function(event) {
console.log('Composition updating:', event.data);
});
input.addEventListener('compositionend', function(event) {
console.log('Composition ended:', event.data);
});
Keyboard Event Properties
// Key identification
element.addEventListener('keydown', function(event) {
console.log('Key:', event.key); // 'a', 'Enter', 'ArrowUp', etc.
console.log('Code:', event.code); // 'KeyA', 'Enter', 'ArrowUp', etc.
console.log('Location:', event.location); // 0=general, 1=left, 2=right, 3=numpad
console.log('Which:', event.which); // Numeric key code (deprecated)
console.log('KeyCode:', event.keyCode); // Numeric key code (deprecated)
});
// Modifier keys
element.addEventListener('keydown', function(event) {
console.log('Alt:', event.altKey);
console.log('Ctrl:', event.ctrlKey);
console.log('Shift:', event.shiftKey);
console.log('Meta:', event.metaKey);
// Check modifier combinations
if (event.ctrlKey && event.key === 'c') {
console.log('Copy shortcut');
}
if (event.altKey && event.key === 'Tab') {
console.log('Alt+Tab pressed');
}
});
// Input method
element.addEventListener('keydown', function(event) {
console.log('Input method:', event.inputMethod);
// Possible values: 'ime', 'keyboard', 'paste', 'drop', 'handwriting'
});
// Repeat detection
element.addEventListener('keydown', function(event) {
console.log('Key repeating:', event.repeat);
if (event.repeat) {
console.log('Key is being held down');
}
});
Touch Events
Events triggered by touch interactions on mobile devices.
Touch Event Types
// Touch events
element.addEventListener('touchstart', function(event) {
console.log('Touch started');
});
element.addEventListener('touchmove', function(event) {
console.log('Touch moving');
});
element.addEventListener('touchend', function(event) {
console.log('Touch ended');
});
element.addEventListener('touchcancel', function(event) {
console.log('Touch cancelled');
});
// Pointer events (unified mouse/touch)
element.addEventListener('pointerdown', function(event) {
console.log('Pointer down:', event.pointerType);
// pointerType: 'mouse', 'pen', 'touch'
});
element.addEventListener('pointermove', function(event) {
console.log('Pointer moving');
});
element.addEventListener('pointerup', function(event) {
console.log('Pointer up');
});
element.addEventListener('pointercancel', function(event) {
console.log('Pointer cancelled');
});
Touch Event Properties
// Touch points
element.addEventListener('touchstart', function(event) {
console.log('Number of touches:', event.touches.length);
// Iterate through all touch points
for (let i = 0; i < event.touches.length; i++) {
const touch = event.touches[i];
console.log(`Touch ${i}:`, touch.identifier, touch.clientX, touch.clientY);
}
});
// Touch lists
element.addEventListener('touchmove', function(event) {
console.log('Active touches:', event.touches);
console.log('Changed touches:', event.changedTouches);
console.log('Target touches:', event.targetTouches);
});
// Touch properties
element.addEventListener('touchstart', function(event) {
const touch = event.touches[0];
console.log('Touch ID:', touch.identifier);
console.log('Force:', touch.force || 0); // Pressure (0-1)
console.log('Radius X:', touch.radiusX || 0);
console.log('Radius Y:', touch.radiusY || 0);
console.log('Rotation angle:', touch.rotationAngle || 0);
});
// Gesture detection
class TouchGesture {
constructor(element) {
this.element = element;
this.startX = 0;
this.startY = 0;
this.startTime = 0;
this.element.addEventListener('touchstart', this.handleStart.bind(this));
this.element.addEventListener('touchmove', this.handleMove.bind(this));
this.element.addEventListener('touchend', this.handleEnd.bind(this));
}
handleStart(event) {
const touch = event.touches[0];
this.startX = touch.clientX;
this.startY = touch.clientY;
this.startTime = event.timeStamp;
}
handleMove(event) {
const touch = event.touches[0];
const deltaX = touch.clientX - this.startX;
const deltaY = touch.clientY - this.startY;
console.log('Swipe delta:', deltaX, deltaY);
}
handleEnd(event) {
const touch = event.changedTouches[0];
const deltaX = touch.clientX - this.startX;
const deltaY = touch.clientY - this.startY;
const deltaTime = event.timeStamp - this.startTime;
// Detect swipe
if (Math.abs(deltaX) > 50 && deltaTime < 500) {
console.log('Horizontal swipe detected');
this.element.dispatchEvent(new CustomEvent('swipe', {
detail: { direction: deltaX > 0 ? 'right' : 'left' }
}));
}
}
}
Form Events
Events specific to form elements and user input.
Form Event Types
// Form events
const form = document.getElementById('myForm');
form.addEventListener('submit', function(event) {
event.preventDefault(); // Prevent default form submission
console.log('Form submitted');
// Custom submission logic
submitForm(this);
});
form.addEventListener('reset', function(event) {
console.log('Form reset');
});
// Input events
const input = document.getElementById('myInput');
input.addEventListener('input', function(event) {
console.log('Input value:', event.target.value);
console.log('Input type:', event.inputType);
});
input.addEventListener('change', function(event) {
console.log('Input changed:', event.target.value);
});
// Focus events
input.addEventListener('focus', function(event) {
console.log('Input focused');
this.classList.add('focused');
});
input.addEventListener('blur', function(event) {
console.log('Input lost focus');
this.classList.remove('focused');
});
// Focus events (bubbling)
input.addEventListener('focusin', function(event) {
console.log('Element about to receive focus');
});
input.addEventListener('focusout', function(event) {
console.log('Element about to lose focus');
});
// Validation events
input.addEventListener('invalid', function(event) {
console.log('Input invalid:', event.target.validationMessage);
// Show custom error message
showError(event.target, event.target.validationMessage);
});
// Select events
const select = document.getElementById('mySelect');
select.addEventListener('change', function(event) {
console.log('Selected value:', event.target.value);
console.log('Selected text:', event.target.options[event.target.selectedIndex].text);
});
// Textarea events
const textarea = document.getElementById('myTextarea');
textarea.addEventListener('input', function(event) {
console.log('Current length:', event.target.value.length);
console.log('Remaining characters:', event.target.maxLength - event.target.value.length);
});
Form Validation Events
// Custom validation with events
class FormValidator {
constructor(form) {
this.form = form;
this.fields = form.querySelectorAll('input, select, textarea');
this.form.addEventListener('submit', this.handleSubmit.bind(this));
this.fields.forEach(field => {
field.addEventListener('input', this.validateField.bind(this));
field.addEventListener('blur', this.validateField.bind(this));
});
}
validateField(event) {
const field = event.target;
const isValid = this.checkField(field);
if (isValid) {
field.classList.remove('invalid');
field.classList.add('valid');
} else {
field.classList.remove('valid');
field.classList.add('invalid');
}
}
checkField(field) {
const value = field.value.trim();
const type = field.type;
const required = field.hasAttribute('required');
// Required field validation
if (required && !value) {
field.setCustomValidity('This field is required');
return false;
}
// Type-specific validation
switch (type) {
case 'email':
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
field.setCustomValidity('Please enter a valid email');
return false;
}
break;
case 'tel':
const phoneRegex = /^\+?[\d\s-()]{10,}$/;
if (!phoneRegex.test(value)) {
field.setCustomValidity('Please enter a valid phone number');
return false;
}
break;
}
field.setCustomValidity('');
return true;
}
handleSubmit(event) {
event.preventDefault();
// Validate all fields
let isValid = true;
this.fields.forEach(field => {
if (!this.checkField(field)) {
isValid = false;
}
});
if (isValid) {
console.log('Form is valid, submitting...');
this.submitForm();
} else {
console.log('Form has validation errors');
this.showErrors();
}
}
submitForm() {
// Custom submission logic
const formData = new FormData(this.form);
fetch('/api/submit', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log('Form submitted successfully:', data);
this.showSuccess();
})
.catch(error => {
console.error('Form submission error:', error);
this.showError(error.message);
});
}
}
Event Delegation
Event delegation is a powerful pattern for handling events efficiently.
Basic Delegation
// Instead of this:
document.querySelectorAll('.button').forEach(button => {
button.addEventListener('click', function(event) {
console.log('Button clicked:', event.target);
});
});
// Use event delegation:
document.addEventListener('click', function(event) {
if (event.target.matches('.button')) {
console.log('Button clicked:', event.target);
}
});
// More specific delegation
const container = document.getElementById('container');
container.addEventListener('click', function(event) {
const button = event.target.closest('.button');
if (button) {
console.log('Container button clicked:', button);
}
});
// Multiple event types delegation
document.addEventListener('click', handleDelegatedClick);
document.addEventListener('focus', handleDelegatedFocus);
function handleDelegatedClick(event) {
if (event.target.matches('.interactive')) {
console.log('Interactive element clicked');
}
}
function handleDelegatedFocus(event) {
if (event.target.matches('input, textarea')) {
console.log('Input element focused');
}
}
Advanced Delegation
// Delegation utility class
class EventDelegator {
constructor(container) {
this.container = container;
this.handlers = new Map();
}
on(selector, eventType, handler) {
const key = `${eventType}:${selector}`;
if (!this.handlers.has(key)) {
this.handlers.set(key, []);
}
this.handlers.get(key).push(handler);
// Only add one listener per combination
if (this.handlers.get(key).length === 1) {
this.container.addEventListener(eventType, this.handleEvent.bind(this));
}
}
handleEvent(event) {
// Find matching handlers
this.handlers.forEach((handlers, key) => {
const [eventType, selector] = key.split(':');
if (eventType === event.type) {
handlers.forEach(handler => {
const target = event.target.closest(selector);
if (target) {
handler.call(target, event);
}
});
}
});
}
off(selector, eventType, handler) {
const key = `${eventType}:${selector}`;
const handlers = this.handlers.get(key) || [];
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
if (handlers.length === 0) {
this.container.removeEventListener(eventType, this.handleEvent.bind(this));
this.handlers.delete(key);
}
}
}
// Usage
const delegator = new EventDelegator(document.body);
delegator.on('.button', 'click', function(event) {
console.log('Button clicked:', this);
});
delegator.on('.link', 'click', function(event) {
console.log('Link clicked:', this);
event.preventDefault();
});
Custom Events
Creating and dispatching custom events for application communication.
Creating Custom Events
// CustomEvent constructor
const customEvent = new CustomEvent('custom-event', {
detail: { message: 'Hello from custom event', data: { id: 123 } },
bubbles: true,
cancelable: true
});
// Dispatch custom event
element.dispatchEvent(customEvent);
// Listen for custom event
element.addEventListener('custom-event', function(event) {
console.log('Custom event received:', event.detail);
});
// Custom event with different phases
const bubblingEvent = new CustomEvent('bubbling-event', {
detail: { message: 'Bubbling' },
bubbles: true
});
const nonBubblingEvent = new CustomEvent('non-bubbling-event', {
detail: { message: 'Non-bubbling' },
bubbles: false
});
// Event namespacing for organization
const appEvents = {
USER_LOGIN: 'app:user-login',
USER_LOGOUT: 'app:user-logout',
DATA_LOADED: 'app:data-loaded',
ERROR_OCCURRED: 'app:error-occurred'
};
// Dispatch namespaced events
function dispatchUserLogin(userData) {
const event = new CustomEvent(appEvents.USER_LOGIN, {
detail: userData
});
document.dispatchEvent(event);
}
function dispatchError(error) {
const event = new CustomEvent(appEvents.ERROR_OCCURRED, {
detail: { error, timestamp: Date.now() }
});
document.dispatchEvent(event);
}
EventTarget and Event Buses
// EventTarget for custom event emitters
class EventEmitter {
constructor() {
this.listeners = new Map();
}
on(eventType, listener) {
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, []);
}
this.listeners.get(eventType).push(listener);
}
off(eventType, listener) {
if (!this.listeners.has(eventType)) return;
const listeners = this.listeners.get(eventType);
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
emit(eventType, data) {
if (!this.listeners.has(eventType)) return;
const event = new CustomEvent(eventType, {
detail: data
});
this.listeners.get(eventType).forEach(listener => {
listener(event);
});
}
}
// Usage
const emitter = new EventEmitter();
emitter.on('message', function(event) {
console.log('Message received:', event.detail);
});
emitter.emit('message', { text: 'Hello World' });
// Application event bus
class EventBus {
constructor() {
this.events = new Map();
}
subscribe(eventType, callback) {
if (!this.events.has(eventType)) {
this.events.set(eventType, []);
}
this.events.get(eventType).push(callback);
}
unsubscribe(eventType, callback) {
if (!this.events.has(eventType)) return;
const callbacks = this.events.get(eventType);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
publish(eventType, data) {
if (!this.events.has(eventType)) return;
this.events.get(eventType).forEach(callback => {
callback(data);
});
}
}
// Usage
const eventBus = new EventBus();
eventBus.subscribe('user:login', function(userData) {
console.log('User logged in:', userData);
});
eventBus.publish('user:login', { id: 123, name: 'John' });
Modern Event APIs
Modern browser APIs for advanced event handling.
Passive Event Listeners
// Passive listeners for better scroll performance
// Check for passive event listener support
const supportsPassive = (() => {
let supports = false;
try {
const opts = Object.defineProperty({}, 'passive', {
get: function() { supports = true; }
});
window.addEventListener('test', null, opts);
window.removeEventListener('test', null, opts);
} catch (e) {}
return supports;
})();
// Add passive scroll listener
function addPassiveScrollListener(element, callback) {
if (supportsPassive) {
element.addEventListener('scroll', callback, { passive: true });
} else {
// Fallback for older browsers
let ticking = false;
element.addEventListener('scroll', function(event) {
if (!ticking) {
requestAnimationFrame(function() {
callback(event);
ticking = false;
});
ticking = true;
}
});
}
}
// Usage
addPassiveScrollListener(window, function(event) {
console.log('Scroll position:', window.scrollY);
});
// Passive touch listener
element.addEventListener('touchstart', function(event) {
console.log('Touch started');
}, { passive: true });
AbortController
// AbortController for canceling operations
const controller = new AbortController();
const signal = controller.signal;
// Add listener with abort signal
element.addEventListener('click', function(event) {
console.log('Click handler');
}, { signal });
// Abort the operation
function abortOperation() {
controller.abort();
console.log('Operation aborted');
}
// Check if operation was aborted
if (signal.aborted) {
console.log('Operation was aborted');
}
// Use with fetch
function fetchDataWithAbort(url) {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(response => response.json())
.then(data => console.log('Data:', data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch was aborted');
} else {
console.error('Fetch error:', error);
}
});
// Abort fetch after timeout
setTimeout(() => controller.abort(), 5000);
return controller;
}
// Usage
const fetchController = fetchDataWithAbort('/api/data');
// Later: fetchController.abort();
Performance and Best Practices
Optimizing event handling for better performance and user experience.
Performance Optimization
// Throttle high-frequency events
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Debounce rapid events
function debounce(func, delay) {
let timeoutId;
return function() {
const args = arguments;
const context = this;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
// Usage for scroll events
window.addEventListener('scroll', throttle(function(event) {
console.log('Throttled scroll:', window.scrollY);
}, 100));
// Usage for search input
searchInput.addEventListener('input', debounce(function(event) {
console.log('Debounced search:', event.target.value);
performSearch(event.target.value);
}, 300));
// Event listener cleanup
class EventManager {
constructor() {
this.listeners = new WeakMap();
}
addListener(element, event, handler) {
element.addEventListener(event, handler);
if (!this.listeners.has(element)) {
this.listeners.set(element, []);
}
this.listeners.get(element).push({ event, handler });
}
cleanup(element) {
const listeners = this.listeners.get(element) || [];
listeners.forEach(({ event, handler }) => {
element.removeEventListener(event, handler);
});
this.listeners.delete(element);
}
}
// Usage
const eventManager = new EventManager();
eventManager.addListener(button, 'click', handler);
eventManager.cleanup(button);
Accessibility Considerations
// Keyboard accessibility
button.addEventListener('keydown', function(event) {
// Handle keyboard interactions for accessibility
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.click();
}
});
// Focus management
function manageFocus(element) {
element.addEventListener('focus', function() {
this.setAttribute('aria-expanded', 'true');
});
element.addEventListener('blur', function() {
this.setAttribute('aria-expanded', 'false');
});
}
// Screen reader announcements
function announceToScreenReader(message) {
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', 'polite');
announcement.textContent = message;
document.body.appendChild(announcement);
setTimeout(() => {
document.body.removeChild(announcement);
}, 1000);
}
// Usage with events
button.addEventListener('click', function() {
announceToScreenReader('Button clicked');
});
// Touch accessibility
let lastTouchEnd = 0;
element.addEventListener('touchend', function(event) {
const now = Date.now();
// Prevent accidental clicks from touch
if (now - lastTouchEnd < 500) {
event.preventDefault();
}
lastTouchEnd = now;
});
Summary
Key Takeaways
- Events enable interactive and responsive web applications
- Event delegation improves performance and handles dynamic content
- Touch events are essential for mobile applications
- Custom events enable component communication
- Modern APIs provide better performance and control
Best Practices
- Use event delegation for dynamic content and performance
- Throttle/debounce high-frequency events (scroll, resize)
- Remove event listeners to prevent memory leaks
- Use passive listeners for scroll/touch events when possible
- Consider accessibility in event handling
Common Pitfalls
- Not removing event listeners causing memory leaks
- Adding too many individual listeners instead of using delegation
- Not throttling high-frequency events causing performance issues
- Forgetting to preventDefault when needed
- Not considering accessibility in event handling