Master JavaScript event handling to create interactive web applications. Learn about event types, event listeners, event objects, bubbling, capturing, and advanced event management techniques.
Master JavaScript event handling to create interactive web applications. Learn about event types, event listeners, event objects, bubbling, capturing, and advanced event management techniques.
Events are actions that happen in the browser - user interactions, page loading, timers, and more:
1// Common event types
2// Mouse events: click, mouseover, mouseout, mousemove
3// Keyboard events: keydown, keyup, keypress
4// Form events: submit, input, change, focus, blur
5// Window events: load, resize, scroll
6// Touch events: touchstart, touchmove, touchend (mobile)
7
8// Basic event listener syntax
9element.addEventListener('eventType', function() {
10 // Event handler code
11});There are several ways to handle events in JavaScript:
1// Method 1: addEventListener (preferred)
2const button = document.getElementById('myButton');
3button.addEventListener('click', function() {
4 console.log('Button clicked!');
5});
6
7// Method 2: Arrow function
8button.addEventListener('click', () => {
9 console.log('Button clicked with arrow function!');
10});
11
12// Method 3: Named function
13function handleClick() {
14 console.log('Button clicked with named function!');
15}
16button.addEventListener('click', handleClick);
17
18// Method 4: HTML attribute (not recommended)
19// <button onclick="alert('Clicked!')">Click me</button>
20
21// Method 5: DOM property (limited)
22button.onclick = function() {
23 console.log('Using onclick property');
24};Clean up event listeners to prevent memory leaks:
1// You must use the same function reference to remove
2function myHandler() {
3 console.log('Handle this!');
4}
5
6// Add listener
7element.addEventListener('click', myHandler);
8
9// Remove listener (must use same function reference)
10element.removeEventListener('click', myHandler);
11
12// Anonymous functions can't be removed!
13// DON'T DO THIS if you need to remove later:
14element.addEventListener('click', function() {
15 console.log('Cannot remove this!');
16});Every event handler receives an event object with useful information:
1button.addEventListener('click', function(event) {
2 console.log('Event type:', event.type); // 'click'
3 console.log('Target element:', event.target); // The clicked element
4 console.log('Mouse X:', event.clientX); // Mouse X coordinate
5 console.log('Mouse Y:', event.clientY); // Mouse Y coordinate
6 console.log('Ctrl key held:', event.ctrlKey); // Boolean
7 console.log('Timestamp:', event.timeStamp); // When event occurred
8});Different events have different properties:
1// Mouse events
2document.addEventListener('click', function(e) {
3 console.log('Clicked at:', e.clientX, e.clientY); // Viewport coordinates
4 console.log('Screen position:', e.screenX, e.screenY); // Screen coordinates
5 console.log('Mouse button:', e.button); // 0=left, 1=middle, 2=right
6 console.log('Modifier keys:', {
7 ctrl: e.ctrlKey,
8 shift: e.shiftKey,
9 alt: e.altKey,
10 meta: e.metaKey // Cmd on Mac, Windows key on PC
11 });
12});
13
14// Keyboard events
15document.addEventListener('keydown', function(e) {
16 console.log('Key pressed:', e.key); // 'a', 'Enter', 'ArrowUp'
17 console.log('Key code:', e.keyCode); // Numeric code (deprecated)
18 console.log('Key code:', e.code); // 'KeyA', 'Enter', 'ArrowUp'
19 console.log('Shift held:', e.shiftKey);
20
21 // Prevent default behavior
22 if (e.key === 'Tab') {
23 e.preventDefault(); // Stops default tab behavior
24 console.log('Tab key intercepted');
25 }
26});
27
28// Form events
29const input = document.getElementById('myInput');
30input.addEventListener('input', function(e) {
31 console.log('Input value:', e.target.value);
32 console.log('Input type:', e.inputType); // 'insertText', 'deleteContentBackward'
33});Control event behavior and bubbling:
1// Prevent default behavior
2const link = document.getElementById('myLink');
3link.addEventListener('click', function(e) {
4 e.preventDefault(); // Stops link from navigating
5 console.log('Link click intercepted, no navigation');
6});
7
8// Stop event bubbling
9const parent = document.getElementById('parent');
10const child = document.getElementById('child');
11
12parent.addEventListener('click', function() {
13 console.log('Parent clicked');
14});
15
16child.addEventListener('click', function(e) {
17 console.log('Child clicked');
18 e.stopPropagation(); // Prevents parent handler from running
19});DOM events have three phases:
1// 1. Capturing Phase: Event travels DOWN from document to target
2// 2. Target Phase: Event reaches the target element
3// 3. Bubbling Phase: Event travels UP from target to document
4
5// document
6// ↓ (capturing)
7// body
8// ↓
9// div (parent)
10// ↓
11// button (target) ← Event occurs here
12// ↑
13// div (parent)
14// ↑ (bubbling)
15// body
16// ↑
17// documentBy default, events bubble up from child to parent:
1// HTML structure:
2// <div id="parent">
3// <button id="child">Click me</button>
4// </div>
5
6const parent = document.getElementById('parent');
7const child = document.getElementById('child');
8
9parent.addEventListener('click', function() {
10 console.log('Parent div clicked');
11});
12
13child.addEventListener('click', function() {
14 console.log('Child button clicked');
15});
16
17// When you click the button, you'll see:
18// "Child button clicked"
19// "Parent div clicked" ← This is bubbling!Listen during the capturing phase instead:
1// Third parameter 'true' enables capturing
2parent.addEventListener('click', function() {
3 console.log('Parent clicked (capturing)');
4}, true);
5
6child.addEventListener('click', function() {
7 console.log('Child clicked');
8});
9
10// Now when you click the button:
11// "Parent clicked (capturing)" ← Fires first!
12// "Child clicked"1// Stop bubbling
2child.addEventListener('click', function(e) {
3 console.log('Child clicked');
4 e.stopPropagation(); // Parent handler won't run
5});
6
7// Stop immediate propagation (stops other listeners on same element)
8child.addEventListener('click', function(e) {
9 console.log('First listener');
10 e.stopImmediatePropagation();
11});
12
13child.addEventListener('click', function(e) {
14 console.log('Second listener'); // Won't run!
15});Use bubbling to handle events efficiently:
1// Instead of adding listeners to every button...
2const buttons = document.querySelectorAll('button');
3buttons.forEach(button => {
4 button.addEventListener('click', handleClick); // Many listeners!
5});
6
7// Use delegation - one listener handles all!
8document.body.addEventListener('click', function(e) {
9 if (e.target.matches('button')) {
10 handleClick(e);
11 }
12});
13
14// Works for dynamically added elements too!
15function addNewButton() {
16 const btn = document.createElement('button');
17 btn.textContent = 'New Button';
18 document.body.appendChild(btn);
19 // No need to add listener - delegation handles it!
20}Forms generate many useful events:
1const form = document.getElementById('myForm');
2const input = document.getElementById('myInput');
3
4// Form submission
5form.addEventListener('submit', function(e) {
6 e.preventDefault(); // Prevent page refresh
7 console.log('Form submitted');
8
9 // Validate form data
10 const formData = new FormData(form);
11 for (const [key, value] of formData) {
12 console.log(`${key}: ${value}`);
13 }
14});
15
16// Input events
17input.addEventListener('input', function(e) {
18 console.log('Input changed:', e.target.value);
19 // Fires on every character typed
20});
21
22input.addEventListener('change', function(e) {
23 console.log('Input finished changing:', e.target.value);
24 // Fires when input loses focus and value changed
25});
26
27// Focus events
28input.addEventListener('focus', function(e) {
29 console.log('Input focused');
30 e.target.style.backgroundColor = '#e6f3ff';
31});
32
33input.addEventListener('blur', function(e) {
34 console.log('Input lost focus');
35 e.target.style.backgroundColor = '';
36});Real-time input validation with events:
1const emailInput = document.getElementById('email');
2const passwordInput = document.getElementById('password');
3const confirmInput = document.getElementById('confirm');
4
5// Email validation
6emailInput.addEventListener('input', function(e) {
7 const email = e.target.value;
8 const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
9
10 if (email.length === 0) {
11 showMessage(e.target, '', 'neutral');
12 } else if (isValid) {
13 showMessage(e.target, 'Valid email', 'success');
14 } else {
15 showMessage(e.target, 'Invalid email format', 'error');
16 }
17});
18
19// Password strength
20passwordInput.addEventListener('input', function(e) {
21 const password = e.target.value;
22 let strength = 0;
23
24 if (password.length >= 8) strength++;
25 if (/[a-z]/.test(password)) strength++;
26 if (/[A-Z]/.test(password)) strength++;
27 if (/[0-9]/.test(password)) strength++;
28 if (/[^A-Za-z0-9]/.test(password)) strength++;
29
30 const levels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong'];
31 showMessage(e.target, `Strength: ${levels[strength]}`,
32 strength < 2 ? 'error' : strength < 4 ? 'warning' : 'success');
33});
34
35// Confirm password
36confirmInput.addEventListener('input', function(e) {
37 const password = passwordInput.value;
38 const confirm = e.target.value;
39
40 if (confirm.length === 0) {
41 showMessage(e.target, '', 'neutral');
42 } else if (password === confirm) {
43 showMessage(e.target, 'Passwords match', 'success');
44 } else {
45 showMessage(e.target, 'Passwords do not match', 'error');
46 }
47});
48
49function showMessage(input, message, type) {
50 // Implementation to show validation message
51 console.log(`${input.name}: ${message} (${type})`);
52}1// File upload handling
2const fileInput = document.getElementById('fileUpload');
3fileInput.addEventListener('change', function(e) {
4 const files = e.target.files;
5
6 Array.from(files).forEach(file => {
7 console.log(`File: ${file.name}`);
8 console.log(`Size: ${file.size} bytes`);
9 console.log(`Type: ${file.type}`);
10
11 // Validate file
12 if (file.size > 5 * 1024 * 1024) { // 5MB
13 console.log('File too large');
14 return;
15 }
16
17 if (!file.type.startsWith('image/')) {
18 console.log('Invalid file type');
19 return;
20 }
21
22 // Process valid file
23 const reader = new FileReader();
24 reader.onload = function(e) {
25 console.log('File loaded');
26 // Handle file data
27 };
28 reader.readAsDataURL(file);
29 });
30});
31
32// Dynamic form generation
33function createDynamicForm() {
34 const form = document.createElement('form');
35
36 // Add form fields dynamically
37 const fields = [
38 { type: 'text', name: 'firstName', label: 'First Name', required: true },
39 { type: 'email', name: 'email', label: 'Email', required: true },
40 { type: 'tel', name: 'phone', label: 'Phone', required: false }
41 ];
42
43 fields.forEach(field => {
44 const div = document.createElement('div');
45 const label = document.createElement('label');
46 const input = document.createElement('input');
47
48 label.textContent = field.label;
49 input.type = field.type;
50 input.name = field.name;
51 input.required = field.required;
52
53 // Add validation
54 input.addEventListener('invalid', function(e) {
55 e.target.setCustomValidity(`Please enter a valid ${field.label.toLowerCase()}`);
56 });
57
58 input.addEventListener('input', function(e) {
59 e.target.setCustomValidity(''); // Clear custom message
60 });
61
62 div.appendChild(label);
63 div.appendChild(input);
64 form.appendChild(div);
65 });
66
67 return form;
68}Create and dispatch your own events:
1// Create custom event
2const customEvent = new CustomEvent('userLogin', {
3 detail: {
4 userId: 123,
5 username: 'john_doe',
6 timestamp: Date.now()
7 },
8 bubbles: true,
9 cancelable: true
10});
11
12// Listen for custom event
13document.addEventListener('userLogin', function(e) {
14 console.log('User logged in:', e.detail);
15});
16
17// Dispatch the event
18document.dispatchEvent(customEvent);
19
20// Custom event class
21class NotificationEvent extends CustomEvent {
22 constructor(type, message, level = 'info') {
23 super('notification', {
24 detail: { type, message, level, timestamp: Date.now() },
25 bubbles: true
26 });
27 }
28}
29
30// Usage
31const notification = new NotificationEvent('success', 'Data saved successfully');
32document.dispatchEvent(notification);Control event frequency for performance:
1// Throttling - limit function calls to once per interval
2function throttle(func, limit) {
3 let inThrottle;
4 return function() {
5 const args = arguments;
6 const context = this;
7 if (!inThrottle) {
8 func.apply(context, args);
9 inThrottle = true;
10 setTimeout(() => inThrottle = false, limit);
11 }
12 };
13}
14
15// Debouncing - delay function call until after interval
16function debounce(func, delay) {
17 let timeoutId;
18 return function() {
19 const args = arguments;
20 const context = this;
21 clearTimeout(timeoutId);
22 timeoutId = setTimeout(() => func.apply(context, args), delay);
23 };
24}
25
26// Usage
27const expensiveFunction = () => console.log('Expensive operation');
28
29// Throttle scroll events
30window.addEventListener('scroll', throttle(expensiveFunction, 100));
31
32// Debounce search input
33searchInput.addEventListener('input', debounce(function(e) {
34 console.log('Searching for:', e.target.value);
35}, 300));Create a centralized event system:
1class EventBus {
2 constructor() {
3 this.events = {};
4 }
5
6 // Subscribe to event
7 on(eventName, callback) {
8 if (!this.events[eventName]) {
9 this.events[eventName] = [];
10 }
11 this.events[eventName].push(callback);
12
13 // Return unsubscribe function
14 return () => {
15 this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
16 };
17 }
18
19 // Subscribe once
20 once(eventName, callback) {
21 const unsubscribe = this.on(eventName, (data) => {
22 callback(data);
23 unsubscribe();
24 });
25 return unsubscribe;
26 }
27
28 // Emit event
29 emit(eventName, data) {
30 if (this.events[eventName]) {
31 this.events[eventName].forEach(callback => callback(data));
32 }
33 }
34
35 // Remove all listeners for event
36 off(eventName) {
37 delete this.events[eventName];
38 }
39}
40
41// Usage
42const eventBus = new EventBus();
43
44// Subscribe to events
45const unsubscribe = eventBus.on('user-action', (data) => {
46 console.log('User action:', data);
47});
48
49eventBus.on('data-updated', (data) => {
50 console.log('Data updated:', data);
51});
52
53// Emit events
54eventBus.emit('user-action', { type: 'click', target: 'button' });
55eventBus.emit('data-updated', { id: 123, changes: ['name', 'email'] });
56
57// Cleanup
58unsubscribe();Optimize event handling for better performance:
1// Passive event listeners for scroll/touch events
2document.addEventListener('touchstart', handleTouch, { passive: true });
3document.addEventListener('wheel', handleWheel, { passive: true });
4
5// AbortController for easy cleanup
6const controller = new AbortController();
7
8element.addEventListener('click', handleClick, {
9 signal: controller.signal
10});
11
12element.addEventListener('mouseover', handleMouseover, {
13 signal: controller.signal
14});
15
16// Later, remove all listeners at once
17controller.abort();
18
19// Event listener cleanup utility
20class EventManager {
21 constructor() {
22 this.listeners = [];
23 }
24
25 add(element, event, handler, options = {}) {
26 element.addEventListener(event, handler, options);
27 this.listeners.push({ element, event, handler, options });
28 }
29
30 remove(element, event, handler) {
31 element.removeEventListener(event, handler);
32 this.listeners = this.listeners.filter(
33 listener => !(listener.element === element &&
34 listener.event === event &&
35 listener.handler === handler)
36 );
37 }
38
39 removeAll() {
40 this.listeners.forEach(({ element, event, handler }) => {
41 element.removeEventListener(event, handler);
42 });
43 this.listeners = [];
44 }
45}Your progress is saved in this browser, on this device only.