π DOM Event Model and Types
Every click, keystroke, and scroll a user makes is an event β a signal the browser hands to your JavaScript. This lesson gives you the mental model behind that system: what an event object carries, the main families of events you'll meet, and how to fire your own custom events for clean, decoupled code.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Explain the DOM event model and the roles of the target, listener, handler, and event object
- Register listeners with
addEventListener()and describe why it beats on-handler properties and inline attributes - Read useful data off the event object β
type,target,key, coordinates, modifier keys - Categorize the common event types: mouse, keyboard, form, window/document, and media
- Create and dispatch a custom event to let components communicate
Estimated Time: 30β40 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Build a live keyboard inspector that reports every key and modifier you press.
In This Lesson
What Are DOM Events?
So far you've learned to read and change the DOM. Events are what make a page come alive: they are the mechanism that lets your code run in response to something happening β a user clicks a button, a form is submitted, an image finishes loading, the network drops. Without events, a web page would be a static poster; with them, it's an application.
π‘ A useful analogy β the doorbell. The button by the door is an element. Pressing it is the event (a "click"). The wiring that carries the signal is the event listener. The chime that plays is your handler function. You can wire different bells to different doors β just as you attach different listeners to different elements β and the house only reacts when a bell is actually rung.
The Event Model & Registration
Modern browsers follow the DOM Level 3 Events specification. A handful of terms describe the whole system:
π Key Terms
Event: an object representing something that happened (a click, a keypress, a page load).
Event target: the element on which the event occurred β event.target.
Event listener: the registration that says "when this event happens on this element, call this function".
Event handler: the function that actually runs.
Event object: the bundle of information (type, target, coordinates, keysβ¦) passed to your handler.
Three ways to register β and the one you should use
There are three historical ways to attach a handler. Only the first is recommended for real work.
1. addEventListener() β the modern standard
const button = document.getElementById('submit-button');
button.addEventListener('click', (event) => {
console.log('Button was clicked!');
console.log('The event object:', event);
});
2. On-event property β one handler only
const input = document.getElementById('username');
// Assigning again REPLACES this handler β you can only have one.
input.onchange = (event) => {
console.log('Value changed to:', event.target.value);
};
3. Inline HTML attribute β avoid this
<!-- Mixes behavior into markup; hard to maintain and a security risk. -->
<button onclick="handleClick()">Click Me</button>
β
Why addEventListener() wins
- You can attach many listeners for the same event on the same element.
- It supports the capture phase and an options object (
once,passive,signal). - It keeps behavior (JS) cleanly separated from structure (HTML).
- You can remove a listener later with
removeEventListener().
The Event Object
When an event fires, the browser builds an event object and passes it as the first argument to your handler. It is your window into what happened and where.
document.getElementById('my-button').addEventListener('click', (event) => {
console.log(event.type); // "click"
console.log(event.target); // the element that was actually clicked
console.log(event.currentTarget); // the element the listener is attached to
console.log(event.timeStamp); // ms since the page loaded
console.log(event.clientX, event.clientY); // pointer position in the viewport
event.preventDefault(); // cancel the browser's default action
event.stopPropagation(); // stop the event travelling to ancestors
});
β οΈ target vs. currentTarget
They are often the same, but not always. currentTarget is the element whose listener is running right now; target is the deepest element the event originated on. When a click bubbles up from a child, currentTarget is the parent with the listener while target is the child that was clicked. This distinction is the whole basis of event delegation (covered two lessons from now).
| Property / method | What it gives you | Example value |
|---|---|---|
type | The kind of event | "click", "keydown" |
target | The element the event started on | the clicked button |
currentTarget | The element the listener is bound to | a parent container |
timeStamp | Milliseconds since page load | 1234.56 |
preventDefault() | Cancels the default browser action | function |
stopPropagation() | Stops further bubbling/capturing | function |
Type-specific properties
Different events carry different extra data. A mouse event knows where the pointer is; a keyboard event knows which key was pressed.
// Mouse events β position & buttons
canvas.addEventListener('mousemove', (event) => {
console.log('viewport:', event.clientX, event.clientY);
console.log('page:', event.pageX, event.pageY);
console.log('within target:', event.offsetX, event.offsetY);
console.log('modifiers:', event.ctrlKey, event.shiftKey, event.altKey);
});
// Keyboard events β which key
input.addEventListener('keydown', (event) => {
console.log('key:', event.key); // e.g. "a", "Enter", "ArrowUp"
console.log('code:', event.code); // physical key, e.g. "KeyA"
if (event.key === '@') event.preventDefault(); // block a character
});
π‘ Prefer event.key over event.keyCode
The old keyCode property is deprecated. Use event.key for the character or named key ("Enter", "Escape", "ArrowLeft"), and event.code when you need the physical key regardless of keyboard layout (useful for games).
The Families of Events
There are hundreds of event types, but they cluster into a few families. You don't need to memorize them β recognize the families and look up specifics on MDN as you need them.
Mouse events
Note a subtle but important pair: mouseenter/mouseleave fire only for the element itself and do not bubble, while mouseover/mouseout also fire as the pointer crosses child elements and do bubble.
const trigger = document.getElementById('info-icon');
const tooltip = document.getElementById('tooltip');
trigger.addEventListener('mouseenter', () => {
const rect = trigger.getBoundingClientRect();
tooltip.style.left = `${rect.right + 10}px`;
tooltip.style.top = `${rect.top}px`;
tooltip.classList.add('visible');
});
trigger.addEventListener('mouseleave', () => {
tooltip.classList.remove('visible');
});
Keyboard events
document.addEventListener('keydown', (event) => {
// Ctrl+S / Cmd+S to save
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
event.preventDefault(); // stop the browser's Save dialog
saveDocument();
}
// Escape to close a modal
if (event.key === 'Escape') closeModal();
});
Form events
input fires on every keystroke; change fires only when the field loses focus after a change. Reach for input when you want live feedback.
const textarea = document.getElementById('message');
const counter = document.getElementById('char-counter');
const max = Number(textarea.getAttribute('maxlength'));
textarea.addEventListener('input', () => {
const remaining = max - textarea.value.length;
counter.textContent = `${remaining} characters remaining`;
counter.classList.toggle('warning', remaining < 20);
});
Window & document events
// Run setup as soon as the HTML is parsed (before images finish loading)
document.addEventListener('DOMContentLoaded', () => initApp());
// Everything, including images, is loaded
window.addEventListener('load', () => {
document.getElementById('loading-screen').style.display = 'none';
});
// Network status
window.addEventListener('online', () => setStatus('Online'));
window.addEventListener('offline', () => setStatus('Offline'));
π DOMContentLoaded vs load
DOMContentLoaded fires when the HTML is parsed and the DOM is ready β the earliest safe moment to touch elements. load waits for all resources (images, stylesheets, iframes). Most app initialization belongs in DOMContentLoaded; only code that truly needs image dimensions or similar should wait for load.
Custom Events
Beyond the built-in events, you can invent your own with CustomEvent and dispatch them on any element. This lets one part of your app announce "something meaningful happened" without knowing who is listening β a clean way to decouple components.
// 1. Create an event carrying application-specific data in `detail`
const productAdded = new CustomEvent('product:added', {
detail: { id: 12345, name: 'Wireless Headphones', price: 79.99, qty: 1 },
bubbles: true, // allow it to bubble up the tree
cancelable: true // allow listeners to preventDefault()
});
// 2. Dispatch it
document.getElementById('shopping-cart').dispatchEvent(productAdded);
// 3. Listen for it anywhere it bubbles to
document.addEventListener('product:added', (event) => {
const { name, price, qty } = event.detail;
console.log(`Added ${qty} Γ ${name}`);
updateCartTotal(price * qty);
});
π‘ When custom events shine
- Component communication without tight coupling β a settings panel emits
theme:changed, and any component can react. - Broadcasting state changes across an app.
- Plugin / extension hooks so third-party code can respond to your app's lifecycle.
Namespacing with a colon (cart:updated, user:login) keeps custom event names readable and collision-free.
Hands-on: Keyboard Inspector
ποΈ Build a live key reporter
Objective: Reinforce the event object by displaying details of every key the user presses.
Starter markup
<input id="probe" placeholder="Click here, then typeβ¦" />
<pre id="readout">Waiting for a keyβ¦</pre>
Your task
- Listen for
keydownon the input. - Show
event.key,event.code, and which modifier keys (Ctrl, Shift, Alt, Meta) are held. - When the user presses Enter, call
event.preventDefault()and append "(submit blocked)" to the readout.
π‘ Hint
Build an array of the active modifiers by filtering, then join them. Template literals make the readout easy: `key: ${event.key}`. Remember the handler receives the event object as its first parameter.
β Sample solution
const probe = document.getElementById('probe');
const readout = document.getElementById('readout');
probe.addEventListener('keydown', (event) => {
const mods = ['ctrlKey', 'shiftKey', 'altKey', 'metaKey']
.filter((m) => event[m])
.map((m) => m.replace('Key', ''));
let text =
`key: ${event.key}\n` +
`code: ${event.code}\n` +
`mods: ${mods.length ? mods.join(' + ') : 'none'}`;
if (event.key === 'Enter') {
event.preventDefault();
text += '\n(submit blocked)';
}
readout.textContent = text;
});
Notice that reading many properties off one event object is exactly how real features β shortcuts, games, accessibility helpers β are built.
Best Practices
| β Do | β Don't |
|---|---|
Register listeners with addEventListener() | Scatter onclick="β¦" attributes through your HTML |
Use event.key for keyboard logic | Rely on the deprecated event.keyCode |
Reach for input when you want live updates | Assume change fires on every keystroke |
Name custom events with a namespace (cart:updated) | Overwrite element.onclick and wonder why the first handler vanished |
Call preventDefault() deliberately, only when needed | Block default behavior everywhere "just in case" |
π― Quick Quiz
Question 1: Inside a click handler, which property always refers to the element the listener is attached to (not necessarily the one clicked)?
Question 2: You want to update a character counter on every keystroke. Which form event fits best?
Question 3: Why is addEventListener() preferred over setting element.onclick?
Summary & Quiz
π Key Takeaways
- Events connect user actions to your JavaScript; the model is target β listener β handler.
- Register with
addEventListener()β it supports multiple listeners, options, and removal. - The event object carries
type,target,currentTarget, coordinates, and keys; preferevent.key. - Events fall into families: mouse, keyboard, form, window/document, media.
- Custom events (
CustomEvent+dispatchEvent) let components talk without tight coupling.
π Further Reading
- MDN β Event interface
- MDN β addEventListener()
- javascript.info β Introduction to browser events
- MDN β Event reference (all event types)
π What's Next?
You now know what events are and which ones exist. Next we go deeper into the handlers themselves β the this context, the options object, one-time listeners, and how to remove listeners cleanly to avoid memory leaks.
π Great progress!
The page can now hear the user. Let's learn to respond to it well.